Skip to content

Tevm Architecture Overview

Tevm's architecture is designed to be modular, extensible, and compatible with the broader JavaScript ecosystem. This guide explains the core components and how they work together to create a complete Ethereum execution environment.

Design Philosophy: Objects and Actions

At its core, Tevm follows a clear separation between Objects (stateful components) and Actions (pure functions that operate on those objects). This pattern, inspired by viem, enables tree-shaking and a composable API.

Objects

Stateful components that encapsulate and maintain data.

  • TevmNode - The core Node interface
  • Evm - The Ethereum Virtual Machine
  • StateManager - Manages blockchain state
  • Blockchain - Handles blocks and chain state

Actions

Pure functions that take an object as their first parameter and perform operations.

  • Tree-shakable for minimal bundle size
  • Single-purpose with clear input/output
  • Composable for complex operations
  • Can be imported individually

Example: Using an Action

Here's how to use a tree-shakable action with a Tevm Node:

import { createTevmNode } from 'tevm'
import { getAccountHandler } from 'tevm/actions'
 
// 1. Create the node object
const node = createTevmNode() 
 
// 2. Create a handler function by passing the node to the action
const getAccount = getAccountHandler(node) 
 
// 3. Use the handler function with specific parameters
const account = await getAccount({ 
  address: '0x1234567890123456789012345678901234567890'
})
 
console.log(account.balance) // Access account properties

This pattern allows you to:

  • Import only the actions you need
  • Create specialized handler functions for specific objects
  • Follow a consistent interface across the library

Client Options: Convenience vs. Tree Shaking

Tevm offers two main approaches for using its functionality:

import { createMemoryClient, http } from 'tevm'
 
// Create a client with all actions pre-attached
const client = createMemoryClient({
  fork: {
    transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY')
  }
})
 
// Use standard viem actions
const code = await client.getContractCode({ 
  address: '0x1234567890123456789012345678901234567890'
}) 
 
// Use Tevm-specific actions (prefixed with 'tevm')
const state = await client.tevmDumpState() 
const balance = await client.getBalance({  
  address: '0x1234567890123456789012345678901234567890'
}) 
import { createClient, http } from 'viem'
import { createTevmTransport } from 'tevm/transport'
import { getBlock, getBlockNumber } from 'viem/actions'
import { tevmSetAccount, tevmMine } from 'tevm/actions'
 
// Create a client with just the transport
const client = createClient({
  transport: createTevmTransport({
    fork: {
      transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY')
    }
  })
})
 
// Import and use only the specific actions you need
const blockNumber = await getBlockNumber(client) 
const block = await getBlock(client) 
 
// Tevm-specific actions
await tevmSetAccount(client, {  
  address: '0x1234...',  
  balance: 1000000000000000000n
}) 
await tevmMine(client, { blocks: 1 }) 

MemoryClient

  • ✅ Easy to get started
  • ✅ All methods available immediately
  • ✅ Less code to write
  • ❌ Larger bundle size

Tree-Shakable Actions

  • ✅ Smallest possible bundle size
  • ✅ Only include what you use
  • ✅ Works with code-splitting
  • ❌ More verbose imports

For more details on tree-shakable actions, see the viem documentation on tree-shaking.

Core Architecture Components

Tevm's modular architecture comprises several key components that work together to provide a complete Ethereum execution environment:

Virtual Machine (EVM)

The execution engine that runs EVM bytecode with full support for all opcodes and precompiles.

// Access via
const evm = (await node.getVm()).evm
 
// Features
evm.events.on('step', (data, next) => {
  // Monitor every EVM operation
  console.log(`Opcode: ${data.opcode.name}`)
  next()
})
 
// Control execution gas limits, precompiles, etc.

State Manager

Maintains account balances, contract code, and storage state with forking capability from live networks.

// Access via
const stateManager = (await node.getVm()).stateManager
 
// Features
await stateManager.putAccount(address, account)
await stateManager.getAccount(address)
await stateManager.getContractStorage(address, key)
await stateManager.checkpoint() // Create state snapshot
await stateManager.revert(checkpointId) // Revert to snapshot

Blockchain

Manages blocks, chain state, and handles block production with various mining strategies.

// Access via
const blockchain = (await node.getVm()).blockchain
 
// Features
await blockchain.getBlock(blockHash)
await blockchain.getBlockByNumber(blockNumber)
await blockchain.putBlock(block)
await blockchain.getLatestBlock()

Transaction Pool

Manages pending transactions, orders them by gas price, and handles transaction validation.

// Access via
const txPool = await node.getTxPool()
 
