如何查询一百万个以太坊地址上的所有代币余额

本文将带你从以太坊的存储模型开始,深入理解如何通过RPC查询某个地址上持有的所有代币(ERC-20),并介绍相关工具和局限。一、以太坊的账户与存储模型以太坊有两类账户:外部账户(EOA):由私钥控制,用户的钱包地址就是EOA。合约账户:由代码控制,是智能合约部署后的地址。每个账户

本文将带你从以太坊的存储模型开始,深入理解如何通过 RPC 查询某个地址上持有的所有代币(ERC-20),并介绍相关工具和局限。


一、以太坊的账户与存储模型 以太坊有两类账户:

  1. 外部账户(EOA):由私钥控制,用户的钱包地址就是 EOA。
  2. 合约账户:由代码控制,是智能合约部署后的地址。 每个账户都有一个状态结构(Account State):
    • nonce(用来记录发出的交易,每次交易+1,从0开始)
    • balance(以太币余额)
    • storageRoot(指向该合约的「存储 trie(状态树)」的根节点哈希)
    • codeHash(指向该合约代码(bytecode)的哈希) 对于合约账户,核心是 storageRoot,它指向一个存储 Trie(状态树),键值对存储了所有的合约内部变量。 对于外部账户, storageRoot= keccak256(RLP("")= 0x56e81f171bcc55a6ff8345e69... codeHash = keccak256('') = 0x5f...36 引申: 判断是不是合约账户需要用 eth_getCode("0xAddress", "latest")

二、ERC-20 代币是如何存储的? ERC-20 是一种智能合约接口,代币不是“存在地址上”,而是记录在代币合约的存储中。 关键字段: mapping(address => uint256) public balanceOf; 这意味着,每个代币合约内部都有一个哈希映射,记录了哪个地址持有多少代币。 以太坊虚拟机(EVM)会将这个 mapping 映射编译成一个如下的存储位置: keccak256(pad(address) ++ pad(slot_number)) slot_numberbalanceOf 在合约中的存储槽位(通常是 0)。所以,要知道某地址在某个代币合约上的余额,就必须计算这个地址的 balanceOf 的位置,并读取合约的存储。 但这种方式 不能批量查所有代币,因为我们不知道地址持有哪些代币合约。同时我们也不知道链上有哪些代币合约。


三、RPC 是怎么工作的? 以太坊节点对外暴露 JSON-RPC 接口。常用的 RPC 方法包括:

  • eth_getBalance:获取地址的 ETH 余额。
  • eth_call:调用合约的 balanceOf 方法。
  • eth_getStorageAt:获取合约某个槽位的原始存储值。 例如:
{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [{
    "to": "0xTokenAddress",
    "data": "0x70a08231000000000000000000000000<Address>" // balanceOf(address)
  }, "latest"],
  "id": 1
}

这个调用执行合约的 balanceOf(address) 函数。


四、那么如何获取一个地址上所有的代币余额? 方式一:链上原生方法不可行 以太坊本身不维护一个「地址拥有的代币列表」,因为每个代币的持仓信息都存在于代币合约自己的存储中。 换句话说,以太坊的状态是分散在每个代币合约中的,没有统一索引。 因此: 无法通过一个 RPC 调用获取某个地址持有的所有代币。


方式二:使用离线索引服务(如 Etherscan、Covalent、Zerion、Alchemy) 这类服务会:

  1. 全链扫描所有 ERC-20 转账事件(Transfer)
  2. 为每个地址构建代币资产索引
  3. 提供聚合 API 查询: 比如: GET https://api.covalenthq.com/v1/eth-mainnet/address/<address>/balances_v2/ 返回:
{
  "data": {
    "items": [
      {
        "contract_name": "USDC",
        "contract_address": "0x...",
        "balance": "1000000",
        "decimals": 6
      },
      ...]
  }
}

方式三:自己实现索引器 如果你不想依赖第三方服务,可以自己实现:

  1. 从区块 0 开始,扫描所有交易和合约事件。
  2. 解析所有 ERC-20 合约的 Transfer 事件:
  3. event Transfer(address indexed from, address indexed to, uint256 value);
  4. 为每个地址建立代币转入转出索引。
  5. 可选:将合约地址做缓存,定期抓取 symbol, decimals, name 信息。 这种方式要求你运行一个节点 + 搭建 ETL 管道和数据库索引。

方案四:通过cpbox.io代理实现索引器

如果你只是查询个别地址上的token余额,你可以使用余额查询,支持 ETH, BSC 等等的evm链,还支持 Tron, Solana, Sui,BTC等等的主流链。 如果你要查询上千,上网甚至几十万的地址余额,cpbox有一个余额监听的功能,此功能可以帮助用户监听上百万的地址上所有的ERC-20 代币,NFT等等所有的资产,也支持主流的EVM链,首先你可以联系cpbox的小助手查询历史的数据,同时还可以满足你未来有资产进来通知你的需求。


五、常用工具和库 Web3 库

  • web3.js / ethers.js 可用于编写脚本批量查询某地址在指定代币上的余额。 示例代码(ethers.js):
const { ethers } = require("ethers");

const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_KEY");
const tokenContract = new ethers.Contract(tokenAddress, ["function balanceOf(address) view returns (uint256)","function decimals() view returns (uint8)"
], provider);

const balance = await tokenContract.balanceOf(userAddress);

六、补充:对 NFT 的支持 NFT(如 ERC-721)也使用 ownerOf(tokenId) 来记录归属,和 ERC-20 类似,也无法直接通过链上查询某地址持有哪些 NFT,通常也需要事件索引或 OpenSea 等聚合器。


如果你有特定使用场景,比如只查询某一小批代币,可以批量调用 balanceOf。如果要构建钱包/浏览器插件级别的功能,建议依赖第三方 API 或自建索引。


有问题你可以联系  https://www.cpbox.io/ 最底下有联系方式,所有的问题都欢迎来咨询

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
CryptoBox
CryptoBox
0x9099...2eE6
https://www.cpbox.io 是集web3批量工具, 一键发Token, 市值管理为一身的专业web3工具