You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升使用ethers批量查询多合约代币余额的速度?

现有代码性能瓶颈

  1. 串行执行:循环内每一次await都会等待前一个RPC请求返回后才发起下一个请求,100个代币就要走100次RPC往返,耗时直接累加
  2. 冗余请求:你的tokens对象已经存储了decimals、symbol字段,代码仍然重复发起链上调用查询decimals,平白多了一倍请求量
  3. 单代币单请求:每个代币的balanceOf都单独发起RPC请求,请求量大会触发RPC节点限流,也会额外增加握手、传输的开销

方案1:最小成本优化(改几行代码即可提速3~10倍)

把串行逻辑改成并行批量执行,同时删除冗余的decimals链上查询,修正原代码大数比较的精度问题:

// 先过滤掉不需要查询的原生币地址
const erc20Tokens = Object.values(tokens).filter(t => t.address !== "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
// 批量发起所有balanceOf查询,并行执行
const balancePromises = erc20Tokens.map(async token => {
  const contract = new ethers.Contract(token.address, Abi, provider)
  const balanceWei = await contract.balanceOf(address)
  return {
    ...token,
    balanceWei
  }
})
const allTokenBalances = await Promise.all(balancePromises)
// 过滤余额大于0的,直接用本地存的decimals计算即可
const validTokens = allTokenBalances.filter(t => t.balanceWei.gt(0)).map(t => {
  // 用ethers内置方法格式化,避免大数精度问题
  const balance = ethers.utils.formatUnits(t.balanceWei, t.decimals)
  return {
    address: t.address,
    decimals: t.decimals,
    symbol: t.symbol,
    balance: balance
  }
})
// 输出结果
validTokens.forEach(t => {
  console.log(t.address, t.decimals, t.symbol, t.balance)
})

方案2:Multicall批量调用(适合代币数量超过20个的场景,提速10~100倍)

通过Multicall合约把所有balanceOf调用打包成1次RPC请求,避免大量并发请求触发RPC限流,网络开销降到最低:

// 以太坊主网Multicall合约地址,其他链可替换成对应链的官方Multicall地址
const MULTICALL_ADDRESS = "0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441"
const MULTICALL_ABI = [{"constant":true,"inputs":[{"components":[{"name":"target","type":"address"},{"name":"callData","type":"bytes"}],"name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"name":"blockNumber","type":"uint256"},{"name":"returnData","type":"bytes[]"}],"payable":false,"stateMutability":"view","type":"function"}]

const erc20Tokens = Object.values(tokens).filter(t => t.address !== "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
// 构造批量调用参数
const iface = new ethers.utils.Interface(Abi)
const calls = erc20Tokens.map(token => {
  const callData = iface.encodeFunctionData("balanceOf", [address])
  return {
    target: token.address,
    callData: callData
  }
})
// 一次请求获取所有余额
const multicall = new ethers.Contract(MULTICALL_ADDRESS, MULTICALL_ABI, provider)
const { returnData } = await multicall.aggregate(calls)
// 解析返回结果
const validTokens = []
erc20Tokens.forEach((token, index) => {
  const balanceWei = ethers.BigNumber.from(returnData[index])
  if (balanceWei.gt(0)) {
    const balance = ethers.utils.formatUnits(balanceWei, token.decimals)
    validTokens.push({
      address: token.address,
      decimals: token.decimals,
      symbol: token.symbol,
      balance
    })
  }
})
// 输出结果
validTokens.forEach(t => {
  console.log(t.address, t.decimals, t.symbol, t.balance)
})

方案3:预索引方案(适合生产环境高频查询场景)

如果你的服务需要给大量用户频繁查询余额,可以自行部署索引服务,监听链上所有ERC20的Transfer事件,把用户余额实时同步到数据库,用户查询时直接读数据库即可,响应耗时可以降到毫秒级,完全不需要实时发起链上调用。


内容的提问来源于stack exchange,提问作者Mariusz Sidorowicz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 07:24:04