Managing State
Tevm offers various approaches to manage Ethereum state, from low-level direct state access to high-level client APIs. Each approach has its benefits and use cases.
State Management Approaches
Raw API
import { createTevmNode } from 'tevm'
import { EthjsAccount } from 'tevm/utils'
const node = createTevmNode()
const vm = await node.getVm()
const stateManager = vm.stateManager
// Read account state
const address = '0x1234567890123456789012345678901234567890'
const account = await stateManager.getAccount(address)
console.log({
balance: account.balance,
nonce: account.nonce,
codeHash: account.codeHash,
storageRoot: account.storageRoot
})
// Create or update an account
await stateManager.putAccount(
address,
new EthjsAccount({
nonce: 0n,
balance: 10_000_000n,
storageRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421',
codeHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
})
)
// Delete an account
await stateManager.deleteAccount(address)
Contract State Management
📄 Deploy Code
Deploy smart contract bytecode to the blockchain
👁️ Read Code
Read the deployed bytecode of a contract
💾 Storage Access
Read and write to contract storage slots
🔄 State Reset
Clear contract storage or delete contracts
Raw API
// Using the raw StateManager API
// Deploy contract code
await stateManager.putContractCode(
address,
new Uint8Array([/* bytecode */])
)
// Read contract code
const code = await stateManager.getContractCode(address)
// Read storage slot
const slot = '0x0000000000000000000000000000000000000000000000000000000000000000'
const value = await stateManager.getContractStorage(address, slot)
// Write storage
const key = '0x0000000000000000000000000000000000000000000000000000000000000000'
const newValue = '0x0000000000000000000000000000000000000000000000000000000000000001'
await stateManager.putContractStorage(address, key, newValue)
// Clear all storage
await stateManager.clearContractStorage(address)
Framework Integration
Viem
import { createMemoryClient } from 'tevm'
const client = createMemoryClient()
// Use Viem-style API for basic operations
await client.setBalance({
address: '0x1234567890123456789012345678901234567890',
value: 1000000000000000000n
})
// Access raw state manager for advanced operations
const vm = await client.transport.tevm.getVm()
const stateManager = vm.stateManager
// Use raw API for complex operations
await stateManager.checkpoint()
try {
await stateManager.putContractStorage(address, key, value)
await stateManager.commit()
} catch (error) {
await stateManager.revert()
}
Advanced Features
State Checkpoints
Create atomic state changes that can be committed or reverted:
const stateManager = (await node.getVm()).stateManager
await stateManager.checkpoint()
try {
// Batch multiple state changes
await Promise.all([
stateManager.putAccount(address, account),
stateManager.putContractStorage(address, key, value),
])
await stateManager.commit()
} catch (error) {
await stateManager.revert()
console.error('State changes reverted:', error)
}
State Persistence
Save and load blockchain state:
// Dump complete state
const state = await stateManager.dumpCanonicalGenesis()
// Save state (example with localStorage)
localStorage.setItem('tevmState', JSON.stringify(state))
// Load saved state
const savedState = JSON.parse(localStorage.getItem('tevmState'))
await stateManager.generateCanonicalGenesis(savedState)
Fork Mode
Tevm supports lazy loading with caching when forking from another network:
const node = createTevmNode({
fork: {
transport: http('https://mainnet.optimism.io')
}
})
const stateManager = (await node.getVm()).stateManager
// First access fetches from remote
const account = await stateManager.getAccount('0x1234...')
// Subsequent access uses cache
const cachedAccount = await stateManager.getAccount('0x1234...')
Best Practices
⚠️ Error Handling
Properly handle errors from state operations
🛡️ State Isolation
Create isolated copies of state for testing
⚛️ Atomic Operations
Group related state changes with checkpoints
Error Handling
try {
const account = await stateManager.getAccount('0x1234...')
if (!account) {
throw new Error('Account not found')
}
// Work with account
} catch (error) {
if (error instanceof MissingAccountError) {
// Handle specific error types
console.log('Account does not exist yet')
} else {
// Handle generic errors
console.error('State operation failed:', error)
}
}