Getting Started with Viem
Installation
Install Dependencies
npm install tevm viem@latestCreate 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
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 & Client — createMemoryClient with fork gives a local sandbox of mainnet state. client.tevmReady() waits for fork init.
Contract Interaction — readContract/writeContract match viem exactly. Writes return a tx hash.
Mining — client.mine({ blocks: 1 }) gives you full determinism unlike real networks.
Key Viem-Compatible Features
await client.getBalance({ address: '0x...' })
await client.getBlockNumber()
await client.readContract({ ... })
await client.writeContract({ ... })
await client.estimateGas({ ... })
await client.sendTransaction({ ... })
// And all other viem actionsCommon 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/commonis 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.ioOr 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
| Action | Description | Use Case |
|---|---|---|
tevmCall | Low-level EVM call with execution hooks | Deep inspection of contract execution |
tevmContract | Enhanced contract interaction with EVM hooks | Detailed debugging of contract calls |
tevmDeploy | Deploy with execution hooks | Understanding deployment execution flow |
tevmMine | Control block mining | Precise transaction inclusion control |
tevmSetAccount | Modify account state | Test different account scenarios |
tevmGetAccount | Read detailed account state | Inspect nonce, code, storage |
tevmDumpState | Export full EVM state | State persistence and analysis |
tevmLoadState | Import saved EVM state | Restore a specific state for testing |
tevmReady | Wait for fork to initialize | Ensure 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.

