当前位置:首页 > 批量操作 > 正文内容

批量查询ERC20代币余额代码示例:从逐一问到一次问完

使用技巧1个月前 (06-07)批量操作32

手上有500个地址,想查它们持有某代币的余额。写个for循环逐个调balanceOf,跑完发现花了20多秒,还被节点429限流。

问题出在每查一个地址就发一次独立RPC请求,500个地址就是500次网络往返。

下面直接给两种能跑通的方案,从逐个查到一次查完,看完就能用。

各大交易所注册链接:

欧易官网

币安官网

芝麻Gate


先看常规写法:逐个查

const { ethers } = require('ethers');
const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com');

const erc20Abi = ['function balanceOf(address owner) view returns (uint256)'];
const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC

async function getBalance(address) {
  const contract = new ethers.Contract(tokenAddress, erc20Abi, provider);
  return (await contract.balanceOf(address)).toString();
}

// 500个地址 = 500次RPC请求,慢且容易被限流
const addresses = ['0x...', '0x...', '...'];
for (const addr of addresses) {
  console.log(`${addr}: ${await getBalance(addr)}`);
}

耗时参考

地址数逐个查询Multicall批量
10个3-5秒0.3秒
50个15-25秒0.5秒
500个3-5分钟2-4秒

逐个与批量性能对比



核心思路:Multicall3合约

Multicall是部署在各条EVM链上的公共合约,把多个只读调用打包成一个请求发给节点,节点一次处理、一次返回所有结果。

以太坊主网地址:0xcA11bde05977b3631167028862bE2a173976CA11

Polygon、BSC、Arbitrum、Optimism都是同一个地址。


方案一:ethers-multicall(最推荐)


npm install ethers@5 @gainsnetwork/ethers-multicall
const { ethers } = require('ethers');
const { Contract, Provider } = require('@gainsnetwork/ethers-multicall');

const provider = new ethers.providers.JsonRpcProvider('https://eth.llamarpc.com');
const multicallProvider = new Provider(provider);
await multicallProvider.init();

const erc20Abi = ['function balanceOf(address) view returns (uint256)'];
const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const addresses = [
  '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
  '0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503',
  // ...更多地址
];

const tokenContract = new Contract(tokenAddress, erc20Abi);
const calls = addresses.map(addr => tokenContract.balanceOf(addr));
const results = await multicallProvider.all(calls);

addresses.forEach((addr, i) => {
  console.log(`${addr}: ${ethers.utils.formatUnits(results[i], 6)} USDC`);
});

核心就三步:

  1. Provider替代原生provider

  2. 把每个查询封装成call对象(此时不发请求)

  3. all()一次性提交,只走一次网络


方案二:直接调Multicall3合约(零依赖)

不想加第三方库,可以自己拼合约调用:

const { ethers } = require('ethers');

const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com');
const multicallAbi = [
  'function aggregate3(tuple(address target,bool allowFailure,bytes callData)[] calls) view returns (tuple(bool success,bytes returnData)[])'
];

const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11';
const tokenAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const BALANCE_OF_ID = '0x70a08231'; // keccak256("balanceOf(address)")[:4]

const addresses = ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'];

async function batchQuery() {
  const mc = new ethers.Contract(multicallAddress, multicallAbi, provider);

  const calls = addresses.map(addr => ({
    target: tokenAddress,
    allowFailure: true,
    callData: BALANCE_OF_ID + addr.slice(2).padStart(64, '0')
  }));

  const results = await mc.aggregate3(calls);

  results.forEach((r, i) => {
    const bal = r.success
      ? ethers.formatUnits('0x' + r.returnData.slice(-64), 6)
      : '0';
    console.log(`${addresses[i]}: ${bal} USDC`);
  });
}

batchQuery();

代码稍长,但不依赖任何第三方封装,适合对底层调用有精确控制需求的场景。


方案三:@evm-balance/sdk(多链多代币场景)

如果要同时查多条链、多种代币,这个SDK更省事:

npm install @evm-balance/sdk
import { createBalanceFetcher, mainnet } from '@evm-balance/sdk';

const fetcher = createBalanceFetcher({
  chain: mainnet,
  rpc: 'https://eth.llamarpc.com'
});

