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

Getting Started with Viem

Installation

Install Dependencies

npm
npm install tevm viem@latest

Create Your Client

Memory client:

import { createMemoryClient } from "tevm";
 
const client = createMemoryClient();

Or fork an existing chain:

import { createMemoryClient, http } from "tevm";
import { optimism } from "tevm/common";
 
const client = createMemoryClient({
  fork: {
    transport: http("https://mainnet.optimism.io"),
    common: optimism,
  },
});
 
await client.tevmReady();

A MemoryClient is a batteries-included client with all viem PublicActions, WalletActions, and TestActions, plus Tevm-specific actions prefixed tevm* (e.g. tevmCall, tevmSetAccount).

You're Ready

const blockNumber = await client.getBlockNumber();
console.log(`Current block: ${blockNumber}`);

Complete Example

Forking Example
import { createMemoryClient, http } from "tevm"; 
import { optimism } from "tevm/common"; 
import { parseAbi, parseEther } from "viem"; 
 
const client = createMemoryClient({
  fork: {
    transport: http("https://mainnet.optimism.io"), 
    common: optimism, 
  }, 
}); 
 
await client.tevmReady();
 
const blockNumber = await client.getBlockNumber();
console.log(`Current block number: ${blockNumber}`);
 
const account = `0x${"baD60A7".padStart(40, "0")}` as const;
const greeterContractAddress = "0x10ed0b176048c34d69ffc0712de06CbE95730748";
 
const greeterAbi = parseAbi([
  "function greet() view returns (string)", 
  "function setGreeting(string memory _greeting) public", 
]); 
 
await client.setBalance({
  address: account,
  value: parseEther("1"),
});
 
const currentGreeting = await client.readContract({
  address: greeterContractAddress,
  abi: greeterAbi,
  functionName: "greet",
});
console.log(`Current greeting: ${currentGreeting}`);
 
const txHash = await client.writeContract({ 
  account,
  address: greeterContractAddress,
  abi: greeterAbi,
  functionName: "setGreeting",
  args: ["Hello from Tevm!"],
  chain: optimism,
});
console.log(`Transaction sent: ${txHash}`);
 
await client.mine({ blocks: 1 }); 
 
const updatedGreeting = await client.readContract({
  address: greeterContractAddress,
  abi: greeterAbi,
  functionName: "greet",
});
console.log(`Updated greeting: ${updatedGreeting}`);
Code Walkthrough

Imports & ClientcreateMemoryClient with fork gives a local sandbox of mainnet state. client.tevmReady() waits for fork init.

Contract InteractionreadContract/writeContract match viem exactly. Writes return a tx hash.

Miningclient.mine({ blocks: 1 }) gives you full determinism unlike real networks.

Key Viem-Compatible Features

Standard viem API
await client.getBalance({ address: '0x...' })
await client.getBlockNumber()
await client.readContract({ ... })
await client.writeContract({ ... })
await client.estimateGas({ ... })
await client.sendTransaction({ ... })
// And all other viem actions

Common Patterns and Best Practices

Creating multiple clients

Commonly you'll use a viem client and a tevm client side by side:

import { createPublicClient, http } from "viem";
import { createMemoryClient } from "tevm";
import { optimism } from "tevm/common";
 
export const publicClient = createPublicClient({
  transport: http("https://mainnet.optimism.io"),
});
 
export const memoryClient = createMemoryClient({
  fork: {
    transport: publicClient,
    rebase: true, // (coming soon)
  },
});
  • Keep using normal viem clients alongside Tevm.
  • Use your viem client as the fork transport so viem's cache is shared with Tevm.
  • tevm/common is a superset of a viem chain — usable for both.

Racing JSON-RPC requests

Run a call with both tevm and viem, return whichever finishes first:

function raceExample() {
  const {resolve, reject, promise} = Promise.withResolvers()
  publicClient.estimateGas(...).then(result => resolve(result))
  memoryClient.estimateGas(...).then(result => resolve(result))
 
  return promise
}

A warm Tevm cache returns nearly instantly; a cold cache lets the RPC respond first while Tevm warms in background.

Using the Tevm Bundler

The Tevm Bundler imports contract ABIs into TypeScript and works with Wagmi, Viem, Ethers, Tevm (and Ponder). Useful even without TevmNode — a TevmContract is a library-agnostic typesafe ABI instance.

import { MyContract } from "./MyContract.sol";
 
function useExample() {
  return useReadContract({
    abi: MyContract.abi,
    address: `0x...`,
    method: "balanceOf",
    args: address,
  });
  // Or the typesafe `read.method()` API
  return useReadContract(
    MyContract.withAddress(`0x...`).read.balanceOf(address),
  );
}

Tree-Shakeable API

For production browser apps, use the tree-shakeable API to minimize bundle size:

import { createClient, http } from "viem";
import { createTevmTransport } from "tevm"; 
import { tevmCall, tevmDumpState } from "tevm/actions"; 
 
const client = createClient({
  transport: createTevmTransport({ 
    fork: { 
      transport: http("https://mainnet.optimism.io"), 
    }, 
  }), 
});
 
await tevmDumpState(client);

createTevmTransport takes the same options as a MemoryClient but only supports client.request. Always use createTevmTransport rather than passing a TevmClient via custom(TevmNode).

Using viem to talk to Tevm over HTTP

Tevm can also run as an HTTP server (useful as an Anvil-like testing tool).

CLI:

npx tevm serve --fork-url https://mainnet.optimism.io

Or run as an HTTP/Express/Hono/Next.js server in Node.js or Bun:

import { createMemoryClient, http } from "tevm";
import { createServer } from "tevm/server";
 
const memoryClient = createMemoryClient();
const server = createServer(memoryClient);
 
server.listen(8545, () => {
  console.log("server started on port 8545");
  http("http://localhost:8545")({})
    .request({ method: "eth_blockNumber" })
    .then(console.log)
    .catch(console.error);
});

Then connect via viem http as normal.

Tevm-Specific Actions

ActionDescriptionUse Case
tevmCallLow-level EVM call with execution hooksDeep inspection of contract execution
tevmContractEnhanced contract interaction with EVM hooksDetailed debugging of contract calls
tevmDeployDeploy with execution hooksUnderstanding deployment execution flow
tevmMineControl block miningPrecise transaction inclusion control
tevmSetAccountModify account stateTest different account scenarios
tevmGetAccountRead detailed account stateInspect nonce, code, storage
tevmDumpStateExport full EVM stateState persistence and analysis
tevmLoadStateImport saved EVM stateRestore a specific state for testing
tevmReadyWait for fork to initializeEnsure node is ready before use

Hook into the EVM

Hook directly into EVM execution via tevmCall and tevmContract:

await client.tevmContract({
  address: greeterContractAddress,
  abi: greeterAbi,
  functionName: "setGreeting",
  args: ["Hello!"],
 
  onStep: (stepInfo, next) => { 
    console.log(`Executing: ${stepInfo.opcode.name} at PC=${stepInfo.pc}`); 
    console.log(`Stack: ${stepInfo.stack.map((val) => val.toString())}`); 
    console.log(`Memory: ${stepInfo.memory.toString("hex")}`); 
    next?.(); 
  }, 
 
  onResult: (result) => {
    console.log(`Gas used: ${result.executionGasUsed}`);
    console.log(`Return value: 0x${result.returnValue?.toString("hex")}`);
  },
});

Enables:

  • Visual Debuggers — step-by-step transaction debuggers.
  • Educational Tools — explain EVM execution.
  • Custom Instrumentation — profile and analyze execution.
  • Intercepting Execution — modify behavior for testing.

Next Steps