第3节:读合约
小白入门:https://github.com/dukedaily/solidity-expert ,欢迎star转发,文末加V入群。
职场进阶: https://dukeweb3.com
ethers 的 Contract 对象把 ABI + 地址 + Provider 绑定起来,直接像调用对象方法一样读链上数据。
最小示例
读取 USDC 的总供应量与 vitalik.eth 的余额:
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://eth.llamarpc.com");
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const abi = [
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint256)",
"function decimals() view returns (uint8)",
];
const usdc = new ethers.Contract(USDC, abi, provider);
const total = await usdc.totalSupply();
const decimals = await usdc.decimals();
const vbal = await usdc.balanceOf(await provider.resolveName("vitalik.eth"));
console.log("USDC 总供应:", ethers.formatUnits(total, decimals));
console.log("Vitalik 余额:", ethers.formatUnits(vbal, decimals));
ABI 的三种写法
// 1) Human-readable ABI(推荐,简洁)
const abi = [
"function balanceOf(address owner) view returns (uint256)",
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
// 2) JSON ABI(编译 artifact 输出的格式)
import abiJson from "./Usdc.json";
const contract = new ethers.Contract(addr, abiJson.abi, provider);
// 3) 通过 Interface 构造(便于 selector / 编码工作)
const iface = new ethers.Interface(abi);
staticCall:不发交易的预执行
要预执行一个会改状态的方法但不真的上链,使用 staticCall:
// 试调 transfer,不发交易,但会检查 revert
try {
const ok = await usdc.transfer.staticCall(
"0xSomeRecipient",
ethers.parseUnits("100", 6),
);
console.log("预检通过:", ok);
} catch (err) {
console.log("预检失败:", err.reason ?? err.message);
}
调用失败的错误结构
v6 的 CALL_EXCEPTION 错误对象携带结构化信息:
try {
await usdc.transfer.staticCall("0xSomeRecipient", 10n ** 30n);
} catch (err) {
// err.code === "CALL_EXCEPTION"
console.log("reason:", err.reason); // 字符串 message
console.log("revert:", err.revert); // custom error 数据
}
小结
Contract + Provider 就是只读的"合约查询客户端"。下一节把 Signer 接入,开始写链。