EVM Events
Tevm call actions accept callbacks for live execution inspection. They are useful for debuggers, tracers, profilers, and test assertions.
Call Events
The four callbacks are:
onStep(step, next)before each opcode;onBeforeMessage(message, next)before an EVM call frame;onAfterMessage(result, next)after an EVM call frame;onNewContract(contract, next)when execution creates a contract.
Callbacks use a middleware continuation. Always call next?.() or execution will stop at that callback.
import {
bytesToHex,
createMemoryClient,
PREFUNDED_ACCOUNTS,
} from 'tevm'
const client = createMemoryClient()
const contract = '0x4444444444444444444444444444444444444444'
await client.tevmSetAccount({
address: contract,
deployedBytecode: '0x6001600055',
})
const opcodes: string[] = []
const result = await client.tevmCall({
from: PREFUNDED_ACCOUNTS[0].address,
to: contract,
createTrace: true,
onStep(step, next) {
opcodes.push(step.opcode.name)
console.log(step.pc, step.gasLeft, Array.from(step.stack))
next?.()
},
onBeforeMessage(message, next) {
console.log('call', message.to?.toString(), message.value)
next?.()
},
onAfterMessage(messageResult, next) {
console.log(
messageResult.execResult.executionGasUsed,
bytesToHex(messageResult.execResult.returnValue),
)
next?.()
},
})
console.log(opcodes, result.executionGasUsed, result.trace?.structLogs)The live step object contains interpreter state, including memory and the mutable stack. Copy values with Array.from(step.stack) before retaining them in a UI.
Tree-Shakable Calls
The standalone action works with a viem client using a Tevm transport.
import { createTevmTransport, tevmCall } from 'tevm'
import { createClient } from 'viem'
const client = createClient({
transport: createTevmTransport(),
})
const result = await tevmCall(client, {
deployedBytecode: '0x6001600055',
createTrace: true,
})
console.log(result.trace?.structLogs)Standalone Tevm action functions are exported from tevm. tevm/actions exports lower-level handler factories and types.
Returned Traces
createTrace: true adds a Geth-style trace to the call result:
import { createMemoryClient } from 'tevm'
const client = createMemoryClient()
const result = await client.tevmCall({
deployedBytecode: '0x6001600055',
createTrace: true,
})
for (const step of result.trace?.structLogs ?? []) {
console.log({
pc: step.pc,
opcode: step.op,
gasLeft: step.gas,
gasCost: step.gasCost,
depth: step.depth,
stack: step.stack,
})
}The returned trace is easier to serialize than live interpreter objects. Use callbacks when updates must stream during execution and result.trace when post-processing is enough.
Mining Events
tevmMine can report each block as it is produced:
import { createMemoryClient } from 'tevm'
const client = createMemoryClient()
const blockNumbers: bigint[] = []
await client.tevmMine({
blockCount: 2,
onBlock(block, next) {
blockNumbers.push(block.header.number)
next?.()
},
})
console.log(blockNumbers)Query canonical results with viem actions such as getBlock, getTransactionReceipt, and getLogs.

