上链ON-CHAIN BLOCK 18 / 26
卷四 · dApp 前端BLOCK 18

viem / ethers.js 读写链


卷二我们手写过 JSON-RPC,理解了底层。日常开发当然不会那么原始——用 SDK。这一章把两个最主流的库(viem 与 ethers.js)拆成两个核心概念:Provider(只读连接)Signer(能签名的账户)。抓住这两个,读链写链的所有代码都顺理成章。

两个核心角色

回顾第 7 章的读写分水岭,SDK 把它具象成两个对象:

Provider(公共客户端)Signer(钱包客户端)
能干什么只读:查余额、查状态、call、订阅事件读 + 写:签名并发送交易
需要私钥吗不需要需要(由钱包或私钥提供)
花钱吗不花写操作花 gas
来自哪一个 RPC 端点 URLMetaMask 注入 / 本地私钥
Android 类比

Provider 像一个只读的 Repository:你能查询,但改不了后端数据。Signer 像带了认证 token、有写权限的那个客户端——它能提交会改变服务器状态的请求。在 dApp 里,读用户资产、渲染界面走 Provider;用户点"确认交易"时才用 Signer 去签名发送。

viem:现代、类型安全的首选

TypeScript · viem 读链
import { createPublicClient, http, formatEther } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({
  chain: mainnet,
  transport: http('https://your-rpc-url'),
});

// 查余额(只读,免费)—— 底层就是 eth_getBalance
const wei = await client.getBalance({ address: '0xAlice…' });
console.log(formatEther(wei), 'ETH');   // formatEther 帮你除以 1e18
TypeScript · viem 调合约
const balance = await client.readContract({
  address: TOKEN_ADDRESS,
  abi: erc20Abi,               // 第 8 章的 ABI,SDK 靠它编解码
  functionName: 'balanceOf',
  args: ['0xAlice…'],
});

写操作:需要 Signer

TypeScript · viem 写链(浏览器钱包)
import { createWalletClient, custom } from 'viem';

const wallet = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),   // MetaMask 注入的对象(第 19 章)
});
const [account] = await wallet.requestAddresses();

// 发交易:调 transfer(会弹钱包让用户确认、花 gas)
const hash = await wallet.writeContract({
  account,
  address: TOKEN_ADDRESS,
  abi: erc20Abi,
  functionName: 'transfer',
  args: ['0xBob…', 1000000n],
});
// hash 是交易哈希;再用 client.waitForTransactionReceipt 等确认

ethers.js:老牌,同一套心智

ethers 概念完全对应,只是命名不同(Provider / Signer / Contract):

JavaScript · ethers v6
import { BrowserProvider, Contract, formatEther } from 'ethers';

const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

// 只读用 provider,写用 signer
const token = new Contract(TOKEN_ADDRESS, erc20Abi, signer);
const bal = await token.balanceOf(addr);        // 读
const tx  = await token.transfer(to, 1000000n);   // 写,返回交易对象
await tx.wait();                                // 等确认
概念viemethers
只读连接PublicClientProvider
能签名的账户WalletClientSigner
读合约readContractcontract.fn()
写合约writeContractcontract.fn() (带 signer)
风格函数式、TS 优先、体积小面向对象、生态老、文档多

新项目一般推荐 viem(类型安全、tree-shaking 友好、常配 wagmi 做 React hooks),但 ethers 依然到处都是。两者你都该能读。

风险记账

永远记得处理"用户拒绝签名"和"交易失败"。用户在钱包弹窗点"取消"会抛错,别让 UI 卡死。交易发出后 wait() 前有段"pending"时间,要给明确反馈(第 4 章:可能几秒也可能很久)。还有单位:链上是 bigint(wei),显示要 format、输入要 parse,别用普通 number 处理金额,会精度丢失。

要点入账

无论 viem 还是 ethers,心智模型只有两个:Provider/PublicClient 负责只读(不花钱、不用私钥),Signer/WalletClient 负责签名写入(花 gas、需钱包)。合约实例 = 地址 + ABI。读渲染走前者,用户确认的写操作走后者。SDK 只是把第 7 章的 JSON-RPC 包成类型安全的调用而已。下一章我们补上最关键的一环:怎么让用户的钱包连上你的 dApp,以及签名的几种姿势。

本章小结

◆ 返回目录