本文将带你从以太坊的存储模型开始,深入理解如何通过RPC查询某个地址上持有的所有代币(ERC-20),并介绍相关工具和局限。一、以太坊的账户与存储模型以太坊有两类账户:外部账户(EOA):由私钥控制,用户的钱包地址就是EOA。合约账户:由代码控制,是智能合约部署后的地址。每个账户
本文将带你从以太坊的存储模型开始,深入理解如何通过 RPC 查询某个地址上持有的所有代币(ERC-20),并介绍相关工具和局限。
一、以太坊的账户与存储模型 以太坊有两类账户:
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_number
是 balanceOf
在合约中的存储槽位(通常是 0)。所以,要知道某地址在某个代币合约上的余额,就必须计算这个地址的 balanceOf
的位置,并读取合约的存储。
但这种方式 不能批量查所有代币,因为我们不知道地址持有哪些代币合约。同时我们也不知道链上有哪些代币合约。
三、RPC 是怎么工作的? 以太坊节点对外暴露 JSON-RPC 接口。常用的 RPC 方法包括:
{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{
"to": "0xTokenAddress",
"data": "0x70a08231000000000000000000000000<Address>" // balanceOf(address)
}, "latest"],
"id": 1
}
这个调用执行合约的 balanceOf(address) 函数。
四、那么如何获取一个地址上所有的代币余额? 方式一:链上原生方法不可行 以太坊本身不维护一个「地址拥有的代币列表」,因为每个代币的持仓信息都存在于代币合约自己的存储中。 换句话说,以太坊的状态是分散在每个代币合约中的,没有统一索引。 因此: 无法通过一个 RPC 调用获取某个地址持有的所有代币。
方式二:使用离线索引服务(如 Etherscan、Covalent、Zerion、Alchemy) 这类服务会:
GET https://api.covalenthq.com/v1/eth-mainnet/address/<address>/balances_v2/
返回:{
"data": {
"items": [
{
"contract_name": "USDC",
"contract_address": "0x...",
"balance": "1000000",
"decimals": 6
},
...]
}
}
方式三:自己实现索引器 如果你不想依赖第三方服务,可以自己实现:
方案四:通过cpbox.io代理实现索引器
如果你只是查询个别地址上的token余额,你可以使用余额查询,支持 ETH, BSC 等等的evm链,还支持 Tron, Solana, Sui,BTC等等的主流链。 如果你要查询上千,上网甚至几十万的地址余额,cpbox有一个余额监听的功能,此功能可以帮助用户监听上百万的地址上所有的ERC-20 代币,NFT等等所有的资产,也支持主流的EVM链,首先你可以联系cpbox的小助手查询历史的数据,同时还可以满足你未来有资产进来通知你的需求。
五、常用工具和库 Web3 库
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/ 最底下有联系方式,所有的问题都欢迎来咨询
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!