批量查询ERC20代币余额代码示例:从逐一问到一次问完
手上有500个地址,想查它们持有某代币的余额。写个for循环逐个调balanceOf,跑完发现花了20多秒,还被节点429限流。
问题出在每查一个地址就发一次独立RPC请求,500个地址就是500次网络往返。
下面直接给两种能跑通的方案,从逐个查到一次查完,看完就能用。
各大交易所注册链接:
先看常规写法:逐个查
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`);
});核心就三步:
用
Provider替代原生provider把每个查询封装成call对象(此时不发请求)
调
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条/批)、超时控制和进度追踪。

实用技巧
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节点。





