Viem Skill
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.
Quick Reference
typescript1import { createPublicClient, createWalletClient, http } from 'viem' 2import { mainnet } from 'viem/chains' 3import { privateKeyToAccount } from 'viem/accounts'
Critical Patterns
1. Client Setup
Public Client (read-only operations):
typescript1const publicClient = createPublicClient({ 2 chain: mainnet, 3 transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'), 4})
Wallet Client (write operations):
typescript1const account = privateKeyToAccount('0x...') 2const walletClient = createWalletClient({ 3 account, 4 chain: mainnet, 5 transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'), 6})
2. ABI Type Safety (CRITICAL)
Always use as const for ABIs to get full type inference:
typescript1// ✅ CORRECT - Full type safety 2const abi = [ 3 { 4 name: 'balanceOf', 5 type: 'function', 6 stateMutability: 'view', 7 inputs: [{ name: 'owner', type: 'address' }], 8 outputs: [{ name: '', type: 'uint256' }], 9 }, 10] as const 11 12// ❌ WRONG - No type inference 13const abi = [{ name: 'balanceOf', ... }] // Missing `as const`
3. Contract Read Pattern
typescript1const balance = await publicClient.readContract({ 2 address: '0x...', // Contract address 3 abi, 4 functionName: 'balanceOf', 5 args: ['0x...'], // Args are fully typed when using `as const` 6})
4. Contract Write Pattern (Simulate First!)
Always simulate before writing to catch errors early:
typescript1// Step 1: Simulate 2const { request } = await publicClient.simulateContract({ 3 account, 4 address: '0x...', 5 abi, 6 functionName: 'transfer', 7 args: ['0x...', 1000000n], // Use BigInt for uint256 8}) 9 10// Step 2: Execute 11const hash = await walletClient.writeContract(request) 12 13// Step 3: Wait for receipt 14const receipt = await publicClient.waitForTransactionReceipt({ hash })
5. Event Watching
typescript1const unwatch = publicClient.watchContractEvent({ 2 address: '0x...', 3 abi, 4 eventName: 'Transfer', 5 onLogs: (logs) => { 6 for (const log of logs) { 7 console.log(log.args.from, log.args.to, log.args.value) 8 } 9 }, 10}) 11 12// Clean up 13unwatch()
6. Multicall for Batch Reads
typescript1const results = await publicClient.multicall({ 2 contracts: [ 3 { address: '0x...', abi, functionName: 'balanceOf', args: ['0x...'] }, 4 { address: '0x...', abi, functionName: 'totalSupply' }, 5 ], 6}) 7// results[0].result, results[1].result
Common Mistakes
| Mistake | Fix |
|---|---|
Missing as const on ABI | Add as const for type inference |
Using Number for amounts | Use BigInt literals: 1000000n |
| Writing without simulate | Always simulateContract first |
| Hardcoding gas | Let viem estimate, or use gas: await publicClient.estimateGas(...) |
| Not awaiting receipts | Use waitForTransactionReceipt for confirmation |
Chain Configuration
typescript1import { mainnet, polygon, arbitrum, optimism, base } from 'viem/chains' 2 3// Custom chain 4const customChain = { 5 id: 123, 6 name: 'My Chain', 7 nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 }, 8 rpcUrls: { 9 default: { http: ['https://rpc.mychain.com'] }, 10 }, 11}
Error Handling
typescript1import { BaseError, ContractFunctionRevertedError } from 'viem' 2 3try { 4 await publicClient.simulateContract({ ... }) 5} catch (err) { 6 if (err instanceof BaseError) { 7 const revertError = err.walk(e => e instanceof ContractFunctionRevertedError) 8 if (revertError instanceof ContractFunctionRevertedError) { 9 const errorName = revertError.data?.errorName 10 // Handle specific revert reason 11 } 12 } 13}
References
For detailed patterns, see:
references/contract-patterns.md- Advanced contract interaction patternsreferences/common-errors.md- Error handling and debugging guide- Viem Documentation - Official docs
- Viem GitHub - Source and releases