Skip to content

Using with Viem

This guide demonstrates how to use Tevm with viem, enabling you to leverage viem's type-safe, modular Ethereum development tools alongside Tevm's in-memory EVM capabilities.

Integration Options

Tevm offers two integration approaches with viem to suit different development needs:

The recommended approach for production applications that need to minimize bundle size:

import { createTevmNode } from 'tevm/node'
import { requestEip1193 } from 'tevm/decorators'
import { createClient, custom } from 'viem'
 
// Create Tevm Node with EIP-1193 support
const node = createTevmNode().extend(requestEip1193())
 
// Create Viem client
const client = createClient({
  // Use Tevm node as the viem transport
  transport: custom(node),
})
 
// Import and use viem actions individually
import { getBlockNumber } from 'viem/actions'
await getBlockNumber(client)
 
// Import and use tevm actions
import { tevmDumpState } from 'tevm'
await tevmDumpState(client)

A more convenient approach when bundle size isn't a primary concern:

import { createMemoryClient } from 'tevm'
 
// Create a fully-loaded client with all actions attached
const client = createMemoryClient()
 
// Use viem actions directly from the client
await client.getBlockNumber()
 
// Use tevm-specific actions
await client.tevmDumpState()

Core Functionality

Public Actions

Use viem's public actions to query your local Tevm environment:

// Get the latest block
const block = await client.getBlock()
console.log(`Block number: ${block.number}`)
 
// Get an account's balance
const balance = await client.getBalance({
  address: '0x1234567890123456789012345678901234567890',
})
console.log(`Balance: ${balance} wei`)
 
// Get transaction count (nonce)
const nonce = await client.getTransactionCount({
  address: '0x1234567890123456789012345678901234567890',
})
console.log(`Transaction count: ${nonce}`)
 
// Read from a contract
const result = await client.readContract({
  address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC on mainnet
  abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
  functionName: 'balanceOf',
  args: ['0x1234567890123456789012345678901234567890'],
})
console.log(`Contract result: ${result}`)

Wallet Actions

Tevm supports all of viem's wallet actions with built-in prefunded accounts:

import { createMemoryClient, PREFUNDED_ACCOUNTS } from 'tevm'
import { parseEther } from 'viem'
 
// Create a client with one of Tevm's prefunded accounts
const client = createMemoryClient({
  account: PREFUNDED_ACCOUNTS[0], // First prefunded account with 10000 ETH
})
 
// Send ETH to another address
const hash = await client.sendTransaction({
  to: '0x1234567890123456789012345678901234567890',
  value: parseEther('1'), // Send 1 ETH
})
console.log(`Transaction sent: ${hash}`)
 
// Wait for the transaction to be mined
const receipt = await client.waitForTransactionReceipt({ hash })
console.log(`Transaction mined in block: ${receipt.blockNumber}`)
 
// Deploy a contract
const { contractAddress } = await client.deployContract({
  abi: parseAbi([
    'function greet() view returns (string)',
    'function setGreeting(string) returns ()'
  ]),
  bytecode: '0x608060405234801561...',  // Contract bytecode
})
console.log(`Contract deployed at: ${contractAddress}`)
Working with Custom Accounts
import { createMemoryClient } from 'tevm'
import { privateKeyToAccount } from 'viem/accounts'
 
// Create an account from a private key
const account = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')
 
// Use that account with the client
const client = createMemoryClient({ account })
 
// First set some balance for the account
await client.setBalance({
  address: account.address,
  value: parseEther('10')
})
 
// Now use the account to send transactions
const hash = await client.sendTransaction({
  to: '0x1234567890123456789012345678901234567890',
  value: parseEther('1')
})

Test Actions

All of viem's test actions are supported for comprehensive testing capabilities:

import { createMemoryClient } from 'tevm'
import { parseEther } from 'viem'
 
const client = createMemoryClient()
 
// Mine additional blocks
await client.mine({ blocks: 5 })
console.log(`New block number: ${await client.getBlockNumber()}`)
 
// Set an account's balance
await client.setBalance({
  address: '0x1234567890123456789012345678901234567890',
  value: parseEther('100')
})
 
// Set block timestamp for time-dependent tests
await client.setNextBlockTimestamp(1695311333n) // Set timestamp for next block
await client.mine({ blocks: 1 }) // Mine the block with that timestamp
 
// Snapshot and revert state
const snapshotId = await client.snapshot()
console.log(`Created snapshot: ${snapshotId}`)
 
// Make some changes...
await client.setBalance({
  address: '0x1234567890123456789012345678901234567890',
  value: parseEther('999')
})
 
// Revert to the snapshot
await client.revert({ id: snapshotId })
console.log('Reverted to previous state')
 
