标签:Go、以太坊、ethclient、ETH 余额、精度、Wei、Ether、区块链开发、以太坊 API
上期我们已把主网节点信息拿到手,本期聚焦「只凭地址就能查到的 ETH 余额」。注意:查询对象为 原生 ETH 而非 ERC-20 代币,也不要求你掌握私钥。跟着本文一步步操作,3 分钟把余额“挖”出来!
准备工作:依赖与网络
- 安装 go-ethereum 官方库:
go get github.com/ethereum/go-ethereum/ethclient - 获取公开节点或自建 RPC(本文用 Infura 做演示)。
- 终端设置环境变量
export INFURA_API_KEY="你的Key"。
搞定之后,用 ethclient.Dial(url) 连接主网,共三步即可查询。
核心 API:client.BalanceAt 解析
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"math/big"
)
ctx := context.Background()
addr := common.HexToAddress("0xd8dA...6045")
bn := big.NewInt(18382246) // 指定区块高度
balance, _ := client.BalanceAt(ctx, addr, bn)- 当
bn = nil时返回最新高度余额。 - 返回值
*big.Int,单位 Wei(1 Ether = 1e18 Wei)。永远不要直接用 float64 传值,否则可能 瞬间丢失小数后 11 位精度!
换单位?先把精度的坑填上!
在钱包里常见的“935.143007 ETH”听起来很优雅,但链上记录是整整 935143001746486974073 Wei。转换时必须选用支持大整数的数据结构。
方法一:标准库 big.Float
bf := new(big.Float).SetInt(balance)
bf.Quo(bf, big.NewFloat(1e18))输出结果会像 935.1430017——没错,它已经四舍五入了一次!
方法二:decimal 库(推荐)
import "github.com/shopspring/decimal"
wei := decimal.RequireFromString(balance.String())
eth := wei.Div(decimal.New(1, 18)) // 即 1e18decimal 基于 十进制浮点 存算,连微小尾差都能挽回,财务对账、DEX 比价场景都建议用它。
参数场景演练:选择区块号 or 不选?
| 需求 | 调用例子 |
|---|---|
| 当前余额 | BalanceAt(ctx, addr, nil) |
| 分叉时余额 | BalanceAt(ctx, addr, big.NewInt(1920000)) |
| 合约部署前状况 | 传入部署区块高度减 1 即可 |
快速比对不同高度的余额变化,用来 追踪大额转账、检测黑客攻击 非常高效。
完整可运行示例
把下方代码保存为 main.go,直接 go run . 体验:
package main
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
decimal "github.com/shopspring/decimal"
"log"
"math/big"
"os"
)
func main() {
apiKey := os.Getenv("INFURA_API_KEY")
if apiKey == "" {
log.Fatal("请先设置 INFURA_API_KEY 环境变量")
}
client, err := ethclient.Dial("https://mainnet.infura.io/v3/" + apiKey)
if err != nil {
log.Fatalf("无法连接节点: %v", err)
}
ctx := context.Background()
addr := common.HexToAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
block := big.NewInt(18382246)
wei, err := client.BalanceAt(ctx, addr, block)
if err != nil {
log.Fatalf("查询失败: %v", err)
}
log.Printf("余额(Wei): %v", wei)
eth := decimal.RequireFromString(wei.String()).Div(decimal.New(1, 18))
log.Printf("余额(Ether, decimal): %v", eth)
}运行输出示例:
2025/06/23 12:34:56 余额(Wei): 935143001746486974073
2025/06/23 12:34:56 余额(Ether, decimal): 935.143001746486974073精度 丝毫无损,对接报表或前端显示时再继续四舍五入即可。
FAQ:最常见疑问一次说清
Q1:我只拿到私钥,能从私钥反向查地址吗?
A:可以。使用 go-ethereum/crypto 中的 ecdsa 套件可从私钥推导出 common.Address,随后再用本文方法查余额。
Q2:能不能一次查 n 个地址的余额?
A:BalanceAt 只能一次查一个。可并发调用,或用批次 RPC 封装(如 eth_getBalance 的多请求 JSON-RPC)。
Q3:查询速率有限制怎么办?
A:无论你用 Infura、Alchemy 还是自建节点,都会有限流。建议本地缓存、批量同步、或者在项目中接入 WebSocket 订阅 newHeads,仅在区块变动时刷新余额,减少 80% 以上请求。
Q4:Decimal 会不会拖慢性能?
A:decimal 使用大整数表示,比 big.Float 稍慢,但在日常百万级精度需求场景差距微乎其微,精度优先时完全值得。
案例实战:3 秒查出黑客出逃前余额
2024 年底,某项目被盗。开发者在得知黑客地址后,直接用脚本:
- 传入区块号
block := big.NewInt(19500000) - 调用
BalanceAt记录黑客真余额 - 与后续几分钟的余额对比验证转账额
全程不到 3 秒,为法务取证争取了时间。精度问题?decimal 控场无误。
结语与进阶阅读
本文通过 balanceAt 让一个 HTTP 请求就能把链上 ETH 余额握在手中;配合环境变量、错误处理与并发池,你在生产环境也已稳如老狗。
下一步:
- 用
client.CodeAt(ctx, addr, nil)检查是否为智能合约,区分 EOA / CA。 - 结合
client.PendingBalanceAt查看内存池里未确认的转账变动。 - 将结果推送到 Prometheus,做实时仪表盘。
Keep coding,余额不落空!