Get the FREE Ultimate OpenClaw Setup Guide →

viem

Scanned
npx machina-cli add skill 0xSardius/onchain-typescript-skills/viem --openclaw
Files (1)
SKILL.md
4.6 KB

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

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

Critical Patterns

1. Client Setup

Public Client (read-only operations):

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})

Wallet Client (write operations):

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)

Always use as const for ABIs to get full type inference:

// ✅ 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. Contract Read Pattern

const balance = await publicClient.readContract({
  address: '0x...', // Contract address
  abi,
  functionName: 'balanceOf',
  args: ['0x...'], // Args are fully typed when using `as const`
})

4. Contract Write Pattern (Simulate First!)

Always simulate before writing to catch errors early:

// 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. Event Watching

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 for Batch Reads

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 ABIAdd as const for type inference
Using Number for amountsUse BigInt literals: 1000000n
Writing without simulateAlways simulateContract first
Hardcoding gasLet viem estimate, or use gas: await publicClient.estimateGas(...)
Not awaiting receiptsUse waitForTransactionReceipt for confirmation

Chain Configuration

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'] },
  },
}

Error Handling

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

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

Source

git clone https://github.com/0xSardius/onchain-typescript-skills/blob/main/skills/viem/SKILL.mdView on GitHub

Overview

Viem provides a modern TypeScript interface for Ethereum. This skill covers creating a read-only publicClient and a write-focused walletClient, using type-safe ABIs, safe contract reads/writes (with simulate-first), event watching, multicall, and ABI encoding for non-React code.

How This Skill Works

Install Viem and create clients: a publicClient for reads and a walletClient for writes, using http transport to an Ethereum node. Always declare ABIs as const to enable full type inference; use readContract for reads, simulateContract before writes, then writeContract and waitForTransactionReceipt. Use watchContractEvent for on-chain events and multicall for batch reads.

When to Use It

  • Building Node scripts, CLI tools, or backend services that read/write to Ethereum or EVM chains.
  • Triggering on contract interactions, wallet operations, or transaction signing in non-React TypeScript code.
  • Event-driven tooling that watches and processes contract events with onLogs and a callback.
  • Fetching multiple on-chain values efficiently with multicall to minimize RPC requests.
  • Encoding ABI calls for offline signing or constructing transactions in non-React apps.

Quick Start

  1. Step 1: Install Viem: npm i viem
  2. Step 2: Create clients: const publicClient = createPublicClient({ chain: mainnet, transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY') }); const walletClient = createWalletClient({ account, chain: mainnet, transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY') });
  3. Step 3: Try a minimal read: const balance = await publicClient.readContract({ address: '0x...', abi, functionName: 'balanceOf', args: ['0x...'] })

Best Practices

  • Always use ABI as const to enable full type inference and safer code.
  • Always simulate before writing: use simulateContract to catch errors early.
  • Keep reads on a publicClient and writes on a walletClient; avoid mixing for clarity.
  • Wait for and validate transaction receipts with waitForTransactionReceipt; handle retries and timeouts.
  • Use multicall for batch reads and rely on Viem to estimate gas rather than hardcoding values.

Example Use Cases

  • Read a token balance with publicClient.readContract using an as-const ABI.
  • Simulate a transfer first, then execute writeContract on walletClient and await the receipt.
  • Watch for Transfer events using publicClient.watchContractEvent and process incoming logs.
  • Fetch balances for multiple addresses in a single request with publicClient.multicall.
  • Prepare an ABI-encoded function call for offline signing or batch encoding before sending.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers