从零开发区块链应用:一步一步查询以太坊余额

·

进阶内容:本文不会只告诉你调一个 API 就能拿到余额,而是把 以太坊账户模型、State Trie、代码流程、实战示例 融在一起,帮助你在 节点直连、缓存策略、主网与历史区块 等场景都能游刃有余。

目录

  1. State Trie:为什么账户余额能去中心化
  2. 查询逻辑拆解:区块 → 交易 → 余额
  3. Golang 实战:主网/历史/待处理余额
  4. 代币余额拓展:ERC-20 合约查询
  5. 常见问题(FAQ)

一、State Trie 与 Account 模型

1.1 什么是 State Trie

以太坊的 Block.Header.Root 称为 stateRoot。它是一颗 Patricia-Merkle Trie(简称 PMT),记录了所有账户最新快照。

也就是说,通过 stateRoot 可在本地 KV 数据库中定位到某一账户的 Account 对象。

1.2 Account 结构

type Account struct {
    Nonce    uint64      // 交易序号
    Balance  *big.Int    // ETH 余额(wei)
    Root     common.Hash // 合约存储树
    CodeHash []byte      // 合约代码哈希
}

而 StateObject 则是 Account 在 Trie 中的动态载体

type stateObject struct {
    address  common.Address
    addrHash common.Hash
    data     Account
    db       *StateDB
}

stateObject.data.Balance 发生变化(转账、挖矿、手续费),Trie 会随之重组根哈希,形成新状态。


二、余额查询思路拆解

很多人误以为“余额”直接在区块里;其实要:

  1. lastBlock.header.Root → 得到 State Trie 根。
  2. 从缓存优先命中 stateObject,若无则走 Trie 查询得到 Account
  3. 返回 account.Balance
这就是为什么 BalanceAt(nil) 等于最新余额,而 BalanceAt(blockNumber) 等于历史状态的原理。

三、Golang 实战:三步完成主网余额查询

3.1 环境准备

3.2 最新余额

client, _ := ethclient.Dial("https://mainnet.infura.io")
addr := common.HexToAddress("0x71c...6f")
balance, _ := client.BalanceAt(context.Background(), addr, nil) // wei

👉 想一次读完区块高度到余额的所有细节?点这里立即体验

3.3 历史区块余额

blockNum := big.NewInt(5532993)
balanceHist, _ := client.BalanceAt(context.Background(), addr, blockNum)

3.4 待处理余额(内存池)

pendingBalance, _ := client.PendingBalanceAt(context.Background(), addr)

3.5 ETH 单位换算

wei := new(big.Float).SetInt(balanceHist)
eth := new(big.Float).Quo(wei, big.NewFloat(1e18))
fmt.Println(eth.String()) // 25.729...
别忘了:math/big 能避免精度错误,而用 Float64 做金融记账易踩坑。

码友福利:下面给出可直接复制的完整 main.go,复制即可跑。


四、token 余额查询(ERC-20 案例)

4.1 准备参数

selector := "0x70a08231000000000000000000000000" + strings.TrimPrefix(addr.Hex(), "0x")

4.2 调用 eth_call

msg := ethereum.CallMsg{
    To:   &contractAddress,
    Data: common.FromHex(selector),
}
result, _ := client.CallContract(ctx, msg, nil)
// 回调后用 big.NewInt(0).SetBytes(result) 解码数量

👉 三分钟快速查看代币余额与转账记录


五、常见问题(FAQ)

Q1 为什么 BalanceAt 和 Etherscan 数值偶尔有差距?

A1 主网出块活跃度与节点同步延迟都会导致时间差,一般 1–2 个区块后自动对齐。

Q2 可以把整个 State Trie 缓存到 Redis 吗?

A2 可以,但 Trie 节点量巨大。建议用 LRU 缓存 + 树剪枝,仅缓存热门地址。

Q3 节点宕机还能查余额吗?

A3 常见做法是多 RPC 容灾(Infura、Alchemy、自建节点)轮询。

Q4 ETH 最小单位是多少?

A4 1 wei = 10⁻¹⁸ ETH,所有链上金额均为整数 wei。

Q5 查询一次要消耗 Gas 吗?

A5 不消耗,所有 eth_getBalanceeth_call 都是只读操作,零 Gas。

Q6 合约地址余额为何可能是 0?

A6 合约把 ETH 转出后经 selfdestructmsg.sender.transfer,余额可走空,但 ERC-20 代币仍保留在storage中,需下面第五部分方法单独查。


通过本文,你已掌握:账户结构、State Trie、代码示例、缓存技巧以及 ERC-20 余额查询——查询以太坊余额再无盲点。下一步?把同样的思路迁移到 Polygon、BNB Smart Chain Try it now!