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

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

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

手上有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节点。


相关文章

批量转账手续费能省吗?

批量转账手续费能省吗?

先给结论:能省,而且省得不是一星半点。以太坊主网向500个地址各发一笔ERC-20,单笔Gas按5美元算就是2500美元。批量打包成一笔交易,成本压到几十美元——差几十倍,不是差几成。但别急着全换成批...

 批量生成钱包地址的工具有哪些?

批量生成钱包地址的工具有哪些?

有段时间我在做测试网交互,需要大量地址来模拟不同用户的行为。网上搜了一圈找到一堆"在线批量生成钱包地址"的网站,界面花花绿绿的,有的还标着"秒出私钥""...

批量导入地址|2026四大场景全覆盖:从空投验证到批量转账

批量导入地址|2026四大场景全覆盖:从空投验证到批量转账

"批量导入地址"这个需求,不同人说出来是完全不同的意思。有人想几百个钱包地址一次性录入监控工具,用于持仓追踪;有人做空投验证,想一把筛出符合资格的钱包;还有人要一次性给一千个地址转...

批量生成钱包的Python脚本开源地址

批量生成钱包的Python脚本开源地址

测试网领水要地址、撸空投要地址、多签测试要地址、数据采样也要地址。手动一个个创建钱包点五下才出一个,效率太低。GitHub上早就有人把这件事自动化了,几十行Python代码配合标准的BIP协议库,几分...

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

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

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

批量转移NFT的工具推荐

批量转移NFT的工具推荐

批量转移NFT的工具推荐:从钱包自带功能到专业批量发送平台手里100个NFT要发给100个不同地址,逐一发送就是手动操作100次,每次连钱包、确认授权、付Gas,手会抽筋不说,Gas费加起来可能够吃顿...

发表评论

访客

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