// Check balance is back to previous value
const balance = await client.getBalance({
  address: '0x1234567890123456789012345678901234567890'
})
console.log(`Balance after revert: ${balance}`)

Tevm Actions

Contract Interactions

import { createMemoryClient } from 'tevm'
import { parseAbi } from 'viem'
 
const client = createMemoryClient()
 
// Using the tevmContract action for contract interaction
const result = await client.tevmContract({
  abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
  address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC on mainnet
  functionName: 'balanceOf',
  args: ['0x1234567890123456789012345678901234567890'],
})
console.log(`Contract result: ${result}`)
 
// Low-level EVM call with tevmCall
const callResult = await client.tevmCall({
  to: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
  data: '0x70a08231000000000000000000000000' + '1234567890123456789012345678901234567890'.slice(2),
})
console.log(`Raw call result: ${callResult.data}`)

Account Management

import { createMemoryClient } from 'tevm'
import { parseEther } from 'viem'
 
const client = createMemoryClient()
 
// Get account state with all details
const account = await client.tevmGetAccount({
  address: '0x1234567890123456789012345678901234567890',
})
console.log('Account state:', account)
 
// Set up a complex account state (EOA or contract)
await client.tevmSetAccount({
  address: '0xabcdef1234567890abcdef1234567890abcdef12',
  balance: parseEther('100'),
  nonce: 5n,
  // For contracts:
  code: '0x608060405234801...',  // Contract bytecode
  storage: {           // Storage slots
    '0x0': '0x1',      // slot 0 -> value 1
    '0x1': '0x2'       // slot 1 -> value 2
  }
})

State Management

import { createMemoryClient } from 'tevm'
 
const client = createMemoryClient()
 
// Dump the entire EVM state
const state = await client.tevmDumpState()
console.log('Current state:', state)
 
// Save state to local variable
const savedState = await client.tevmDumpState()
 
// Make changes
await client.setBalance({
  address: '0x1234567890123456789012345678901234567890',
  value: 123456789n
})
 
// Restore previous state
await client.tevmLoadState({
  state: savedState
})
 
// Mine blocks with Tevm action
await client.tevmMine({
  blocks: 5
})

Inside the Memory Client

A MemoryClient is essentially a viem client with Tevm's functionality added. Here's how you could build one from scratch:

// Step 1: Create a fork transport (for connecting to an existing network)
import { http } from 'viem'
const forkTransport = http('https://mainnet.optimism.io')
 
// Step 2: Create a Tevm Node and make it EIP-1193 compatible
import { createTevmNode } from 'tevm'
import { requestEip1193 } from 'tevm/decorators'
 
const node = createTevmNode({
  fork: {
    transport: forkTransport
  }
}).extend(requestEip1193())
 
// Step 3: Create a viem client with Tevm extensions
import { custom, createClient, publicActions, testActions, walletActions } from 'viem'
import { tevmViemActions } from 'tevm/memory-client'
 
const memoryClient = createClient({
  transport: custom(node),
})
  // Add Tevm-specific actions
  .extend(tevmViemActions())
  // Add viem standard actions
  .extend(publicActions)
  .extend(walletActions)
  .extend(testActions({ mode: 'anvil' }))
 
// Now you have a fully functional memoryClient

This breakdown illustrates Tevm's key architectural components:

  1. EIP-1193 Compatibility Layer: Tevm implements the standard Ethereum provider interface
  2. In-Memory EVM: Tevm runs a complete Ethereum Virtual Machine locally
  3. Viem Integration: Tevm extends viem's functionality with EVM-specific capabilities

Complete Action Reference

Public Actions - Read blockchain state

Contract Interactions

Block & Transaction

Account & Chain

Test Actions - Manipulate blockchain state

Block & Mining

Account & State

State Management

  • snapshot - Create a snapshot of the current state
  • revert - Revert to a previous snapshot
  • reset - Reset the fork to a fresh state
  • dumpState - Export the current state
  • loadState - Import a previously exported state
Wallet Actions - Send transactions and interact with accounts

Account Management

Transaction Operations

Signing Operations

Chain Management

Permissions & Assets

Tevm Actions - Enhanced EVM capabilities
  • tevmCall - Low-level EVM call
  • tevmContract - Call a contract method with detailed EVM info
  • tevmDeploy - Deploy a contract with detailed results
  • tevmGetAccount - Get detailed account information
  • tevmSetAccount - Set up a complex account state
  • tevmDumpState - Export complete EVM state
  • tevmLoadState - Import complete EVM state
  • tevmMine - Mine blocks with additional options

Next Steps

Using with Ethers.js

Learn how to integrate Tevm with ethers.js

Forking Mainnet

Create a local fork of mainnet for testing

Local Testing

Set up a comprehensive local testing environment

TevmNode Interface

Explore the low-level node interface