viem

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Viem Skill

Viem 技能指南

Version: Viem 2.x | Official Docs
Viem is the modern TypeScript interface for Ethereum. This skill ensures correct patterns for contract interactions, client setup, and type safety.
版本: Viem 2.x | 官方文档
Viem 是面向Ethereum的现代化TypeScript接口。本技能指南确保你掌握合约交互、客户端配置和类型安全的正确实践。

Quick Reference

快速参考

typescript
import { createPublicClient, createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
typescript
import { createPublicClient, createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

Critical Patterns

核心实践模式

1. Client Setup

1. 客户端配置

Public Client (read-only operations):
typescript
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})
Wallet Client (write operations):
typescript
const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})
公共客户端(只读操作):
typescript
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})
钱包客户端(写入操作):
typescript
const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})

2. ABI Type Safety (CRITICAL)

2. ABI 类型安全(至关重要)

Always use
as const
for ABIs to get full type inference:
typescript
// ✅ 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`
始终为ABI添加
as const
以获得完整的类型推断:
typescript
// ✅ 正确做法 - 完整类型安全
const abi = [
  {
    name: 'balanceOf',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'owner', type: 'address' }],
    outputs: [{ name: '', type: 'uint256' }],
  },
] as const

// ❌ 错误做法 - 无类型推断
const abi = [{ name: 'balanceOf', ... }] // 缺少`as const`

3. Contract Read Pattern

3. 合约读取模式

typescript
const balance = await publicClient.readContract({
  address: '0x...', // Contract address
  abi,
  functionName: 'balanceOf',
  args: ['0x...'], // Args are fully typed when using `as const`
})
typescript
const balance = await publicClient.readContract({
  address: '0x...', // 合约地址
  abi,
  functionName: 'balanceOf',
  args: ['0x...'], // 当使用`as const`时,参数会被完全类型化
})

4. Contract Write Pattern (Simulate First!)

4. 合约写入模式(先模拟!)

Always simulate before writing to catch errors early:
typescript
// 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 })
写入前务必先模拟,以便提前捕获错误:
typescript
// 步骤1:模拟
const { request } = await publicClient.simulateContract({
  account,
  address: '0x...',
  abi,
  functionName: 'transfer',
  args: ['0x...', 1000000n], // 对uint256类型使用BigInt
})

// 步骤2:执行
const hash = await walletClient.writeContract(request)

// 步骤3:等待交易回执
const receipt = await publicClient.waitForTransactionReceipt({ hash })

5. Event Watching

5. 事件监听

typescript
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()
typescript
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)
    }
  },
})

// 清理资源
unwatch()

6. Multicall for Batch Reads

6. 批量读取的Multicall用法

typescript
const results = await publicClient.multicall({
  contracts: [
    { address: '0x...', abi, functionName: 'balanceOf', args: ['0x...'] },
    { address: '0x...', abi, functionName: 'totalSupply' },
  ],
})
// results[0].result, results[1].result
typescript
const results = await publicClient.multicall({
  contracts: [
    { address: '0x...', abi, functionName: 'balanceOf', args: ['0x...'] },
    { address: '0x...', abi, functionName: 'totalSupply' },
  ],
})
// results[0].result, results[1].result

Common Mistakes

常见错误

MistakeFix
Missing
as const
on ABI
Add
as const
for type inference
Using
Number
for amounts
Use
BigInt
literals:
1000000n
Writing without simulateAlways
simulateContract
first
Hardcoding gasLet viem estimate, or use
gas: await publicClient.estimateGas(...)
Not awaiting receiptsUse
waitForTransactionReceipt
for confirmation
错误修复方案
ABI未添加
as const
添加
as const
以实现类型推断
使用
Number
处理金额
使用
BigInt
字面量:
1000000n
未模拟直接写入始终先执行
simulateContract
硬编码gas值让viem自动估算,或使用
gas: await publicClient.estimateGas(...)
未等待交易回执使用
waitForTransactionReceipt
确认交易

Chain Configuration

链配置

typescript
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'] },
  },
}
typescript
import { mainnet, polygon, arbitrum, optimism, base } from 'viem/chains'

// 自定义链
const customChain = {
  id: 123,
  name: 'My Chain',
  nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
  rpcUrls: {
    default: { http: ['https://rpc.mychain.com'] },
  },
}

Error Handling

错误处理

typescript
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
    }
  }
}
typescript
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
      // 处理具体的回滚原因
    }
  }
}

References

参考资料

For detailed patterns, see:
  • references/contract-patterns.md
    - Advanced contract interaction patterns
  • references/common-errors.md
    - Error handling and debugging guide
  • Viem Documentation - Official docs
  • Viem GitHub - Source and releases
如需了解详细模式,请查阅:
  • references/contract-patterns.md
    - 高级合约交互模式
  • references/common-errors.md
    - 错误处理与调试指南
  • Viem官方文档
  • Viem GitHub仓库 - 源码与版本发布信息