// Features
await txPool.add(transaction)
await txPool.getTransactions()
const pendingTxs = txPool.getPendingTransactions()
const pendingNonces = txPool.getPendingNonce(address)

Use Cases & Capabilities

Tevm enables several powerful use cases that were previously difficult or impossible with traditional Ethereum clients:

Advanced Debugging
import { createTevmNode } from 'tevm'
import { runCall } from 'tevm/vm'
 
const node = createTevmNode()
const vm = await node.getVm()
 
// Add EVM execution hooks
vm.evm.events.on('step', (data, next) => {
  console.log(`Executing ${data.opcode.name} at PC=${data.pc}`)
  console.log(`Stack: ${data.stack.join(', ')}`)
  next?.()
})
 
// Execute contract call
await runCall(vm)({
  to: '0x1234567890123456789012345678901234567890',
  data: '0xabcdef12' // Call data
})
Cross-Environment
// Works in Node.js
const nodejsClient = createMemoryClient()
 
// Works in browsers
const browserClient = createMemoryClient()
 
// Works in serverless functions
export default async function handler(req, res) {
  const client = createMemoryClient()
  const balance = await client.getBalance({ 
    address: req.body.address 
  })
  return res.json({ balance })
}
Custom Tools
import { createMemoryClient } from 'tevm'
 
export async function simulateTransaction(tx) {
  const client = createMemoryClient()
  
  // Take snapshot of current state
  const snapshot = await client.tevmSnapshot()
  
  try {
    // Execute transaction
    const receipt = await client.sendTransaction(tx)
    await client.mine()
    
    // Check transaction results
    const logs = receipt.logs
    const gasUsed = receipt.gasUsed
    const events = parseEvents(logs)
    
    return { success: true, gasUsed, events }
  } finally {
    // Always revert to original state
    await client.tevmRevert(snapshot)
  }
}

Custom Tool Opportunities

Transaction Simulators

Preview transaction outcomes before sending to mainnet

EVM Debuggers

Step through transactions with full state visibility

Local-first dApps

Build apps that work offline with optimistic updates

Educational Tools

Create interactive EVM learning experiences

CI/CD Integration

Test smart contracts in continuous integration pipelines

Gas Optimization

Analyze contract gas usage patterns with precision

Serverless Execution

Run Ethereum nodes in serverless or edge computing environments

State Snapshots

Create, save, and restore blockchain state at precise points

For detailed examples of these use cases, see the examples section.

API Interfaces

API LevelDescriptionBest For
Viem Client APIStandard viem actions plus Tevm-specific actionsMost application development
JSON-RPC APIStandard Ethereum RPC methods plus Anvil and Tevm-specific methodsDirect RPC integration, tooling
TevmNode APIDirect access to the node and its componentsAdvanced use cases, custom extensions
Low-Level ComponentsDirect access to EVM, StateManager, Blockchain, etc.Tool developers, deep customization
📁 API Layers
📁 Viem Client API (high-level)
📄 client.getBalance()
📄 client.tevmMine()
📄 client.sendTransaction()
📁 JSON-RPC API
📄 eth_getBalance
📄 anvil_mine
📄 tevm_dumpState
📁 TevmNode API
📄 node.getVm()
📄 node.getTxPool()
📄 node.extend()
📁 Low-Level Components
📄 vm.evm.runCall()
📄 stateManager.getAccount()
📄 blockchain.putBlock()
import { createMemoryClient } from 'tevm'
 
const client = createMemoryClient()
 
// Standard viem actions
const balance = await client.getBalance({ address: '0x123...' }) 
const blockNumber = await client.getBlockNumber() 
 
// Tevm-specific actions
await client.tevmSetAccount({  
  address: '0x123...',  
  balance: 1000000000000000000n
})  
await client.tevmMine()
import { createTevmNode } from 'tevm'
import { requestEip1193 } from 'tevm/decorators'
 
const node = createTevmNode().extend(requestEip1193())
 
// Standard Ethereum JSON-RPC methods
const balance = await node.request({ 
  method: 'eth_getBalance', 
  params: ['0x123...', 'latest'] 
}) 
 
// Anvil-compatible methods
await node.request({ 
  method: 'anvil_setBalance', 
  params: ['0x123...', '0x10000000000000000'] 
}) 
 
// Tevm-specific methods
const state = await node.request({ 
  method: 'tevm_dumpState', 
  params: [] 
}) 
import { createTevmNode } from 'tevm'
import { createAddress } from 'tevm/address'
 
const node = createTevmNode()
const vm = await node.getVm()
 
