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 interfaceEvm
- The Ethereum Virtual MachineStateManager
- Manages blockchain stateBlockchain
- 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:
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
})
// 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 })
}
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 Level | Description | Best For |
---|---|---|
Viem Client API | Standard viem actions plus Tevm-specific actions | Most application development |
JSON-RPC API | Standard Ethereum RPC methods plus Anvil and Tevm-specific methods | Direct RPC integration, tooling |
TevmNode API | Direct access to the node and its components | Advanced use cases, custom extensions |
Low-Level Components | Direct access to EVM, StateManager, Blockchain, etc. | Tool developers, deep customization |
📁 API Layers
📁 Viem Client API (high-level)
📁 JSON-RPC API
📁 TevmNode API
📁 Low-Level Components
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
Resource | Description |
---|---|
TevmNode Interface Reference | Detailed API reference for the core node interface |
GitHub Repository | Source code and contributions |
Custom Precompiles Guide | Learn how to extend the EVM |
Performance Profiling | Optimize your Ethereum applications |