const results = await fetcher.FetchBalances({
  config: { chain: mainnet },
  mode: { type: 'addresses', addresses: ['0x...', '0x...'] },
  options: {
    tokens: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],
    includeNative: true
  }
});

results.forEach(r => {
  console.log(`${r.address}:`, r.balances.map(b => `${b.symbol} ${b.formatted}`).join(', '));
});

内部用的也是Multicall3,自带分页(默认500条/批)、超时控制和进度追踪。


Multicall3架构示意

实用技巧

CSV地址导入

const fs = require('fs');
const csvData = fs.readFileSync('addresses.csv', 'utf-8');
const addresses = csvData
  .split('\n')
  .map(l => l.trim())
  .filter(a => a.startsWith('0x') && a.length === 42);

分批处理(防单次请求过大)

const BATCH = 200;

async function queryAll(addresses) {
  const all = [];
  for (let i = 0; i < addresses.length; i += BATCH) {
    const batch = addresses.slice(i, i + BATCH);
    all.push(...await batchBalanceQuery(batch));
  }
  return all;
}

小数位处理

不同代币小数位不一样,别硬写18:

const decimals = {
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': 6,  // USDC
  '0xdAC17F958D2ee523a2206206994597C13D831ec7': 6,  // USDT
  '0x6B175474E89094C44Da98b954EedeAC495271d0F': 18  // DAI
};

方案怎么选

场景推荐
<20个地址,单链逐个查就行,别折腾
20-1000个地址,单链ethers-multicall,简单够用
要精确控制底层调用直接调Multicall3合约
多链+多代币混合查@evm-balance/sdk

免责声明

代码仅供开发参考,不构成任何投资建议。请根据实际合约地址和ABI自行调整,合理使用RPC节点。


相关文章

批量导出所有地址私钥的方法

批量导出所有地址私钥的方法

整理钱包的时候发现,几年下来助记词没丢,但到底生成了多少个地址、每个地址的私钥是什么,完全没数。有些空投领到了早期地址里,有些DeFi协议还残留着零碎资金,想归集一下,钱包App却只能一个一个地址点进...

批量转账怎么算Gas费?按这个公式来

批量转账怎么算Gas费?按这个公式来

给200个地址空投代币,一笔一笔转的话,不仅要盯着屏幕操作两小时,200笔Gas费叠加下来,光手续费就把空投预算吃掉一大半。做空投、发奖励、分发代币都绕不开同一个问题:批量转账到底花多少Gas费?不算...

多钱包管理太累?这套批量授权+转账操作,10分钟搞定100个地址

多钱包管理太累?这套批量授权+转账操作,10分钟搞定100个地址

一、币安多钱包管理,到底累在哪?说句得罪人的话:2026年了,币安的多钱包管理体验,跟三年前比几乎没进步。你开了BTC、ETH、BSC、Solana四个钱包,每个钱包单独设置Gas费、单独管理授权、单...

交易所钱包批量撤销授权教程:5分钟清理完,再也不怕DApp偷权限

交易所钱包批量撤销授权教程:5分钟清理完,再也不怕DApp偷权限

2026年Web3行业进入高速扩张期,各类新晋DApp、链上挖矿、NFT铸造、零撸交互项目层出不穷,普通用户为参与链上活动,频繁授权钱包权限,却普遍忽略授权背后的隐性风险。多数用户存在认知误区:退出D...

Web3钱包高效玩家:批量转账、批量领空投与一键归集资产的隐秘技巧

Web3钱包高效玩家:批量转账、批量领空投与一键归集资产的隐秘技巧

一、引言:Web3资产运维差距,藏在小众高阶工具里2026年多链交互成为Web3用户常态,绝大多数交易者和资产持有者,都持有多条公链、多个地址的零散代币、NFT与空投权益。传统手动单地址转账、逐链查空...

Web3钱包批量转账与批量Mint实操,效率提升10倍

Web3钱包批量转账与批量Mint实操,效率提升10倍

一、前言:撸毛内卷时代,手动操作彻底被淘汰2026年Layer2、新公链、零撸NFT项目批量爆发,优质空投、白名单权益、早期交互奖励的核心门槛,从“能否参与”转变为“参与数量与交互频率”。常规个人撸毛...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。