Viem is a lightweight, EIP-1193–compliant client for sending JSON-RPC requests—so it can plug into any EIP-1193–compatible server (Anvil, Hardhat, Alchemy, Geth, Reth, Tevm, etc.). Tevm is an EIP-1193–compatible server that can plug into Viem, Ethers, Express.js, Bun, or any JSON-RPC handler.
-
Transport Defines how you send JSON-RPC requests. For example:
import { http } from "viem"; // Create a new HTTP transport and send a simple request: const result = await http("https://mainnet.infura.io/v3/YOUR_PROJECT_ID")({}).request({ method: "eth_blockNumber", }); console.log(result); // e.g. { jsonrpc: "2.0", id: 1, result: "0x10d4f" }
Here,
http()returns a transport‐factory function; you pass any options (e.g., headers) in the first call, then call.request(...)with your JSON-RPC payload. -
Utils Utility functions that don’t require talking to a node. For example, to compute Keccak-256:
import { keccak256 } from "viem"; const data = new TextEncoder().encode("hello world"); const hash = keccak256(data); console.log(hash); // "0x47173285a8d7342…"
You can use any Viem utility (e.g.,
encodeFunctionArgs,decodeEventLog) without a live transport. -
Clients Opinionated, type-safe structs built around transports. A basic client is effectively the same as
http(), but you can inject chain metadata:import { createPublicClient, http } from "viem"; import { optimism } from "viem/chains"; // Create a typed client for the Optimism network: const client = createPublicClient({ chain: optimism, transport: http("https://optimism-mainnet.infura.io/v3/YOUR_PROJECT_ID")({}), }); // This is equivalent to: http(...)({}).request(...) const blockNumberRx = await client.request({ method: "eth_blockNumber" }); console.log(blockNumberRx); // e.g. { jsonrpc: "2.0", id: 1, result: "0xABCDE" }
By passing
chain: optimism, Viem will handle any chain‐specific quirks (e.g., formatting of special transactions). -
Actions Opinionated, type-safe wrappers around raw JSON-RPC methods. For example, the built‐in
getBlockNumberaction:import { getBlockNumber } from "viem/actions"; // `params` is undefined because getBlockNumber doesn’t take any arguments. const latest = await getBlockNumber(client, undefined); console.log(latest); // e.g. 11217453
Under the hood,
getBlockNumber(client, undefined)simply invokesclient.request({ method: "eth_blockNumber" })and parses the response. -
Convenience Clients Higher‐level clients that bundle actions so you can call
client.someAction(params)directly:import { createPublicClient, createWalletClient, createMemoryClient } from "viem"; // PublicClient example: const publicClient = createPublicClient({ chain: optimism, transport: http("https://optimism-mainnet.infura.io/v3/YOUR_PROJECT_ID")({}), }); const blockNum = await publicClient.getBlockNumber(); console.log(blockNum); // (Similarly, you could do): // const memoryClient = createMemoryClient({ chain: optimism, transport: http(...)({}) }); // memoryClient.getBlockNumber(); // WalletClient example (when you want to sign transactions): const walletClient = createWalletClient({ chain: optimism, transport: http("https://optimism-mainnet.infura.io/v3/YOUR_PROJECT_ID")({}), account: "0xYourAddress", privateKey: "0xYourPrivateKey", }); const balance = await walletClient.getBalance(); console.log(balance);
-
JSON-RPC Procedures All Tevm RPC methods live under
tevm/actions. Internally, Tevm has:- An EVM implementation in Zig that executes opcodes.
- A state manager and immutable blockchain structure.
- (Optionally) Anvil compatibility to mirror Hardhat/Anvil behavior.
You can create a fully in-memory EIP-1193 node like this:
import { createTevmNode } from "tevm/node"; import { eip1193RequestFn } from "tevm/eip1193"; // Create a new Tevm node with default configuration: const node = createTevmNode().extend(eip1193RequestFn()); // Now you can call any JSON-RPC method on `node.request(...)`: const blockNumber = await node.request({ method: "eth_blockNumber" }); console.log(blockNumber); // e.g. "0x0" (hex-encoded)
-
Node Configuration When instantiating a Tevm node, you can pass custom settings:
import { createTevmNode } from "tevm/node"; import { eip1193RequestFn } from "tevm/eip1193"; import { precompiles, EIPInfo } from "tevm/config"; const node = createTevmNode({ chainId: 420, // custom chainId eips: EIPInfo.latest, precompiles: [...precompiles.default], }).extend(eip1193RequestFn());
-
Communication Methods
-
Direct JSON-RPC
const blockNumber = await node.request({ method: "eth_blockNumber" });
-
HTTP Server
import { createServer } from "tevm/server"; import { createMemoryClient } from "tevm"; // Wrap Tevm in an HTTP server (listens on port 8545): const server = createServer(createMemoryClient()); server.listen(8545, () => { console.log("listening on port 8545"); // From anywhere (e.g., another process), you can do: import { http } from "viem"; http("http://localhost:8545")({}).request({ method: "eth_blockNumber", }) .then((response) => { console.log(response); }); });
Output in console:
listening on port 8545 response { jsonrpc: "2.0", method: "eth_blockNumber", result: "0x0", id: 1 } -
In-Memory Client (Viem/Ethers)
-
Using Viem
import { createMemoryClient, createTevmTransport } from "tevm"; import { createPublicClient } from "viem"; // Use Tevm’s custom transport instead of `http()`: const tevmTransport = createTevmTransport({ chain: { chainId: 1337, name: "Local Tevm" }, node: createTevmNode().extend(eip1193RequestFn()), }); const client = createPublicClient({ transport: tevmTransport, chain: { chainId: 1337, name: "Local Tevm" }, }); // Now `client.getBlockNumber()` calls go directly to the in‐memory Tevm instance: const bn = await client.getBlockNumber(); console.log(bn); // e.g. 0
-
Using Ethers.js BrowserProvider
import { createTevmNode } from "tevm/node"; import { eip1193RequestFn } from "tevm/eip1193"; import { ethers } from "ethers"; const node = createTevmNode().extend(eip1193RequestFn()); // Wrap Tevm’s EIP-1193 interface in an Ethers.js provider: const provider = new ethers.BrowserProvider(node); // Now you can use any Ethers.js methods: const blockNumber = await provider.getBlockNumber(); console.log(blockNumber); // e.g. 0
-
-
-
Convenience Clients Tevm also ships its own in-memory convenience client:
import { createMemoryClient } from "tevm"; import { eip1193RequestFn } from "tevm/eip1193"; // `createMemoryClient()` under the hood spins up a Tevm node + actions: const client = createMemoryClient({ chain: { chainId: 1337, name: "Local Tevm" }, eips: [/* … */], precompiles: [/* … */], }); // Now you can directly call: const bn = await client.getBlockNumber(); console.log(bn); // e.g. 0 // And because we’ve wired up “Anvil” methods, you can set code on an address: await client.setCode({ address: "0xAbC1230000000000000000000000000000000000", code: "0x6001600155", // simple runtime bytecode }); const code = await client.getCode({ address: "0xAbC1230000000000000000000000000000000000", }); console.log(code); // "0x6001600155"
-
Anvil Compatibility Tevm supports Anvil-style methods (e.g.,
setCode,impersonateAccount,evm_mine, etc.). For example:// Using the same `client` from above: // 1. Set bytecode at address await client.setCode({ address: "0xAbC1230000000000000000000000000000000000", code: "0x6001600155", }); // 2. Mine a new block await client.mine(); // 3. Check balance, code, etc. const code = await client.getCode({ address: "0xAbC1230000000000000000000000000000000000" }); console.log(code); // "0x6001600155"
-
Server Wrapping You can also expose Tevm over HTTP by combining
createServerwith Viem’shttp()call:import { createServer } from "tevm/server"; import { createMemoryClient } from "tevm"; import { http } from "viem"; const server = createServer(createMemoryClient()); server.listen(8545, () => { console.log("listening on port 8545"); http("http://localhost:8545")({}).request({ method: "eth_blockNumber", }) .then((response) => { console.log(response); }); });
Running
bun run example.tsproduces:➜ bun run example.ts listening on port 8545 response { jsonrpc: "2.0", method: "eth_blockNumber", result: "0x0", id: 1 }