Managing State
Tevm exposes state management via two layers: a low-level StateManager and a high-level viem-style client API.
State Management Approaches
Raw API
import { createTevmNode } from 'tevm'
import { createAddress } from 'tevm/address'
import { createAccount } from 'tevm/utils'
const node = createTevmNode()
const vm = await node.getVm()
const stateManager = vm.stateManager
// Read account state
const address = createAddress('0x1234567890123456789012345678901234567890')
const account = await stateManager.getAccount(address)
if (account) {
console.log({
balance: account.balance,
nonce: account.nonce,
codeHash: account.codeHash,
storageRoot: account.storageRoot
})
}
// Create or update an account
await stateManager.putAccount(
address,
createAccount({
nonce: 0n,
balance: 10_000_000n
})
)
// Delete an account
await stateManager.deleteAccount(address)Contract State Management
- Deploy bytecode
- Read deployed code
- Read/write storage slots
- Clear storage or delete contracts
Raw API
import { createAddress } from 'tevm/address'
import { hexToBytes } from 'tevm/utils'
const address = createAddress('0x1234567890123456789012345678901234567890')
// Deploy contract code
await stateManager.putCode(address, new Uint8Array([1, 2, 3]))
// Read contract code
const code = await stateManager.getCode(address)
// Read storage slot
const slot = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000000')
const value = await stateManager.getStorage(address, slot)
// Write storage
const key = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000000')
const newValue = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000001')
await stateManager.putStorage(address, key, newValue)
// Clear all storage
await stateManager.clearStorage(address)Framework Integration
You can combine viem/ethers with raw state access.
Viem
import { createMemoryClient } from 'tevm'
const client = createMemoryClient()
await client.setBalance({
address: '0x1234567890123456789012345678901234567890',
value: 1000000000000000000n
})
// Drop down to raw state manager
const vm = await client.transport.tevm.getVm()
const stateManager = vm.stateManager
await stateManager.checkpoint()
try {
await stateManager.putStorage(address, key, value)
await stateManager.commit()
} catch (error) {
await stateManager.revert()
}Advanced Features
State Checkpoints
Atomic changes that can be committed or reverted (transaction-like semantics):
const stateManager = (await node.getVm()).stateManager
await stateManager.checkpoint()
try {
await stateManager.putAccount(address, account)
await stateManager.putStorage(address, key, value)
await stateManager.commit()
} catch (error) {
await stateManager.revert()
console.error('State changes reverted:', error)
}State Persistence
Dump and restore canonical state to any storage backend (localStorage, DB, etc.):
const state = await stateManager.dumpCanonicalGenesis()
localStorage.setItem('tevmState', JSON.stringify(state))
const savedState = JSON.parse(localStorage.getItem('tevmState'))
await stateManager.generateCanonicalGenesis(savedState)Fork Mode
Forked nodes lazy-load and cache state from a remote provider:
import { createTevmNode, http } from 'tevm'
import { createAddress } from 'tevm/address'
const rpcUrl = process.env.MAINNET_RPC_URL
if (!rpcUrl) {
throw new Error('MAINNET_RPC_URL is required for fork mode')
}
const node = createTevmNode({
fork: { transport: http(rpcUrl)({}) }
})
const stateManager = (await node.getVm()).stateManager
const address = createAddress('0x1234567890123456789012345678901234567890')
// First access fetches from remote
const account = await stateManager.getAccount(address)
// Subsequent access uses cache
const cachedAccount = await stateManager.getAccount(address)Best Practices
- Handle errors from state operations.
- Use
deepCopyfor isolated test scenarios. - Group related changes with checkpoints.
Error Handling
import { createAddress } from 'tevm/address'
const address = createAddress('0x1234567890123456789012345678901234567890')
try {
const account = await stateManager.getAccount(address)
if (!account) {
console.log('Account does not exist yet')
} else {
console.log('Balance:', account.balance)
}
} catch (error) {
console.error('State operation failed:', error)
}Related Resources
Runtime Model and ZEVM · State Manager API · Account Management · Contract Storage · Forking Guide

