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

Install Tevm and Zod

npm
npm install tevm zod

Hello World: Ralph Loop

Before choosing clients and integrations, start with one full flow. Ralph Loop is a tiny MDX entry point that accepts props, validates them, writes to a local in-memory chain, mines, then reads the result back.

Create the MDX shell

type RalphLoopPageProps = {
  name?: string;
  rounds?: number;
};
 
export default async function RalphLoopPage(props: RalphLoopPageProps) {
  const lines = await runRalphLoop(props);
 
  return <pre>{lines.join("\n")}</pre>;
}

Add the imports

import { createMemoryClient, PREFUNDED_ACCOUNTS } from "tevm";
import { SimpleContract } from "tevm/contract";
import { z } from "zod";

Define the Zod schemas

const ralphLoopProps = z.object({
  name: z.string().min(1).default("Ralph"),
  rounds: z.number().int().min(1).max(5).default(3),
});
 
type RalphLoopPageProps = z.input<typeof ralphLoopProps>;

Run the Ralph loop

async function runRalphLoop(input: RalphLoopPageProps) {
  const { name, rounds } = ralphLoopProps.parse(input);
  const client = createMemoryClient();
  const contract = SimpleContract.withAddress(`0x${"40".repeat(20)}`);
 
  await client.setCode({
    address: contract.address,
    bytecode: contract.deployedBytecode,
  });
 
  const lines = [];
 
  for (let round = 1; round <= rounds; round += 1) {
    await client.writeContract({
      account: PREFUNDED_ACCOUNTS[0],
      address: contract.address,
      abi: contract.abi,
      functionName: "set",
      args: [BigInt(round)],
    });
 
    await client.tevmMine();
 
    const value = await client.readContract({
      address: contract.address,
      abi: contract.abi,
      functionName: "get",
    });
 
    lines.push(`Hello ${name}: loop ${round} committed value ${value}`);
  }
 
  return lines;
}

Choose Your Client

In-memory client

Spin up an empty Ethereum node:

import { createMemoryClient } from "tevm";
 
const memoryClient = createMemoryClient();
const blockNumber = await memoryClient.getBlockNumber();
How it works
  1. memoryClient runs an Ethereum node entirely in memory (TypeScript + Wasm).
  2. Query it via the viem api.

Fork client

Fork an existing chain (like Anvil/Hardhat):

import { createMemoryClient } from "tevm";
import { http } from "viem"; // also re-exported from tevm
import { mainnet } from "tevm/common";
 
const forkClient = createMemoryClient({
  fork: {
    transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"),
    common: mainnet,
  },
});
How it works
  1. Pass a transport to createMemoryClient.
  2. Tevm fetches the latest block on creation.
  3. State is lazily fetched from the fork URL as needed.
  4. Tevm has optimizations making fetching more efficient than Anvil/Hardhat.

Rebasing client (coming soon)

Forks an existing chain and listens for new blocks, rebasing local state as new blocks arrive.

import { createMemoryClient } from "tevm";
import { http } from "viem";
import { mainnet } from "tevm/common";
 
const forkClient = createMemoryClient({
  fork: {
    transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"),
    common: mainnet,
    rebase: true,
  },
});
How it works
  1. Same as the fork client but with rebase: true.
  2. Tevm updates its fork as the "latest" tag changes, efficiently invalidating only affected state.

Tree-shakable client

Tevm supports Viem's tree-shakable API:

import { createClient, http } from "viem";
import { createTevmTransport } from "tevm/memory-client";
import { optimism } from "tevm/common";
 
const client = createClient({
  transport: createTevmTransport({
    fork: { transport: http("https://mainnet.optimism.io") },
    common: optimism,
  }),
});
 
import { getBlockNumber } from "viem";
await getBlockNumber(client);

The tree-shakable API excludes unused viem/tevm actions. Recommended for browser apps to keep bundle size minimal.

Ethers client

Tevm works with Ethers (and any EIP-1193-compatible library):

import { TevmProvider } from "tevm/ethers";
import { http, toHex, parseEth } from "tevm";
 
const provider = await TevmProvider.createMemoryProvider({
  fork: { transport: http("https://mainnet.optimism.io") },
});
await provider.send("tevm_setAccount", [
  {
    address: `0x${"69".repeat(20)}`,
    nonce: toHex(1),
    balance: toHex(parseEth("25")),
  },
]);

Use your client to read and write to an in-memory blockchain

A full flow: add a contract, write to it, mine, then read.

import { createMemoryClient, PREFUNDED_ACCOUNTS } from "tevm";
import { http } from "viem";
import { SimpleContract } from "tevm/contract";
import { optimism } from "tevm/common";
 
const client = createMemoryClient({
  common: optimism,
  fork: { transport: http("https://mainnet.optimism.io") },
});
 
const contract = SimpleContract.withAddress(`0x${"40".repeat(20)}`);
 
await client.setCode({
  address: contract.address,
  bytecode: contract.deployedBytecode,
});
 
await client.writeContract({
  account: PREFUNDED_ACCOUNTS[0],
  abi: contract.abi,
  functionName: "set",
  args: [420n],
  address: contract.address,
});
 
await client.tevmMine();
 
const value = await client.readContract({
  abi: contract.abi,
  functionName: "get",
  address: contract.address,
});
 
console.log(value); // 420n
How it works
  1. Create an in-memory memoryClient.
  2. Define the contract and its address.
  3. Deploy via setCode (simpler than a full deploy tx).
  4. Write using viem-style API.
  5. Mine to include the tx.
  6. Read the updated value.

Key Points

  • Familiar Interfaceviem is the primary API.
  • Ethers Support — works via the EIP-1193 provider standard.
  • In-Memory Execution — runs in memory, not over JSON-RPC/HTTP.
  • Full Control — mine on demand, manipulate state, more.
  • Cross-platform — runs in the browser.
  • Powerful — advantages over Anvil, Ganache, and Hardhat.

Start Building

// Runs in browsers, Node.js, Deno, Bun. Zero native deps.
import { createMemoryClient } from "tevm";
// Optionally import Solidity directly (requires Tevm Bundler — see Bundler Quickstart)
import { ComplexSimulation } from "../contracts/ComplexSimulation.s.sol";
 
const client = createMemoryClient();
 
console.log(await client.getBlockNumber());
 
const {
  data,
  error,
  logs,
  createdAddresses,
  executionGasUsed,
  l1Fee,
  trace,
  accessList,
  txHash,
} = await client.tevmContract({
  deployedBytecode: ComplexSimulation.deployedBytecode,
  ...ComplexSimulation.read.simulate(2n, "arg2"),
  createTrace: true,
  createAccessList: true,
  createTransaction: true,
  throwOnFail: false,
  onStep: (step, next) => {
    console.log(step.opcode);
    next?.();
  },
});

Essential viem concepts

Tevm is a great learning platform for both Ethereum and TypeScript:

  • Isolated Environment — experiment without network costs or confirmations.
  • TypeScript Integration — full type safety and autocomplete.
  • Simple API Surface — focus on core concepts.
  • Step Debugger — trace opcode-level execution.

For users new to viem, familiarize yourself with viem. Essentials:

  • ClientscreateClient, createPublicClient, createWalletClient, createTestClient, createMemoryClient. The main abstraction.
  • ActionsgetBlockNumber, call, readContract, estimateGas, tevmSetAccount, etc.
  • Transports — EIP-1193 providers used to resolve JSON-RPC. Both viem and tevm use transports like http().
  • TevmNode — itself a transport, plugging an in-memory Ethereum node into viem.

Ready to dive in?