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

Forking

A Tevm fork reads historical chain data from an upstream EIP-1193 transport and stores local changes in an in-memory overlay. It does not copy the entire remote chain and never writes upstream.

Create a Reproducible Fork

import { createMemoryClient, http } from 'tevm'
import { mainnet } from 'tevm/common'
 
const client = createMemoryClient({
  common: mainnet,
  fork: {
    transport: http('https://ethereum-rpc.publicnode.com')({}),
    blockTag: 20_000_000n,
  },
})
 
await client.tevmReady()
 
const block = await client.getBlock({ blockNumber: 20_000_000n })
console.log(block.hash)

fork.transport is an EIP-1193 request function. viem's http(url) returns a transport factory, so invoke it with ({}) before passing it to Tevm.

Set common when the chain is known. This avoids an extra chain-ID request and selects the correct hardfork and chain behavior. Pin blockTag for tests; omit it only when following the current upstream head is intended.

Read Remote State, Write Locally

Missing accounts, code, storage, and blocks are fetched lazily and cached. Test actions modify only the overlay.

import { createMemoryClient, http } from 'tevm'
import { mainnet } from 'tevm/common'
 
const client = createMemoryClient({
  common: mainnet,
  fork: {
    transport: http('https://ethereum-rpc.publicnode.com')({}),
    blockTag: 20_000_000n,
  },
})
 
const account = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
const remoteBalance = await client.getBalance({
  address: account,
  blockNumber: 20_000_000n,
})
 
await client.setBalance({
  address: account,
  value: remoteBalance + 1n,
})
 
console.log(await client.getBalance({ address: account }))

Historical reads at or before the fork block can still resolve remotely. New local blocks, receipts, and state roots are owned by Tevm.

Fork Through a Viem Transport

The fork.transport option accepts the transport object returned by a viem transport factory:

import { createMemoryClient, http } from 'tevm'
import { mainnet } from 'tevm/common'
 
const upstream = http('https://ethereum-rpc.publicnode.com')({})
 
const client = createMemoryClient({
  common: mainnet,
  fork: {
    transport: upstream,
    blockTag: 20_000_000n,
  },
})
 
await client.tevmReady()
console.log(await client.getBlockNumber())

Pass the transport object, not an already-created viem client.

Test a Fork with Snapshots

Snapshots isolate local changes without refetching the fork:

import { createMemoryClient, http } from 'tevm'
import { mainnet } from 'tevm/common'
 
const client = createMemoryClient({
  common: mainnet,
  fork: {
    transport: http('https://ethereum-rpc.publicnode.com')({}),
    blockTag: 20_000_000n,
  },
})
 
const snapshotId = await client.snapshot()
 
await client.setCode({
  address: '0x1111111111111111111111111111111111111111',
  bytecode: '0x6001600055',
})
 
await client.revert({ id: snapshotId })

Failure and Cache Behavior

  • The first uncached read can fail if the upstream transport is unavailable.
  • Cached fork data and all local state remain usable without another network request.
  • An invalid blockTag fails during initialization or the first read.
  • rc.151 does not provide an automatic rebase mode. Create a new client when a test needs a different fork block.

Related

Architecture Overview · Managing State · Local Testing