// Direct EVM access
vm.evm.events.on('step', (data, next) => { 
  // Inspect execution at each EVM step 
  console.log(data.opcode.name, data.stack) 
  next?.() 
}) 
 
// Direct state management
await vm.stateManager.putAccount( 
  createAddress('0x123...'), 
  { 
    nonce: 0n, 
    balance: 10000000000000000000n, 
    storageRoot: '0x...', 
    codeHash: '0x...'
  } 
) 
 
// Direct blockchain control
const block = await vm.blockchain.getBlockByNumber(1n)

For component API details, see:

Advanced Features

Tevm includes several powerful features that enable advanced use cases:

Custom Precompiles

Extend the EVM with JavaScript functions that can be called from smart contracts:

import { createTevmNode, definePrecompile } from 'tevm'
import { createContract } from 'tevm/contract'
import { parseAbi } from 'tevm'
 
// Define a precompile that provides an off-chain data oracle
const dataOraclePrecompile = definePrecompile({
  contract: createContract({
    abi: parseAbi(['function getCurrentPrice(string symbol) view returns (uint256)']),
    address: '0x0000000000000000000000000000000000000123'
  }),
  call: async ({ data }) => {
    // Parse input data and fetch external price data
    const symbol = parseInputData(data)
    const price = await fetchPriceData(symbol)
    
    return {
      returnValue: encodePrice(price),
      executionGasUsed: 3000n
    }
  }
})
 
// Create a node with the custom precompile
const node = createTevmNode({
  customPrecompiles: [dataOraclePrecompile.precompile()]
})

EVM Execution Hooks

Instrument the EVM to observe and measure execution:

import { createTevmNode } from 'tevm'
 
const node = createTevmNode()
const vm = await node.getVm()
 
// Add detailed execution hooks
vm.evm.events.on('step', (step, next) => {
  console.log(`
    Operation: ${step.opcode.name}
    PC: ${step.pc}
    Gas Used: ${step.gasLeft}
    Stack: ${step.stack.join(', ')}
    Memory: ${step.memory.slice(0, 64)}...
  `)
  next?.() // Continue execution
})

Forking Capabilities

Create local forks of any EVM chain with lazy-loading:

import { createTevmNode, http } from 'tevm'
import { mainnet } from 'tevm/common'
 
// Fork from Ethereum mainnet
const node = createTevmNode({
  fork: {
    transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
    common: mainnet,
    blockTag: 18000000n // Optional: specific block number
  }
})
 
// State is loaded lazily - only accessed accounts are loaded
// This allows working with huge state trees efficiently

Contract Utilities

Type-safe contract interactions with TypeScript support:

import { createContract } from 'tevm/contract'
 
// Define a contract interface
const erc20Contract = createContract({
  abi: [
    'function balanceOf(address owner) view returns (uint256)',
    'function transfer(address to, uint256 amount) returns (bool)',
    'event Transfer(address indexed from, address indexed to, uint256 value)'
  ],
  address: '0x123...' 
})
 
// Type-safe read and write operations
const balance = await erc20Contract.read.balanceOf('0x456...')
const txHash = await erc20Contract.write.transfer('0x789...', 1000n)

For Tevm Bundler users, directly import Solidity with full type safety:

// Import Solidity directly (with tevm bundler plugins)
import { ERC20 } from './ERC20.sol'
 
// Contract with full TypeScript types
const token = ERC20.withAddress('0x123...')
 
// Safe contract interaction
const decimals = await token.read.decimals()

Extensibility Model

Node Extension API

Tevm's plugin system allows adding new functionality to nodes:

import { createTevmNode } from 'tevm'
 
// Create a node with extensions
const node = createTevmNode().extend((baseNode) => {
  // Add custom methods
  return {
    async simulateBulkTransactions(txs) {
      const results = []
      for (const tx of txs) {
        const vm = await baseNode.getVm()
        results.push(await vm.runTx({ tx }))
      }
      return results
    },
    
    async resetToSnapshot(snapshot) {
      const vm = await baseNode.getVm()
      return vm.stateManager.revert(snapshot)
    }
  }
})
 
// Use the extended functionality
const snapshot = await node.getVm().stateManager.checkpoint()
const results = await node.simulateBulkTransactions([tx1, tx2, tx3])
await node.resetToSnapshot(snapshot)

This extension model allows for powerful customizations while maintaining the core API.

Further Resources

ResourceDescription
TevmNode Interface ReferenceDetailed API reference for the core node interface
GitHub RepositorySource code and contributions
Custom Precompiles GuideLearn how to extend the EVM
Performance ProfilingOptimize your Ethereum applications