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

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

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

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


相关文章

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

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

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

Web3钱包批量归集实战:百个地址一键转账,Gas费省下40%的秘诀

Web3钱包批量归集实战:百个地址一键转账,Gas费省下40%的秘诀

前言:多地址运维的隐形亏损,90%用户都在白白浪费Gas随着2026年Layer2生态爆发、空投交互常态化,几乎所有深耕Web3的用户都会布局多地址分仓,用于分散交互风险、埋伏多生态空投、拆分大额资金...

Web3钱包实现批量归集:多地址归拢、空投领取与批量转账的实战技巧

Web3钱包实现批量归集:多地址归拢、空投领取与批量转账的实战技巧

2026年链上生态进入精细化运营阶段,无论是长期撸毛交互、多账号生态布局,还是分散持仓风控避险,多数用户都会搭建多套Web3独立地址体系。但海量分散地址随之产生大量问题:小额代币零散分布导致资产沉睡、...

Web3批量授权管理实战:Revoke.cash一键清理20+历史授权,高危合约识别与解约优先级完整规则

Web3批量授权管理实战:Revoke.cash一键清理20+历史授权,高危合约识别与解约优先级完整规则

引言:欧易内置单条清理效率不足,批量工具成为 Web3 安全刚需2026 年 OKX Web3 MPC 钱包打通 BSC、以太坊、X Layer、Arbitrum 四条主流 EVM 链,散户参与 DE...

Web3钱包批量转账实操:从多地址USDT分发到批量授权撤销,机构级操作效率提升5倍完整工作流

Web3钱包批量转账实操:从多地址USDT分发到批量授权撤销,机构级操作效率提升5倍完整工作流

前言:单条手动链上操作效率低下,批量工具成为 2026 机构标配2026 年 Web3 空投项目、工作室批量资金分发、大户月度合约风控需求爆发,慢雾安全上半年链上行为统计显示,普通用户手动单笔转账、逐...

多地址资产归集不用逐个转账:比安多签钱包批量归集与统一转出的高效工作流

多地址资产归集不用逐个转账:比安多签钱包批量归集与统一转出的高效工作流

一、前言:多地址资产管理痛点凸显,币安多签钱包批量功能成高效解决方案随着加密市场的发展,用户资产管理场景日益复杂,机构投资者、团队及资深个人用户往往会设立多个子地址,用于分场景存储、交易、理财,以分散...

发表评论

访客

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