关于 viem
面向使用 Viem 2.x 构建 Ethereum 交互型 TypeScript 应用的开发者技能,涵盖类型安全的客户端设置、合约读写、事件监听及常见错误预防方法。
主要功能
- 01公共与钱包客户端设置
- 02类型安全的 ABI 写法
- 03合约读取与模拟写入
- 04合约事件监听
- 05Multicall 批量读取
- 06错误处理与链配置
适用场景
- 01设置只读或钱包客户端
- 02读取和写入智能合约
- 03监听合约事件
- 04批量执行合约读取
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ 0xsardius/ onchain-typescript-skills --skill viem安装后在 Claude Code 中输入/viem确认已加载。
附带文件references/common-errors.mdreferences/contract-patterns.md
详细介绍
Viem 技能
版本: Viem 2.x | 官方文档
Viem 是面向 Ethereum 的现代 TypeScript 接口。此技能确保合约交互、客户端设置和类型安全使用正确模式。
快速参考
import { createPublicClient, createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
关键模式
1. 客户端设置
公共客户端(只读操作):
const publicClient = createPublicClient({
chain: mainnet,
transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})
钱包客户端(写入操作):
const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})
2. ABI 类型安全(重要)
始终为 ABI 使用 as const,以获得完整的类型推断:
// ✅ CORRECT - Full type safety
const abi = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'owner', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
},
] as const
// ❌ WRONG - No type inference
const abi = [{ name: 'balanceOf', ... }] // Missing `as const`
3. 合约读取模式
const balance = await publicClient.readContract({
address: '0x...', // Contract address
abi,
functionName: 'balanceOf',
args: ['0x...'], // Args are fully typed when using `as const`
})
4. 合约写入模式(先模拟!)
写入前始终先进行模拟,以便尽早发现错误:
// Step 1: Simulate
const { request } = await publicClient.simulateContract({
account,
address: '0x...',
abi,
functionName: 'transfer',
args: ['0x...', 1000000n], // Use BigInt for uint256
})
// Step 2: Execute
const hash = await walletClient.writeContract(request)
// Step 3: Wait for receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash })
5. 事件监听
const unwatch = publicClient.watchContractEvent({
address: '0x...',
abi,
eventName: 'Transfer',
onLogs: (logs) => {
for (const log of logs) {
console.log(log.args.from, log.args.to, log.args.value)
}
},
})
// Clean up
unwatch()
6. 使用 Multicall 批量读取
const results = await publicClient.multicall({
contracts: [
{ address: '0x...', abi, functionName: 'balanceOf', args: ['0x...'] },
{ address: '0x...', abi, functionName: 'totalSupply' },
],
})
// results[0].result, results[1].result
常见错误
| 错误 | 修正方法 |
|---|---|
ABI 缺少 as const | 添加 as const 以进行类型推断 |
使用 Number 表示金额 | 使用 BigInt 字面量:1000000n |
| 未模拟就写入 | 始终先调用 simulateContract |
| 将 gas 写死 | 让 viem 估算,或使用 gas: await publicClient.estimateGas(...) |
| 未等待回执 | 使用 waitForTransactionReceipt 确认交易 |
链配置
import { mainnet, polygon, arbitrum, optimism, base } from 'viem/chains'
// Custom chain
const customChain = {
id: 123,
name: 'My Chain',
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
rpcUrls: {
default: { http: ['https://rpc.mychain.com'] },
},
}
错误处理
import { BaseError, ContractFunctionRevertedError } from 'viem'
try {
await publicClient.simulateContract({ ... })
} catch (err) {
if (err instanceof BaseError) {
const revertError = err.walk(e => e instanceof ContractFunctionRevertedError)
if (revertError instanceof ContractFunctionRevertedError) {
const errorName = revertError.data?.errorName
// Handle specific revert reason
}
}
}
参考资料
如需了解详细模式,请参阅:
references/contract-patterns.md- 高级合约交互模式references/common-errors.md- 错误处理与调试指南- Viem 文档 - 官方文档
- Viem GitHub - 源代码与版本发布
评论
支持 Markdown