用 JSON-RPC 裸读链
ethers.js、viem、web3j——所有这些 SDK 底下都是同一件事:向节点发 JSON-RPC 请求。这一章我们把 SDK 拿开,直接用 curl 和 fetch 跟链对话。当你亲手拼过一次请求,以后所有 SDK 对你都不再是黑盒,调试时也能一眼看穿"它到底发了什么"。
节点的对外接口就是一个 HTTP 端点
一个以太坊节点对外暴露一个 JSON-RPC 端点(一个 URL)。你 POST 一个 JSON 过去,它回一个 JSON。协议是标准的 JSON-RPC 2.0,长这样:
{
"jsonrpc": "2.0",
"method": "eth_blockNumber", // 调哪个方法
"params": [], // 参数数组
"id": 1 // 你自己编的请求编号,用于匹配响应
}
你可以完全把它当成一个只有一个 endpoint、用 method 字段区分动作的 REST 后端。eth_getBalance ≈ GET /balance,eth_sendRawTransaction ≈ POST /tx。ethers/viem 之于 JSON-RPC,正如 Retrofit 之于 OkHttp:给你类型安全的方法签名,底下还是在拼 HTTP 请求。理解了这层,SDK 就没有魔法了。
先拿一个 RPC 端点
你需要一个节点的 URL。三条路:公共 RPC(如 https://rpc.sepolia.org,免费但有限流)、节点服务商(Alchemy/Infura,注册拿带 key 的 URL)、或自建节点。下面例子用占位 $RPC 代表你的端点。
用 curl 读链
curl -s $RPC \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' # 响应: {"jsonrpc":"2.0","id":1,"result":"0x14a2b3c"} # result 是十六进制的区块号,0x14a2b3c = 21_636_412
注意返回值几乎都是十六进制字符串——包括余额、区块号、gas。这是新手第一个坑:"0x..." 要自己转十进制,而且金额单位是 wei(1 ETH = 10¹⁸ wei),别把 wei 当成 ETH 直接显示。
curl -s $RPC -H "Content-Type: application/json" -d '{ "jsonrpc":"2.0", "method":"eth_getBalance", "params":["0xYourAddress", "latest"], "id":1 }' # result: "0x38d7ea4c68000" = 1000000000000000 wei = 0.001 ETH
最常用的一小把方法
| 方法 | 作用 | 类比 |
|---|---|---|
eth_blockNumber | 最新区块高度 | 健康检查 / ping |
eth_getBalance | 查地址 ETH 余额(wei) | GET /balance |
eth_getTransactionByHash | 按哈希查一笔交易 | GET /tx/{hash} |
eth_getTransactionReceipt | 查交易回执(是否成功、日志、gas used) | GET /tx/{hash}/receipt |
eth_call | 只读调用合约(不上链、不花钱) | 只读查询 |
eth_estimateGas | 预估一笔交易要花多少 gas | 预检 |
eth_sendRawTransaction | 广播一笔已签名交易(唯一"写"操作) | POST /tx |
eth_getLogs | 按过滤条件查历史事件日志 | 查询数据流 |
读与写的分水岭
这是理解链上交互的关键切分,请刻进脑子:
- 读(read / call):
eth_call、eth_getBalance等,只是查询节点本地状态,不上链、不花 gas、不需要私钥、即时返回。 - 写(send):任何改变状态的操作,必须是一笔已签名交易,通过
eth_sendRawTransaction广播,花 gas、要等确认。
注意 eth_call 能"调用"合约函数却不花钱——因为它只是在节点本地模拟执行、拿返回值,不广播、不改状态。你查一个代币余额走的就是它。而真要转账,则必须发交易。SDK 会自动帮你分流:标了 view/pure 的合约函数走 call,会改状态的走 send。
async function rpc(method, params) { const res = await fetch(RPC_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', method, params, id: 1 }) }); const { result, error } = await res.json(); if (error) throw new Error(error.message); return result; } const hex = await rpc('eth_blockNumber', []); console.log(parseInt(hex, 16)); // 转成十进制区块号
公共 RPC 会看到你查询的所有内容(你关心哪些地址、什么时候在线)。对隐私敏感或生产环境,用自己的付费端点或自建节点。另外别把带 API key 的 RPC URL 硬编码进前端打包文件——它会被扒出来盗用你的额度。前端该走后端代理或用专门的前端安全 key。
整个以太坊对开发者的接口,归根结底就是这一个 JSON-RPC 端点。读操作用 call 类方法,即时免费;写操作把已签名交易通过 eth_sendRawTransaction 广播,花 gas 等确认。所有 SDK 都是这层之上的糖。下一章我们不再只读,而是自己写一个合约、部署上去,然后调用它——calldata 这个第 6 章 Etherscan 里见过的 Input Data 字段,终于要装东西了。
本章小结
- 节点对外是一个 JSON-RPC over HTTP 端点;method 字段区分动作,和单端点 REST 后端同构。
- 返回值多为十六进制,金额单位是 wei(1 ETH = 10¹⁸ wei),需自行转换。
- 读(call,免费即时)与写(send 已签名交易,花 gas 等确认)是根本分水岭。
- 所有 SDK 都是 JSON-RPC 之上的封装;懂了裸接口,SDK 与调试都不再是黑盒。