Viem 与 Ethers.js 对比

Viem 与 Ethers.js 都能完成以太坊 RPC 调用、合约交互、日志解析和交易签名。选择时应关注项目现有技术栈、类型安全要求、插件兼容性与团队经验,而不是简单判断某个库更先进。

设计差异

维度 Viem Ethers.js
API 风格 函数式客户端与显式账户 面向对象的 Provider、Signer 与 Contract
类型系统 基于 ABI 推导参数和返回类型 类型支持完善,可配合 TypeChain
模块组织 公共客户端与钱包客户端分离 读取与签名能力通过对象组合
包体与导入 支持细粒度导入 核心入口统一,使用直观
生态 常与 Wagmi 等现代前端工具配合 历史项目、脚本和教程覆盖广泛
迁移成本 API 差异较大,需要重写调用层 版本升级也需关注破坏性变化
1
2
3
4
5
6
7
8
9
10
flowchart LR
A[应用业务层] --> B{客户端库}
B --> C[Viem publicClient]
B --> D[Ethers Provider]
C --> E[JSON-RPC 节点]
D --> E
A --> F[Viem walletClient]
A --> G[Ethers Signer]
F --> H[钱包或本地账户]
G --> H

Viem 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createPublicClient, createWalletClient, http, parseEther } from "viem";
import { mainnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
});

const balance = await publicClient.getBalance({ address: account.address });
const hash = await walletClient.sendTransaction({
to: "0x000000000000000000000000000000000000dEaD",
value: parseEther("0.001"),
});
await publicClient.waitForTransactionReceipt({ hash });

Ethers.js 示例

1
2
3
4
5
6
7
8
9
10
11
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY as string, provider);

const balance = await provider.getBalance(signer.address);
const transaction = await signer.sendTransaction({
to: "0x000000000000000000000000000000000000dEaD",
value: ethers.parseEther("0.001"),
});
await transaction.wait();

两个示例的业务含义相同。差异主要体现在对象模型、类型推导和读取与签名职责的组织方式。

Hardhat 项目选择

初始化模板提供 Viem 或 Ethers.js 选项时,应优先选择与项目插件和已有测试一致的组合。已有 Ethers.js 代码库无需仅为追求新工具而迁移。新建 TypeScript 项目如果重视 ABI 类型推导,并计划使用 Wagmi,可以优先评估 Viem。若团队依赖大量 Ethers.js 示例、TypeChain 或既有部署脚本,选择 Ethers.js 更稳妥。

测试示例

1
2
3
4
5
6
7
8
9
10
import assert from "node:assert/strict";

it("returns the stored value", async () => {
const value = await publicClient.readContract({
address: contractAddress,
abi,
functionName: "value",
});
assert.equal(value, 42n);
});

无论选择哪个库,都应固定主版本,集中封装 RPC 客户端,显式处理链 ID、确认数、交易替换和重试,并避免在前端代码中保存私钥。

结论

Viem 更强调函数式组合、客户端职责分离和 ABI 驱动的类型推导。Ethers.js 提供成熟直观的对象模型,并在历史项目中具有较广覆盖。面试或技术评审中,应结合实际约束说明选择依据,不应把生态规模或行业采用情况表述为永久不变的结论。