Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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.

Related