# Tevm Node > A lightweight, unopinionated, powerful EVM node that runs in the browser ### Advanced Ethers.js Integration For basic setup (provider, wallets, deploy/call), see [Using with Ethers.js](/examples/ethers). This page covers advanced patterns. #### Contract Deployment ```ts import { ContractFactory, Wallet } from 'ethers' const abi = [/* ... */] const bytecode = '0x...' const signer = Wallet.createRandom().connect(provider) await client.setBalance({ address: signer.address, value: parseEther('10') }) const factory = new ContractFactory(abi, bytecode, signer) const contract = await factory.deploy(...constructorArgs) await client.mine({ blocks: 1 }) const contractAddress = await contract.getAddress() console.log(`Contract deployed to: ${contractAddress}`) ``` #### Working with Events ```ts contract.on('Transfer', (from, to, amount, event) => { console.log(`Transfer of ${amount} from ${from} to ${to}`) console.log(`Block: ${event.blockNumber}`) console.log(`Transaction hash: ${event.transactionHash}`) }) // Query past events const filter = contract.filters.Transfer() const events = await contract.queryFilter(filter, -1000, 'latest') for (const event of events) { const { from, to, value } = event.args console.log(`Historical transfer: ${value} from ${from} to ${to}`) } ``` ### Common Patterns #### State Snapshots & Time Control ```ts const snapshot = await client.snapshot() await contract.transfer(recipient, amount) await client.mine({ blocks: 1 }) await client.revert({ id: snapshot }) // Manipulate blockchain time (1 hour in the future) await client.setNextBlockTimestamp(Date.now() + 3600000) ``` #### Debugging Transactions Combine ethers.js with Tevm's EVM stepping: ```ts const calldata = contract.interface.encodeFunctionData( 'complexFunction', [param1, param2] ) const result = await client.tevmCall({ to: await contract.getAddress(), data: calldata, onStep: (step, next) => { console.log(`Opcode: ${step.opcode.name}`) console.log(`Memory: ${step.memory}`) next?.() } }) ``` ### Next Steps * [Forking from live networks](/core/forking) * [Building a debugger UI](/examples/debugger-ui) * [Local testing](/examples/local-testing) ## @tevm/actions Action handlers for the Tevm client, covering standard Ethereum JSON-RPC, Anvil-compatible test methods, debug/tracing, and Tevm-specific calls. Full API: [packages/actions/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs). ### Installation ```bash npm install @tevm/actions ``` ### API Reference #### Error Classes * [BlobGasLimitExceededError](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/classes/BlobGasLimitExceededError.md) * [MissingAccountError](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/classes/MissingAccountError.md) * [NoForkUrlSetError](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/classes/NoForkUrlSetError.md) #### Core Types * [Address](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/Address.md) * [Abi](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/Abi.md) * [Block](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/Block.md) * [BlockTag](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/BlockTag.md) #### Base Actions ##### Call Actions * [CallHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/CallHandler.md) * [CallParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/CallParams.md) * [CallResult](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/CallResult.md) * [BaseCallParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/BaseCallParams.md) ##### Contract Actions * [ContractHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/ContractHandler.md) * [ContractParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/ContractParams.md) * [ContractResult](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/ContractResult.md) ##### Deploy Actions * [DeployHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/DeployHandler.md) * [DeployParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/DeployParams.md) * [DeployResult](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/DeployResult.md) #### Validation Functions * [validateBaseCallParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateBaseCallParams.md) * [validateCallParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateCallParams.md) * [validateContractParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateContractParams.md) * [validateGetAccountParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateGetAccountParams.md) * [validateLoadStateParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateLoadStateParams.md) * [validateMineParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateMineParams.md) * [validateSetAccountParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/validateSetAccountParams.md) #### JSON-RPC Procedures * [anvilImpersonateAccountJsonRpcProcedure](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/anvilImpersonateAccountJsonRpcProcedure.md) * [callProcedure](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/callProcedure.md) * [getAccountProcedure](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/getAccountProcedure.md) * [mineProcedure](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/mineProcedure.md) * [requestProcedure](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/requestProcedure.md) * [requestBulkProcedure](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/requestBulkProcedure.md) #### Internal Utilities * [forkAndCacheBlock](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/forkAndCacheBlock.md) * [handlePendingTransactionsWarning](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/handlePendingTransactionsWarning.md) * [shouldCreateTransaction](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/functions/shouldCreateTransaction.md) #### Ethereum JSON-RPC Actions ##### Account & Network * [EthAccountsHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthAccountsHandler.md) * [EthChainIdHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthChainIdHandler.md) * [EthCoinbaseHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthCoinbaseHandler.md) * [EthGasPriceHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthGasPriceHandler.md) * [EthBlockNumberHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthBlockNumberHandler.md) ##### State Reading * [EthGetBalanceHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthGetBalanceHandler.md) * [EthGetCodeHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthGetCodeHandler.md) * [EthGetStorageAtHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthGetStorageAtHandler.md) * [EthCallHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthCallHandler.md) ##### Block Operations * [EthGetBlockByHashHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthGetBlockByHashHandler.md) * [EthGetBlockByNumberHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/EthGetBlockByNumberHandler.md) #### Anvil (Testing & Development) Actions ##### State Manipulation * [AnvilSetBalanceHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilSetBalanceHandler.md) * [AnvilSetCodeHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilSetCodeHandler.md) * [AnvilSetNonceHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilSetNonceHandler.md) * [AnvilSetStorageAtHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilSetStorageAtHandler.md) ##### Mining & Chain Control * [AnvilMineHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilMineHandler.md) * [AnvilSetChainIdHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilSetChainIdHandler.md) * [AnvilResetHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/AnvilResetHandler.md) #### Debug Actions * [DebugTraceCallHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/DebugTraceCallHandler.md) * [DebugTraceTransactionHandler](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/type-aliases/DebugTraceTransactionHandler.md) ### Usage Examples ```typescript import { createTevmNode } from 'tevm' import { callHandler, contractHandler, deployHandler, ethCallHandler } from 'tevm/actions' const client = createTevmNode() const call = await callHandler(client)({ to: '0x1234567890123456789012345678901234567890', data: '0x', value: 1000n, }) const contract = await contractHandler(client)({ to: '0x1234567890123456789012345678901234567890', abi: [...], functionName: 'transfer', args: ['0x0000000000000000000000000000000000000000', 1000n], }) const deploy = await deployHandler(client)({ bytecode: '0x...', abi: [...], args: ['constructor arg'], }) const ethCall = await ethCallHandler(client)({ to: '0x1234567890123456789012345678901234567890', data: '0x', }) ``` All actions accept `throwOnFail: false` to return errors in the result instead of throwing. ```typescript const invalidCall = await callHandler(client)({ to: '0x123', data: '0x', throwOnFail: false, }) if (invalidCall.errors) { console.error(invalidCall.errors) } ``` ### See Also * [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/) * [Anvil Documentation](https://book.getfoundry.sh/reference/anvil/) * [Full API Reference](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs/globals.md) ## @tevm/address Utilities for Ethereum addresses. Wraps the ZEVM-compatible `EthjsAddress` primitive with a Tevm-style API and returns EIP-55 checksummed strings. ### Installation ```bash npm install @tevm/address ``` ### API Reference #### `Address` Extends ZEVM's `EthjsAddress` (re-exported from `@tevm/utils`). Methods: * `toString()` - checksummed hex string * `toBytes()` - `Uint8Array` * `equals(address)` - equality check * `isZero()` - zero-address check * `isPrecompileOrSystemAddress()` - precompile/system check #### `createAddress(input): Address` Accepts hex string, unprefixed hex string, number, bigint, `Uint8Array`, or `Address`. ```typescript import { createAddress } from '@tevm/address' createAddress(`0x${'00'.repeat(20)}`) createAddress(0n) createAddress(new Uint8Array(20)) createAddress('55'.repeat(20)) ``` Throws `InvalidAddressError` on invalid input. #### `createContractAddress(from: Address, nonce: bigint): Address` CREATE address. Throws `InvalidAddressError` if `from` is invalid. ```typescript import { createAddress, createContractAddress } from '@tevm/address' const contractAddress = createContractAddress(createAddress('0x1234...'), 0n) ``` #### `create2ContractAddress(from: Address, salt: string, code: string): Address` CREATE2 address ([EIP-1014](https://eips.ethereum.org/EIPS/eip-1014)). `salt` must be 32 bytes hex. ```typescript import { createAddress, create2ContractAddress } from '@tevm/address' const create2Address = create2ContractAddress( createAddress('0x1234...'), `0x${'00'.repeat(32)}`, '0x...', // creation code ) ``` Throws `InvalidSaltError` or `InvalidAddressError`. Union: `Create2ContractAddressError`. ### See Also * [EIP-55 Address Format](https://eips.ethereum.org/EIPS/eip-55) * [EIP-1014 CREATE2](https://eips.ethereum.org/EIPS/eip-1014) * [@tevm/utils](https://tevm.sh/reference/tevm/utils/) ## @tevm/block Block, block-header, and RLP/JSON-RPC serialization utilities. Includes Verkle/EIP-6800 payload types only; Tevm does not execute Verkle witnesses. Full API: [packages/block/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs). ### Installation ```bash npm install @tevm/block ``` ### API Reference #### Core Classes * [Block](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/classes/Block.md) * [BlockHeader](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/classes/BlockHeader.md) * [ClRequest](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/classes/ClRequest.md) #### Interfaces * [BlockData](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/BlockData.md) * [BlockOptions](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/BlockOptions.md) * [HeaderData](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/HeaderData.md) * [JsonBlock](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/JsonBlock.md) * [JsonHeader](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/JsonHeader.md) * [JsonRpcBlock](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/JsonRpcBlock.md) #### Verkle Payload Types Payload shapes only; Tevm does not execute Verkle/EIP-6800 state-witness blocks. * [VerkleExecutionWitness](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/VerkleExecutionWitness.md) * [VerkleProof](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/VerkleProof.md) * [VerkleStateDiff](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/interfaces/VerkleStateDiff.md) #### Block Types * [BlockBodyBytes](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/type-aliases/BlockBodyBytes.md) * [BlockBytes](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/type-aliases/BlockBytes.md) * [BlockHeaderBytes](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/type-aliases/BlockHeaderBytes.md) * [ExecutionPayload](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/type-aliases/ExecutionPayload.md) * [BeaconPayloadJson](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/type-aliases/BeaconPayloadJson.md) #### Utility Functions * [blockFromRpc](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/functions/blockFromRpc.md) * [executionPayloadFromBeaconPayload](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/functions/executionPayloadFromBeaconPayload.md) * [getDifficulty](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/functions/getDifficulty.md) * [valuesArrayToHeaderData](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/functions/valuesArrayToHeaderData.md) ### Usage Examples ```typescript import { Block } from '@tevm/block' import { createCommon } from '@tevm/common' import { mainnet } from 'viem/chains' import { EthjsAddress } from '@tevm/utils' const common = createCommon({ ...mainnet }) const block = new Block({ common }) const fromData = Block.fromBlockData({ header: { parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000', uncleHash: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', coinbase: EthjsAddress.fromString('0x0000000000000000000000000000000000000000'), stateRoot: '0x0000000000000000000000000000000000000000000000000000000000000000', transactionsTrie: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', receiptTrie: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', difficulty: 0n, number: 0n, gasLimit: 30000000n, gasUsed: 0n, timestamp: BigInt(Math.floor(Date.now() / 1000)), baseFeePerGas: 1000000000n, }, }, { common }) const hash = block.hash() const serialized = block.serialize() const json = block.toJSON() const fromRlp = Block.fromRLPSerializedBlock(serialized, { common }) const fromValues = Block.fromValuesArray(block.raw(), { common }) ``` ### See Also * [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/) * [Block Structure](https://ethereum.org/en/developers/docs/blocks/) * [Full API Reference](https://github.com/evmts/tevm-monorepo/tree/main/packages/block/docs/globals.md) ## @tevm/blockchain Tevm's block and chain-state implementation. Manages local blocks, fork-backed lookup, canonical heads, iterator heads, and chain validation using Tevm block types and ZEVM-compatible primitives. Full API: [packages/blockchain/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/blockchain/docs). ### Installation ```bash npm install @tevm/blockchain ``` ### API Reference #### `Chain` Main blockchain interface. Methods: * `putBlock(block)` - add a block * `delBlock(blockHash)` - delete a block and its children * `getBlock(blockId)` - lookup by hash or number * `getBlockByTag(blockTag)` - lookup by tag (`latest`, `earliest`, etc.) * `getCanonicalHeadBlock()` - latest block on canonical chain * `validateHeader(header)` - header validation * `iterator(name, onBlock, maxBlocks?)` - iterate blocks * `getIteratorHead(name)` / `setIteratorHead(name, hash)` - iterator head management * `deepCopy()` / `shallowCopy()` - copy helpers for snapshots and simulations #### `ChainOptions` ```typescript type ChainOptions = { common: Common loggingLevel?: LogLevel genesisBlock?: Block genesisStateRoot?: Uint8Array fork?: { transport: { request: EIP1193RequestFn } blockTag?: BlockTag | bigint | `0x${string}` } } ``` #### Functions * `createChain(options): Promise` - create a new chain. * `createBaseChain(options): BaseChain` - internal implementation used by `createChain`. #### Errors `BlockNotFoundError`, `InvalidBlockError`, `InvalidHeaderError`, `InvalidChainError`. ### Usage Examples ```typescript import { createChain } from '@tevm/blockchain' import { createCommon, mainnet } from '@tevm/common' import { http } from 'viem' const chain = await createChain({ common: createCommon({ ...mainnet }), loggingLevel: 'debug', fork: { transport: { request: http('https://mainnet.infura.io/v3/YOUR-KEY') }, blockTag: 'latest', }, }) await chain.putBlock(block) const head = await chain.getCanonicalHeadBlock() const byNumber = await chain.getBlock(1234n) const latest = await chain.getBlockByTag('latest') await chain.validateHeader(block.header) await chain.delBlock(block.hash()) await chain.iterator('vm', async (block, reorg) => { if (reorg) console.log('reorg') console.log(block.header.number) }, 1000) ``` ### See Also * [VM & Submodules](../api/vm-and-submodules) * [@tevm/block](https://tevm.sh/reference/tevm/block/) * [@tevm/common](https://tevm.sh/reference/tevm/common/) * [Full API Reference](https://github.com/evmts/tevm-monorepo/tree/main/packages/blockchain/docs/globals.md) ## Bundler Plugins Tevm bundler plugins enable direct Solidity imports in TypeScript/JavaScript, compiling them into type-safe Tevm Contract instances at build time. :::note The bundler is optional. You can also generate contract types with `npx tevm gen` (see [Codegen Approach](#codegen-approach)). ::: ### Available Plugins | Bundler | Plugin Import Path | Repository | | ------- | ----------------------------- | ------------------------------------------------------------------------------------------------- | | Vite | `tevm/bundler/vite-plugin` | [@tevm/vite-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/vite) | | Webpack | `tevm/bundler/webpack-plugin` | [@tevm/webpack-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/webpack) | | Rollup | `tevm/bundler/rollup-plugin` | [@tevm/rollup-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/rollup) | | ESBuild | `tevm/bundler/esbuild-plugin` | [@tevm/esbuild-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/esbuild) | | Bun | `tevm/bundler/bun-plugin` | [@tevm/bun-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/bun) | | Rspack | `tevm/bundler/rspack-plugin` | [@tevm/rspack-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/rspack) | All plugins share a configuration interface. ### Prerequisites & Key Points * **Optional**: `npx tevm gen` works without any bundler integration. * **TypeScript**: Add `@tevm/ts-plugin` to `tsconfig.json` for editor support (NatSpec hovers, go-to-definition). * **`.s.sol` for bytecode**: regular `.sol` produces ABI only; `.s.sol` includes deployable bytecode. * **Cache**: artifacts go in `.tevm/` — add it to `.gitignore`. * **Foundry/remappings**: configure in `tevm.config.json`. * **Next.js**: type-checker conflicts; use [codegen](#codegen-approach) or disable `next.config` typechecks. ### Plugin Configuration ```ts type PluginOptions = { solc?: '0.8.19' | '0.8.20' | /* other supported solc versions */ } ``` Global settings (Foundry, libs, remappings) live in `tevm.config.json`. ### Bundler-Specific Setup :::code-group ```ts [vite.config.ts] import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { vitePluginTevm } from 'tevm/bundler/vite-plugin' export default defineConfig({ plugins: [react(), vitePluginTevm({ solc: '0.8.20' })], }) ``` ```js [webpack.config.js] const { webpackPluginTevm } = require('tevm/bundler/webpack-plugin') module.exports = { plugins: [webpackPluginTevm({ solc: '0.8.20' })], } ``` ```js [rollup.config.js] import { rollupPluginTevm } from 'tevm/bundler/rollup-plugin' export default { plugins: [rollupPluginTevm({ solc: '0.8.20' })], } ``` ```js [build.js (esbuild)] const { build } = require('esbuild') const { esbuildPluginTevm } = require('tevm/bundler/esbuild-plugin') build({ entryPoints: ['src/index.js'], outdir: 'dist', bundle: true, plugins: [esbuildPluginTevm({ solc: '0.8.20' })], }) ``` ```ts [plugins.ts (bun)] import { plugin } from 'bun' import { tevmBunPlugin } from 'tevm/bundler/bun-plugin' plugin(tevmBunPlugin({ solc: '0.8.20' })) ``` ```js [rspack.config.js] const { rspackPluginTevm } = require('tevm/bundler/rspack-plugin') module.exports = { plugins: [rspackPluginTevm({ solc: '0.8.20' })], } ``` ::: For Bun, register the plugin in `bunfig.toml`: ```toml preload = ["./plugins.ts"] [test] preload = ["./plugins.ts"] ``` ### TypeScript Plugin ```json { "compilerOptions": { "plugins": [{ "name": "@tevm/ts-plugin" }] } } ``` In VSCode, switch to the workspace TypeScript version. ### How the Bundler Works All Tevm bundler plugins share a unified core (`@tevm/base-bundler`): * **Import detection & resolution**: scans `.sol` imports, merges tsconfig paths, foundry remappings, and `tevm.config.json`. * **Compilation**: runs solc on the dependency graph; `.s.sol` includes bytecode, `.sol` is ABI-only. * **Code generation**: emits a TS module exporting a Tevm Contract (with `.read`, `.write`, abi, optional bytecode). * **Caching**: stores results in `.tevm/`. * **LSP**: `@tevm/ts-plugin` references bundler outputs for IDE features. ### Configuration with tevm.config.json ```json { "foundryProject": true, "libs": ["./lib", "./node_modules"], "remappings": { "foo": "vendored/foo" }, "debug": false, "cacheDir": ".tevm", "jsonAsConst": ["**/*.abi.json"] } ``` | Option | Type | Description | | ---------------- | ------------------------ | ------------------------------------------------- | | `foundryProject` | `boolean \| string` | Enable Foundry (`true`) or path to `foundry.toml` | | `libs` | `string[]` | Library paths for Solidity imports | | `remappings` | `Record` | Custom import remappings | | `debug` | `boolean` | Extra debug logs and files in `.tevm` | | `cacheDir` | `string` | Artifact location (default: `.tevm`) | | `jsonAsConst` | `string \| string[]` | Glob patterns for `as const` JSON imports | ### Codegen Approach ```bash npx tevm gen ``` Generates `.ts` files next to each `.sol`. Use this when a bundler hook is impractical (e.g., Next.js with strict typechecking). ### Troubleshooting * **Red underlines on `.sol` imports**: ensure `@tevm/ts-plugin` is in tsconfig and you're using the workspace TS version. * **Next.js type errors**: disable typechecking or use codegen. * **File not found**: check libs/remappings; set `foundryProject: true` for Foundry projects. * **Stale cache**: delete `.tevm/`. ### Examples and Further Reading * [Examples](https://github.com/evmts/tevm-monorepo/tree/main/examples) - Vite, Next, Bun, ESBuild * [Tevm Contract Reference](/reference/contract) * [Wagmi + Tevm examples](https://github.com/evmts/tevm-monorepo/tree/main/examples) * [base-bundler globals](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/base-bundler/docs/globals.md) ## Tevm Contract Bundler Import Solidity files directly into TypeScript with type safety and IDE integration. :::note See the new [Bundler Reference](/reference/bundler/) — this page is preserved for backlink compatibility. ::: * **[Overview](/reference/bundler/overview)** - Introduction, key benefits, available plugins * **[Internals](/reference/bundler/internals)** - How the bundler works under the hood * **[Methods & Exports](/reference/bundler/methods)** - Key APIs for advanced usage * **[Troubleshooting](/reference/bundler/troubleshooting)** - Common issues and solutions ### What is the Tevm Bundler? At build time, the bundler reads `.sol` files, runs solc on the dependency graph, extracts ABI and (for `.s.sol`) bytecode, and emits a TypeScript module exporting a [Tevm Contract](/reference/contract) instance. For a quickstart, see [Bundler Quickstart](/getting-started/bundler). ### Coming Soon * **Inline Solidity with `sol` tag** - inline contracts via template literals * **CAIP-10 contract imports** - import contracts from any chain by standardized identifier See [Upcoming Features](/reference/bundler/overview#coming-soon-features). ## @tevm/common Chain-specific configuration. Combines viem chain definitions with a ZEVM-backed `Common` instance the VM uses for hardfork, EIP, consensus, and gas-parameter lookups. Includes custom crypto hooks such as KZG for blob transaction testing. Full API: [packages/common/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs). ### Installation ```bash npm install @tevm/common ``` ### API Reference #### Enumerations * [ConsensusAlgorithm](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/enumerations/ConsensusAlgorithm.md) * [ConsensusType](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/enumerations/ConsensusType.md) #### Interfaces * [CustomCrypto](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/interfaces/CustomCrypto.md) * [EvmStateManagerInterface](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/interfaces/EvmStateManagerInterface.md) * [StorageDump](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/interfaces/StorageDump.md) * [StorageRange](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/interfaces/StorageRange.md) #### Type Aliases * [AccountFields](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/type-aliases/AccountFields.md) * [CliqueConfig](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/type-aliases/CliqueConfig.md) * [Common](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/type-aliases/Common.md) * [CommonOptions](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/type-aliases/CommonOptions.md) * [Hardfork](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/type-aliases/Hardfork.md) * [MockKzg](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/type-aliases/MockKzg.md) #### Functions * [createCommon](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/functions/createCommon.md) * [createMockKzg](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/functions/createMockKzg.md) #### Supported Networks **Layer 1**: [mainnet](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/mainnet.md), [sepolia](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/sepolia.md), [goerli](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/goerli.md), [holesky](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/holesky.md). **Layer 2**: [arbitrum](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/arbitrum.md), [optimism](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/optimism.md), [base](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/base.md), [zksync](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/zksync.md), [polygon](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/polygon.md), [polygonZkEvm](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/polygonZkEvm.md). **Alternative**: [bsc](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/bsc.md), [avalanche](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/avalanche.md), [fantom](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/fantom.md), [gnosis](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/gnosis.md). **Dev**: [hardhat](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/hardhat.md), [foundry](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/foundry.md), [anvil](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/anvil.md), [localhost](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/variables/localhost.md). ### Usage Examples ```typescript import { createCommon, createMockKzg, mainnet, optimism, arbitrum } from '@tevm/common' const common = createCommon({ ...mainnet, hardfork: 'shanghai' }) const custom = createCommon({ name: 'Custom Chain', id: 1337, defaultHardfork: 'shanghai', consensus: { type: 'poa', algorithm: 'clique', clique: { period: 15, epoch: 30000 }, }, }) const withKzg = createCommon({ ...mainnet, customCrypto: { kzg: createMockKzg() }, }) const optimismCommon = createCommon({ ...optimism, hardfork: 'bedrock' }) const arbitrumCommon = createCommon({ ...arbitrum, hardfork: 'nitro' }) ``` The returned `Common` is a viem chain object plus: * `ethjsCommon` - ZEVM-backed Common instance used internally by the VM. * `copy()` - isolated copy for a new VM or client. ### See Also * [VM & Submodules](../api/vm-and-submodules) * [Viem Chains](https://viem.sh/docs/chains/introduction.html) * [Full API Reference](https://github.com/evmts/tevm-monorepo/tree/main/packages/common/docs/globals.md) ## @tevm/contract Type-safe contract interactions, deployments, events, and pre-built ERC20/ERC721 contracts. Full API: [packages/contract/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs). ### Installation ```bash npm install @tevm/contract ``` ### API Reference #### Type Aliases * [Contract](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/Contract.md) * [CreateContractFn](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/CreateContractFn.md) * [CreateContractParams](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/CreateContractParams.md) * [EventActionCreator](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/EventActionCreator.md) * [MaybeExtractEventArgsFromAbi](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/MaybeExtractEventArgsFromAbi.md) * [ReadActionCreator](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/ReadActionCreator.md) * [ValueOf](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/ValueOf.md) * [WriteActionCreator](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/type-aliases/WriteActionCreator.md) #### Functions * [createContract](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/functions/createContract.md) #### Pre-built Contracts * [ERC20](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/variables/ERC20.md) * [ERC721](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/variables/ERC721.md) * [SimpleContract](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/variables/SimpleContract.md) ### Usage Examples ```typescript import { createContract, ERC20, ERC721 } from '@tevm/contract' const contract = createContract({ humanReadableAbi: [ 'function balanceOf(address) view returns (uint256)', 'function transfer(address to, uint256 amount) returns (bool)', 'event Transfer(address indexed from, address indexed to, uint256 value)', ] as const, name: 'MyToken', }) const readAction = contract.read.balanceOf('0x...') const writeAction = contract.write.transfer('0x...', 1000n) const token = contract.withAddress('0x1234...') const balance = token.read.balanceOf('0x...') const erc20 = ERC20.withAddress('0x...') const nameAction = erc20.read.name() const transferAction = erc20.write.transfer('0x...', 1000n) const nft = ERC721.withAddress('0x...') const ownerAction = nft.read.ownerOf(1n) ``` #### Deployless Scripts ```typescript import { ERC20 } from '@tevm/contract' const script = ERC20.script({ bytecode: '0x...', args: ['MyToken', 'MTK', 1000000n], }) const name = await client.contract(script.read.name()) ``` #### Event Filters ```typescript const filter = contract.events.Transfer({ fromBlock: 'latest' }) client.watchEvent(filter, (event) => { console.log(event.args.from, event.args.to, event.args.value) }) ``` ### Best Practices Use `as const` with ABIs for full type inference: ```typescript const abi = ['function example(uint256 value) returns (bool)'] as const const contract = createContract({ humanReadableAbi: abi, name: 'Example' }) ``` Set explicit gas for writes that need it: ```typescript const tx = contract.write.complexOperation('0x...', { gas: 500000n, maxFeePerGas: 30000000000n, }) ``` ### Exported Types ```typescript import type { Contract, CreateContractFn, CreateContractParams, EventActionCreator, ReadActionCreator, WriteActionCreator, } from '@tevm/contract' type MyContract = Contract ``` ### See Also * [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) * [ERC-721](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) * [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/) * [Full API Reference](https://github.com/evmts/tevm-monorepo/tree/main/packages/contract/docs/globals.md) ## @tevm/decorators Action decorators for extending Tevm clients: EIP-1193 providers, Ethereum JSON-RPC methods, and Tevm-specific actions. Full API: [packages/decorators/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/decorators/docs). ### Installation ```bash npm install @tevm/decorators ``` ### API Reference #### Core Functions * [`ethActions()`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/functions/ethActions.md) - standard Ethereum JSON-RPC methods. * [`requestEip1193()`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/functions/requestEip1193.md) - EIP-1193 provider extension. * [`tevmActions()`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/functions/tevmActions.md) - Tevm-specific actions. * [`tevmSend()`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/functions/tevmSend.md) - send Tevm JSON-RPC requests. #### Provider Types * [`Eip1193RequestProvider`](https://github.com/evmts/tevm-monorepo/tree/main/packages/decorators/docs/type-aliases/Eip1193RequestProvider.md) * [`EIP1193Parameters`](https://github.com/evmts/tevm-monorepo/tree/main/packages/decorators/docs/type-aliases/EIP1193Parameters.md) * [`EIP1193RequestFn`](https://github.com/evmts/tevm-monorepo/tree/main/packages/decorators/docs/type-aliases/EIP1193RequestFn.md) * [`EIP1193RequestOptions`](https://github.com/evmts/tevm-monorepo/tree/main/packages/decorators/docs/type-aliases/EIP1193RequestOptions.md) #### Action Types * [`EthActionsApi`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/EthActionsApi.md) * [`TevmActionsApi`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/TevmActionsApi.md) * [`TevmSendApi`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/TevmSendApi.md) #### RPC Schema Types * [`RpcSchema`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/RpcSchema.md) * [`RpcSchemaOverride`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/RpcSchemaOverride.md) * [`DerivedRpcSchema`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/DerivedRpcSchema.md) * [`JsonRpcSchemaPublic`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/JsonRpcSchemaPublic.md) * [`JsonRpcSchemaTevm`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/JsonRpcSchemaTevm.md) * [`JsonRpcSchemaWallet`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/JsonRpcSchemaWallet.md) * [`TestRpcSchema`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/TestRpcSchema.md) #### Ethereum Types * [`AddEthereumChainParameter`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/AddEthereumChainParameter.md) * [`WatchAssetParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/WatchAssetParams.md) * [`WalletPermission`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/WalletPermission.md) * [`WalletPermissionCaveat`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/WalletPermissionCaveat.md) #### Utility Types * [`Hash`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/Hash.md) * [`LogTopic`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/LogTopic.md) * [`NetworkSync`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/NetworkSync.md) * [`Quantity`](https://github.com/evmts/tevm-monorepo/blob/main/packages/decorators/docs/type-aliases/Quantity.md) ### Usage Examples ```typescript import { requestEip1193, ethActions, tevmActions } from '@tevm/decorators' const eip1193Client = createClient({ transport: requestEip1193() }) const result = await eip1193Client.request({ method: 'eth_call', params: [{ to: '0x...', data: '0x...' }], }) const ethClient = createClient({ transport: ethActions() }) const balance = await ethClient.eth.getBalance({ address: '0x...' }) const code = await ethClient.eth.getCode({ address: '0x...' }) const tevmClient = createClient({ transport: tevmActions() }) const call = await tevmClient.transport.tevm.call({ to: '0x...', data: '0x...' }) const state = await tevmClient.transport.tevm.dumpState() ``` #### Error Codes ```typescript try { await client.request({ method: 'eth_call', params: [{ to: '0x...', data: '0x...' }] }) } catch (error) { if (error.code === 4001) { // user rejected } else if (error.code === -32000) { // execution error } } ``` #### Chain Parameters ```typescript const chainParams: AddEthereumChainParameter = { chainId: '0x1', chainName: 'Ethereum Mainnet', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: ['https://...'], } ``` ### See Also * [JSON-RPC API](/api/json-rpc) * [Actions Guide](/reference/actions) * [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) * [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) ## @tevm/evm Tevm's low-level EVM facade. Backed by `@evmts/zevm/evm` and wrapped with Tevm-specific defaults for state, blockchain, precompiles, predeploys, logging, profiling, and unlimited-contract-size testing. Most apps should use `createMemoryClient`, viem actions, or JSON-RPC. Use `@tevm/evm` directly when building Tevm extensions, debuggers, or profilers. Full API: [packages/evm/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs). ### Installation ```bash npm install @tevm/evm ``` ### API Reference * [Evm](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs/classes/Evm.md) - EVM facade. * [createEvm](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs/functions/createEvm.md) - creates an EVM for a Tevm VM. * [CreateEvmOptions](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs/type-aliases/CreateEvmOptions.md) * [CustomPrecompile](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs/type-aliases/CustomPrecompile.md) * [EVMOpts](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs/type-aliases/EVMOpts.md) - ZEVM EVM options exposed through Tevm. * Re-exported ZEVM EVM types: `EvmError`, `EVMError`, `EvmResult`, `EvmRunCallOpts`, `ExecResult`, `InterpreterStep`, `Log`, `EthjsMessage`, `PrecompileInput`. ### Usage Examples ```typescript import { createChain } from '@tevm/blockchain' import { mainnet } from '@tevm/common' import { createEvm } from '@tevm/evm' import { createStateManager } from '@tevm/state' import { createAddress } from '@tevm/address' const common = mainnet.copy() const stateManager = createStateManager({ common }) const blockchain = await createChain({ common }) const evm = await createEvm({ common, stateManager, blockchain, profiler: false, loggingLevel: 'warn', }) const result = await evm.runCall({ to: createAddress('0x1234567890123456789012345678901234567890'), caller: createAddress('0x5678901234567890123456789012345678901234'), data: new Uint8Array(), value: 1000n, gasLimit: 100000n, }) evm.events?.on('step', (step) => { console.log({ pc: step.pc, opcode: step.opcode.name, gasLeft: step.gasLeft, depth: step.depth }) }) ``` #### Custom Precompiles ```typescript import { createContract, createMemoryClient, defineCall, definePrecompile, parseAbi } from 'tevm' const Calculator = createContract({ name: 'Calculator', address: '0x0000000000000000000000000000000000000a11', abi: parseAbi(['function addTwo(uint256 value) returns (uint256)']), }) const calculatorPrecompile = definePrecompile({ contract: Calculator, call: defineCall(Calculator.abi, { addTwo: async ({ args }) => ({ returnValue: args[0] + 2n, executionGasUsed: 0n }), }), }) const client = createMemoryClient({ customPrecompiles: [calculatorPrecompile.precompile()], }) ``` ### See Also * [VM & Submodules](../api/vm-and-submodules) * [EVM Events](../api/evm-events) * [Custom Precompiles](../advanced/custom-precompiles) * [@tevm/state](https://tevm.sh/reference/tevm/state/) * [Full API Reference](https://github.com/evmts/tevm-monorepo/tree/main/packages/evm/docs/globals.md) ## Package Reference Documentation * [@tevm/actions](/reference/actions) - Core action handlers for interacting with the EVM * [@tevm/address](/reference/address) - Ethereum address utilities * [@tevm/block](/reference/block) - Block creation and manipulation * [@tevm/blockchain](/reference/blockchain) - Blockchain data structures and utilities * [@tevm/common](/reference/common) - Shared utilities and constants * [@tevm/contract](/reference/contract) - Library for using contracts in typesafe way * [Contract Bundler](/reference/bundler) - Import Solidity files directly in TypeScript * [@tevm/decorators](/reference/decorators) - Extension decorators * [@tevm/evm](/reference/evm) - Core EVM implementation * [@tevm/memory-client](/reference/memory-client) - Batteries included viem client along with tree shakable actions * [@tevm/receipt-manager](/reference/receipt-manager) - Transaction receipt handling * [@tevm/state](/reference/state) - State management utilities * [@tevm/tx](/reference/tx) - Transaction handling utilities * [@tevm/txpool](/reference/txpool) - Transaction pool management * [@tevm/utils](/reference/utils) - General utility functions * [@tevm/vm](/reference/vm) - Virtual Machine implementation ## @tevm/memory-client In-memory Ethereum client combining viem with Tevm's EVM. Supports forking, manual/auto mining, and Tevm-specific actions on top of standard viem public, wallet, and test actions. ### Installation ```bash npm install @tevm/memory-client ``` ### API Reference #### Type Aliases * [CreateMemoryClientFn](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/CreateMemoryClientFn.md) * [MemoryClient](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/MemoryClient.md) * [MemoryClientOptions](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/MemoryClientOptions.md) * [TevmActions](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/TevmActions.md) * [TevmContract](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/TevmContract.md) * [TevmRpcSchema](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/TevmRpcSchema.md) * [TevmTransport](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/TevmTransport.md) * [TevmViemActionsApi](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/type-aliases/TevmViemActionsApi.md) #### Actions * [createClient](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/createClient.md) * [createMemoryClient](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/createMemoryClient.md) * [createTevmTransport](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/createTevmTransport.md) * [publicActions](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/publicActions.md) * [testActions](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/testActions.md) * [tevmCall](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmCall.md) * [tevmContract](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmContract.md) * [tevmDeal](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmDeal.md) - add native ETH or ERC20 tokens. * [tevmDeploy](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmDeploy.md) * [tevmDumpState](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmDumpState.md) * [tevmGetAccount](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmGetAccount.md) * [tevmLoadState](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmLoadState.md) * [tevmMine](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmMine.md) * [tevmReady](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmReady.md) * [tevmSetAccount](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmSetAccount.md) * [tevmViemActions](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/tevmViemActions.md) * [walletActions](https://github.com/evmts/tevm-monorepo/tree/main/packages/memory-client/docs/functions/walletActions.md) ### Client Options ```typescript interface MemoryClientOptions { common?: TCommon fork?: { transport: Transport blockTag?: string | number } name?: string account?: TAccountOrAddress pollingInterval?: number cacheTime?: number } ``` ### Usage Examples ```typescript import { createMemoryClient, http, PREFUNDED_ACCOUNTS } from 'tevm' import { optimism } from 'tevm/common' const rpcUrl = process.env.OPTIMISM_RPC_URL if (!rpcUrl) { throw new Error('OPTIMISM_RPC_URL is required for fork mode') } const client = createMemoryClient({ fork: { transport: http(rpcUrl)({}), blockTag: 'latest', }, common: optimism, }) const manualMining = createMemoryClient({ miningConfig: { type: 'manual' }, account: PREFUNDED_ACCOUNTS[0], }) await manualMining.mine({ blocks: 1 }) await client.setAccount({ address: '0x1234567890123456789012345678901234567890', balance: 100n, nonce: 1n, state: { '0x0000000000000000000000000000000000000000000000000000000000000000': '0x0000000000000000000000000000000000000000000000000000000000000001', }, }) const account = await client.getAccount({ address: '0x1234567890123456789012345678901234567890', returnStorage: true, }) const deployResult = await client.tevmDeploy({ abi: contractAbi, bytecode: contractBytecode, args: ['Constructor', 'Args'], }) const callResult = await client.tevmCall({ to: contractAddress, data: encodeFunctionData(/* ... */), }) const contractResult = await client.tevmContract({ contract: myContract, method: 'myMethod', args: [arg1, arg2], }) const hash = await client.writeContract({ address: contractAddress, abi: contractAbi, functionName: 'myFunction', args: [arg1, arg2], }) const receipt = await client.waitForTransactionReceipt({ hash }) ``` #### State Persistence ```typescript import { createMemoryClient, createSyncPersister } from '@tevm/memory-client' const client = createMemoryClient({ persister: createSyncPersister({ storage: localStorage }), }) ``` #### HTTP Server Integration ```typescript import { createServer } from '@tevm/server' import { createMemoryClient } from '@tevm/memory-client' const server = createServer({ request: createMemoryClient().request }) server.listen(8545) ``` ### Network Support Officially supported: Ethereum mainnet, standard OP Stack chains. Other EVM chains may work but aren't officially supported. ### EIP Support Always enabled: EIP-1559, EIP-4895, EIP-4844, EIP-4788. ### See Also * [What is Tevm Node?](../introduction/what-is-tevm-node) * [Creating a Node](../core/create-tevm-node) * [Viem Documentation](https://viem.sh) ## @tevm/node API Reference Full API on GitHub: [packages/node/docs/globals.md](https://github.com/evmts/tevm-monorepo/tree/main/packages/node/docs/globals.md). * [Type Aliases](https://github.com/evmts/tevm-monorepo/tree/main/packages/node/docs/type-aliases) * [Classes](https://github.com/evmts/tevm-monorepo/tree/main/packages/node/docs/classes) * [Functions](https://github.com/evmts/tevm-monorepo/tree/main/packages/node/docs/functions) * [Variables](https://github.com/evmts/tevm-monorepo/tree/main/packages/node/docs/variables) ## @tevm/receipt-manager ZEVM-backed transaction receipt storage and lookup. Powers Tevm's `eth_getTransactionReceipt`, `eth_getBlockReceipts`, `eth_getLogs`, filter, and subscription flows. ### Installation ```bash npm install @tevm/receipt-manager ``` ### API #### ReceiptsManager ```typescript import { ReceiptsManager } from '@tevm/receipt-manager' const receiptsManager = new ReceiptsManager(mapDb, chain) ``` Methods: * `saveReceipts(block, receipts)` * `getReceipts(blockHash)` * `getReceiptByTxHash(txHash)` * `getLogs(from, to, addresses?, topics?)` * `deleteReceipts(block)` #### Receipt Types ```typescript interface BaseTxReceipt { cumulativeBlockGasUsed: bigint bitvector: Uint8Array logs: Log[] } interface PreByzantiumTxReceipt extends BaseTxReceipt { stateRoot: Uint8Array } interface PostByzantiumTxReceipt extends BaseTxReceipt { status: 0 | 1 } interface EIP4844BlobTxReceipt extends PostByzantiumTxReceipt { blobGasUsed: bigint blobGasPrice: bigint } type TxReceipt = PreByzantiumTxReceipt | PostByzantiumTxReceipt | EIP4844BlobTxReceipt ``` #### MapDb ```typescript import { createMapDb } from '@tevm/receipt-manager' const mapDb = createMapDb({ cache: new Map() }) ``` ```typescript interface MetaDBManagerOptions { cache: Map<`0x${string}`, Uint8Array> } ``` #### Limits ```typescript GET_LOGS_LIMIT = 10000 GET_LOGS_LIMIT_MEGABYTES = 150 GET_LOGS_BLOCK_RANGE_LIMIT = 2500 ``` ### Usage Examples ```typescript await receiptsManager.saveReceipts(block, receipts) const receipts = await receiptsManager.getReceipts(blockHash) const receipt = await receiptsManager.getReceiptByTxHash(txHash) const logs = await receiptsManager.getLogs(fromBlock, toBlock, addresses, topics) ``` #### DbType ```typescript type DbType = 'Receipts' | 'TxHash' | 'SkeletonBlock' | 'SkeletonBlockHashToNumber' | 'SkeletonStatus' | 'SkeletonUnfinalizedBlockByHash' | 'Preimage' ``` ## @tevm/rlp API Reference ZEVM-backed Recursive Length Prefix encoding utilities through Tevm's package namespace. Full API on GitHub: [packages/rlp/docs/globals.md](https://github.com/evmts/tevm-monorepo/tree/main/packages/rlp/docs/globals.md). * [Type Aliases](https://github.com/evmts/tevm-monorepo/tree/main/packages/rlp/docs/type-aliases) * [Classes](https://github.com/evmts/tevm-monorepo/tree/main/packages/rlp/docs/classes) * [Functions](https://github.com/evmts/tevm-monorepo/tree/main/packages/rlp/docs/functions) * [Variables](https://github.com/evmts/tevm-monorepo/tree/main/packages/rlp/docs/variables) ## @tevm/state State management for Tevm. Handles account state (balance, nonce, code, storage), state transitions/checkpoints, caching, persistence, and forked chain state. ### Installation ```bash npm install @tevm/state ``` ### API Reference #### Enumerations * [CacheType](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/enumerations/CacheType.md) #### State Management * [StateManager](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/interfaces/StateManager.md) * [BaseState](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/BaseState.md) * [TevmState](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/TevmState.md) * [StateAction](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/StateAction.md) * [StateOptions](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/StateOptions.md) * [StateRoots](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/StateRoots.md) * [ParameterizedTevmState](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/ParameterizedTevmState.md) #### Storage Types * [AccountStorage](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/interfaces/AccountStorage.md) * [ParameterizedAccountStorage](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/interfaces/ParameterizedAccountStorage.md) * [SerializableTevmState](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/SerializableTevmState.md) * [ForkOptions](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/interfaces/ForkOptions.md) #### Caching * [StateCache](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/type-aliases/StateCache.md) * [AccountCache](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/classes/AccountCache.md) * [ContractCache](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/classes/ContractCache.md) * [StorageCache](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/classes/StorageCache.md) #### State Creation and Management * [createStateManager](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/createStateManager.md) * [createBaseState](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/createBaseState.md) * [deepCopy](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/deepCopy.md) * [shallowCopy](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/shallowCopy.md) #### State Operations * [getAccount](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getAccount.md) * [putAccount](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/putAccount.md) * [deleteAccount](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/deleteAccount.md) * `getCode` ([getContractCode](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getContractCode.md)) * `putCode` ([putContractCode](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/putContractCode.md)) * `getStorage` ([getContractStorage](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getContractStorage.md)) * `putStorage` ([putContractStorage](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/putContractStorage.md)) * `clearStorage` * [getAccountAddresses](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getAccountAddresses.md) * [getAppliedKey](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getAppliedKey.md) * [modifyAccountFields](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/modifyAccountFields.md) #### State Root Management * [getStateRoot](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getStateRoot.md) * [setStateRoot](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/setStateRoot.md) * [hasStateRoot](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/hasStateRoot.md) #### Checkpointing * [checkpoint](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/checkpoint.md) * [commit](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/commit.md) * [revert](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/revert.md) #### Cache Management * [clearCaches](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/clearCaches.md) * [originalStorageCache](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/originalStorageCache.md) #### Genesis and Forking * [dumpCanonicalGenesis](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/dumpCanonicalGenesis.md) * [generateCanonicalGenesis](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/generateCanonicalGenesis.md) * [getForkBlockTag](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getForkBlockTag.md) * [getForkClient](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getForkClient.md) #### Storage Operations * [dumpStorage](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/dumpStorage.md) * [dumpStorageRange](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/dumpStorageRange.md) * [getProof](https://github.com/evmts/tevm-monorepo/tree/main/packages/state/docs/functions/getProof.md) `getProof` requires fork mode because local state managers do not merkleize state. ### Usage Examples ```ts import { createAddress } from 'tevm/address' import { createStateManager } from 'tevm/state' import { createAccount, hexToBytes } from 'tevm/utils' const stateManager = createStateManager({ loggingLevel: 'info' }) await stateManager.ready() const address = createAddress('0x1234567890123456789012345678901234567890') await stateManager.putAccount(address, createAccount({ nonce: 0n, balance: 100n })) await stateManager.modifyAccountFields(address, { nonce: 1n, balance: 200n }) const account = await stateManager.getAccount(address) const key = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000001') await stateManager.putCode(address, new Uint8Array([1, 2, 3])) const code = await stateManager.getCode(address) await stateManager.putStorage(address, key, new Uint8Array([2])) const value = await stateManager.getStorage(address, key) await stateManager.clearStorage(address) ``` ## @tevm/trie API Reference ZEVM-backed Merkle Patricia Trie utilities through Tevm's package namespace. Full API on GitHub: [packages/trie/docs/globals.md](https://github.com/evmts/tevm-monorepo/tree/main/packages/trie/docs/globals.md). * [Type Aliases](https://github.com/evmts/tevm-monorepo/tree/main/packages/trie/docs/type-aliases) * [Classes](https://github.com/evmts/tevm-monorepo/tree/main/packages/trie/docs/classes) * [Functions](https://github.com/evmts/tevm-monorepo/tree/main/packages/trie/docs/functions) * [Variables](https://github.com/evmts/tevm-monorepo/tree/main/packages/trie/docs/variables) ## @tevm/tx Tevm's transaction facade backed by `@evmts/zevm/tx`. Exports typed Ethereum transaction constructors, helpers, type guards, and Tevm's impersonated transaction helper. Full API: [packages/tx/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/tx/docs). ### Installation ```bash npm install @tevm/tx ``` ### API Reference #### Transaction Types * [LegacyTransaction](https://github.com/evmts/tevm-monorepo/tree/main/packages/tx/docs/classes/LegacyTransaction.md) * [AccessListEIP2930Transaction](https://github.com/evmts/tevm-monorepo/tree/main/packages/tx/docs/classes/AccessListEIP2930Transaction.md) * [FeeMarketEIP1559Transaction](https://github.com/evmts/tevm-monorepo/tree/main/packages/tx/docs/classes/FeeMarketEIP1559Transaction.md) * [BlobEIP4844Transaction](https://github.com/evmts/tevm-monorepo/tree/main/packages/tx/docs/classes/BlobEIP4844Transaction.md) * `EOACodeEIP7702Transaction` - EIP-7702 EOA code transactions. * [ImpersonatedTx](https://github.com/evmts/tevm-monorepo/tree/main/packages/tx/docs/interfaces/ImpersonatedTx.md) - Tevm-specific tx that executes as an address without a private key. #### Helpers * `TransactionFactory` - alias for ZEVM's `createTx`. * `createTxFromRLP` - decode a serialized transaction. * `createTxFromBlockBodyData` - create from block body data. * `createImpersonatedTx` - unsigned EIP-1559-shaped tx that executes as `impersonatedAddress`. * `createEOACodeEIP7702Tx`, `createEOACodeEIP7702TxFromBytesArray`, `createEOACodeEIP7702TxFromRLP`. * Type guards: `isLegacyTx`, `isAccessListEIP2930Tx`, `isFeeMarketEIP1559Tx`, `isBlobEIP4844Tx`, `isEOACodeEIP7702Tx`. ### Creating Transactions ```typescript import { TransactionFactory, createImpersonatedTx, createTxFromRLP, isBlobEIP4844Tx, isEOACodeEIP7702Tx, } from '@tevm/tx' import { createAddress } from '@tevm/address' const tx = TransactionFactory({ nonce: 0n, gasLimit: 21000n, maxFeePerGas: 20_000_000_000n, maxPriorityFeePerGas: 2_000_000_000n, to: createAddress('0x1234567890123456789012345678901234567890'), value: 1_000_000_000_000_000_000n, }) const decoded = createTxFromRLP(tx.serialize()) if (isBlobEIP4844Tx(decoded) || isEOACodeEIP7702Tx(decoded)) { // typed transaction fields } ``` ### Impersonated Transactions Lets the VM execute as an address without a signature. For app-level impersonation, prefer JSON-RPC or MemoryClient helpers (`anvil_impersonateAccount`, `tevm_impersonateAccount`, `setImpersonatedAccount`). ```typescript import { createImpersonatedTx } from '@tevm/tx' import { createAddress } from '@tevm/address' const tx = createImpersonatedTx({ impersonatedAddress: createAddress('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'), to: createAddress('0x70997970c51812dc3a010c7d01b50e0d17dc79c8'), value: 1_000_000_000_000_000_000n, gasLimit: 21_000n, maxFeePerGas: 20_000_000_000n, maxPriorityFeePerGas: 2_000_000_000n, nonce: 0n, }) const vm = await node.getVm() const result = await vm.runTx({ tx, skipBalance: true, skipNonce: true }) ``` ### Transaction Coverage Legacy, EIP-2930, EIP-1559, EIP-4844, EIP-7702, and Tevm impersonated transactions. ### Common Methods `hash()`, `getBaseFee()`, `getDataFee()`, `getUpfrontCost()`, `isSigned()`, `serialize()`, `toJSON()`. ### See Also * [Transaction Pool](../advanced/txpool) * [VM & Submodules](../api/vm-and-submodules) * [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718), [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930), [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559), [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844), [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) ## @tevm/txpool Transaction pool facade backed by `@evmts/zevm/txpool`. Adds Tevm-specific fee classification for fee-market-shaped transactions, including EIP-7702 and impersonated transactions. Normally managed by `TevmNode`, `MemoryClient`, and the JSON-RPC layer. Use directly when building custom VM tooling or low-level pending-tx control. ### Installation ```bash npm install @tevm/txpool ``` ### API #### TxPool [`TxPool`](https://github.com/evmts/tevm-monorepo/blob/main/packages/txpool/src/TxPool.ts) extends the ZEVM txpool. ```ts import { TxPool, type TxPoolOptions } from '@tevm/txpool' const txPool = new TxPool({ vm, maxSize: 5000, maxPerSender: 100, } satisfies TxPoolOptions) ``` #### Options ```ts type TxPoolOptions = { vm: TxPoolVm maxSize?: number maxPerSender?: number } ``` * `vm` - VM facade with `blockchain`, `stateManager`, and `deepCopy()`. * `maxSize` - max transactions in the pool. * `maxPerSender` - max transactions per sender. #### Methods * `open()` / `close()` - open or clear the pool. * `start()` / `stop()` - background cleanup. * `add(tx, requireSignature?, skipBalance?)` - validate and add. * `addUnverified(tx)` - add without validation. * `getByHash(hashOrHashes)` - look up by hex hash, or many by byte hashes. * `removeByHash(hash)` - remove from all indexes. * `txsByPriceAndNonce({ baseFee, allowedBlobs })` - ordered for block building. * `cleanup()` - drop stale txs and sync indexes. * `deepCopy(options)` - copy with another VM/options. ### JSON-RPC Exposed via: * `txpool_content` * `txpool_contentFrom` * `txpool_inspect` * `txpool_status` Dev methods (`anvil_impersonateAccount`, `tevm_impersonateAccount`, `evm_mine`) interact with the same pending state. ### Transaction Support Legacy, EIP-2930, EIP-1559, EIP-4844, EIP-7702, and Tevm impersonated EIP-1559-shaped transactions. ### Related Packages * [@tevm/vm](./vm) - VM execution facade * [@tevm/tx](./tx) - transaction constructors and helpers * [@tevm/common](./common) - chain and hardfork configuration * [JSON-RPC](../api/json-rpc) ## Utilities & Addresses Lightweight utility functions and classes built on [`tevm/utils`](https://github.com/evmts/tevm-monorepo/tree/main/packages/utils), [`@evmts/zevm`](https://github.com/evmts/zevm), and [`viem`](https://viem.sh). ### createAddress Create an Ethereum address from various inputs. ```ts import { createAddress } from 'tevm/address' createAddress(`0x${'00'.repeat(20)}`) createAddress(420n) createAddress(new Uint8Array(20)) ``` #### Signature ```ts declare function createAddress( address: number | bigint | string | Uint8Array | EthjsAddress ): Address ``` Accepts `0x`-prefixed hex, unprefixed hex, numbers, bigints, `Uint8Array`s, or `EthjsAddress`. Throws `InvalidAddressError` on invalid input. ### Address Class Thin wrapper around `EthjsAddress`. ```ts import { Address } from 'tevm/address' const a = new Address(Uint8Array.from([ /* 20 bytes */ ])) a.bytes // raw 20-byte address a.toString() // '0x...' ``` ### createContractAddress CREATE address per [EIP-1014](https://eips.ethereum.org/EIPS/eip-1014): ```ts import { createContractAddress } from 'tevm/address' const contractAddr = createContractAddress(createAddress('0x1111...1111'), 1n) ``` Computes `keccak256(rlp([senderAddress, nonce]))[-20..]`. Throws `InvalidAddressError` if `from` is invalid. ### Common Errors * `InvalidAddressError` - bytes/string can't parse as a 20-byte address. * `UnreachableCodeError` - unexpected code path; usually wrapped into a more descriptive error. ### Other Re-exports Lower-level helpers from [`tevm/utils`](https://github.com/evmts/tevm-monorepo/tree/main/packages/utils), ZEVM, or [`viem`](https://viem.sh): * `hexToBytes(hex)` - hex to raw bytes (with optional size checks). * `keccak256(data, 'bytes' | 'hex')` - keccak256 hasher. * `encodeFunctionData(...)`, `toRlp(...)` - ABI/RLP encoders. * EIP-7702 helpers: `eoaCode7702SignAuthorization`, `eoaCode7702RecoverAuthority`, and authorization-list encoders. :::tip Prefer [`viem`](https://viem.sh) utilities for bytes conversion, hashing, and ABIs in browser/Node code. Tevm re-exports many for convenience. ::: ## @tevm/utils Full API: [packages/utils/docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/utils/docs). Combines ZEVM primitives, viem utilities, and Tevm helpers. ### Installation ```bash npm install @tevm/utils ``` ### Main Components #### Hex and Bytes Conversion ```typescript import { bytesToHex, hexToBytes, bytesToBigInt, bytesToNumber, hexToBigInt, hexToNumber, numberToHex, stringToHex, hexToString, } from '@tevm/utils' bytesToHex(new Uint8Array([1, 164])) // '0x01a4' hexToBytes('0x01a4') // Uint8Array([1, 164]) hexToNumber('0x01a4') // 420 numberToHex(420) // '0x1a4' hexToString('0x48656c6c6f') // 'Hello' stringToHex('Hello') // '0x48656c6c6f' ``` #### Type Checking ```typescript import { isHex, isBytes, isAddress } from '@tevm/utils' isHex('0x123') isBytes(new Uint8Array()) isAddress('0x123...') ``` #### Unit Conversion ```typescript import { formatEther, parseEther, formatGwei, parseGwei } from '@tevm/utils' formatEther(1000000000000000000n) // '1' parseEther('1.0') // 1000000000000000000n formatGwei(1000000000n) // '1' parseGwei('1.0') // 1000000000n ``` #### Cryptography ```typescript import { keccak256, ecrecover, ecsign, randomBytes } from '@tevm/utils' const hash = keccak256('0x1234') const signature = ecsign(messageHash, privateKey) const address = ecrecover(messageHash, v, r, s) const random = randomBytes(32) ``` #### ABI Encoding/Decoding ```typescript import { encodeAbiParameters, decodeAbiParameters, encodeFunctionData, decodeFunctionData, encodeEventTopics, decodeEventLog, } from '@tevm/utils' const data = encodeFunctionData({ abi: [...], functionName: 'transfer', args: [address, amount] }) const result = decodeFunctionData({ abi: [...], data: '0x...' }) const topics = encodeEventTopics({ abi: [...], eventName: 'Transfer', args: [from, to, null] }) ``` #### RLP ```typescript import { toRlp, fromRlp } from '@tevm/utils' const rlp = toRlp(['0x123', '0x456']) const decoded = fromRlp(rlp) ``` #### Memory Database ```typescript import { createMemoryDb } from '@tevm/utils' const db = createMemoryDb() const db2 = createMemoryDb(new Map()) ``` #### Event Emitter ```typescript import { AsyncEventEmitter } from '@tevm/utils' const emitter = new AsyncEventEmitter() emitter.on('event', async (data) => { /* ... */ }) await emitter.emit('event', data) ``` ### Types #### Basic ```typescript import type { Address, Hex, BlockTag, BlockNumber, BytesLike, BigIntLike } from '@tevm/utils' const address: Address = '0x...' const hex: Hex = '0x...' const blockTag: BlockTag = 'latest' ``` #### ABI ```typescript import type { Abi, AbiFunction, AbiEvent, AbiConstructor, ParseAbi, FormatAbi } from '@tevm/utils' type ParsedAbi = ParseAbi type FormattedAbi = FormatAbi ``` #### Contract ```typescript import type { ContractFunctionName, ContractConstructorArgs, ExtractAbiFunction, ExtractAbiEvent, } from '@tevm/utils' type TransferFunction = ExtractAbiFunction type TransferEvent = ExtractAbiEvent ``` ### Constants ```typescript import { GWEI_TO_WEI, KECCAK256_RLP, KECCAK256_RLP_ARRAY } from '@tevm/utils' GWEI_TO_WEI // 1000000000n ``` ### Error Handling ```typescript import { invariant } from '@tevm/utils' invariant(condition, 'Error message') ``` ### See Also * [Viem](https://viem.sh/docs/utilities/fromBytes) * [ZEVM](https://github.com/evmts/zevm) * [ABI Specification](https://docs.soliditylang.org/en/latest/abi-spec.html) ## @tevm/vm High-performance EVM implementation for Tevm. Handles bytecode execution, transaction processing, block building, and state transitions across supported hardforks and EIPs. ### Installation ```bash npm install @tevm/vm ``` ### API Reference #### Core Types * [`Vm`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/Vm.ts#L6) - core VM type. #### Block Building * [`BlockBuilder`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/BlockBuilder.ts) * [`BuildBlock`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/buildBlock.ts#L5) * [`BuildBlockOpts`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/BuildBlockOpts.ts) * [`BuildStatus`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/BuildStatus.ts) #### Transaction Processing * [`RunTx`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/runTx.ts) * [`RunTxOpts`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/RunTxOpts.ts) * [`RunTxResult`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/RunTxResult.ts) #### Block Processing * [`RunBlock`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/runBlock.ts) * [`RunBlockOpts`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/RunBlockOpts.ts) * [`RunBlockResult`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/RunBlockResult.ts) #### Events * [`AfterTxEvent`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/events/AfterTxEvent.ts) * [`AfterBlockEvent`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/events/AfterBlockEvent.ts) * [`VMEvents`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/utils/VMEvents.ts) #### Core Functions * [`createVm`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/createVm.js#L11) * [`deepCopy`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/deepCopy.js#L20) * [`applyBlock`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/applyBlock.ts#L24) * [`buildBlock`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/buildBlock.ts#L8) * [`genTxTrie`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/genTxTrie.ts#L6) * [`validateRunTx`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/validateRunTx.js#L10) * [`applyDAOHardfork`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/applyDAOHardfork.js) * [`execHardfork`](https://github.com/evmts/tevm-monorepo/blob/main/packages/vm/src/actions/execHardfork.js#L14) ### Usage Examples ```typescript import { createVm } from '@tevm/vm' import { Common } from '@tevm/common' const common = new Common({ chain: 'mainnet' }) const vm = createVm({ common }) const blockBuilder = await vm.buildBlock({ parentBlock: block, blockOpts: { /* options */ }, }) await blockBuilder.addTransaction(tx) const built = await blockBuilder.build() const blockResult = await vm.runBlock({ block: built }) const txResult = await vm.runTx({ tx }) console.log(txResult.gasUsed.toString(), txResult.execResult.returnValue) ``` ### Configuration ```typescript const vm = createVm({ common, stateManager, blockchain, activatePrecompiles: true, }) ``` ### Related Packages * [@tevm/state](./state) * [@tevm/common](./common) * [@tevm/blockchain](./blockchain) ## Tevm Contract Bundler Import Solidity files directly into TypeScript with type safety and IDE integration. :::note The bundler is optional. You can also generate contract types with `npx tevm gen` (see [Codegen Approach](/reference/bundler/troubleshooting#codegen-approach)). ::: * **[Overview](/reference/bundler/overview)** - Introduction, key benefits, available plugins * **[Internals](/reference/bundler/internals)** - How the bundler works under the hood * **[Methods & Exports](/reference/bundler/methods)** - Key APIs for advanced usage * **[Troubleshooting](/reference/bundler/troubleshooting)** - Common issues and solutions ### What is the Tevm Bundler? At build time, the bundler reads `.sol` files, runs solc on the dependency graph, extracts ABI and (for `.s.sol`) bytecode, and emits a TypeScript module exporting a [Tevm Contract](/reference/contract) instance. For a quickstart, see [Bundler Quickstart](/getting-started/bundler). ### Coming Soon * **Inline Solidity with `sol` tag** - inline contracts via template literals * **CAIP-10 contract imports** - import contracts from any chain by standardized identifier See [Upcoming Features](/reference/bundler/overview#coming-soon-features). ## Bundler Internals All Tevm bundler plugins share a unified core (`@tevm/base-bundler`). Each plugin adapts the same pipeline to its build tool. ### 1. Import Detection & Resolution For each `.sol` import the bundler: * Parses the import statement and converts relative paths to absolute paths. * Applies remappings from tsconfig paths, foundry remappings, and `tevm.config.json`. * Resolves node\_modules for npm packages. * Special-cases `.s.sol` for bytecode inclusion. ``` User Import → Node.js Resolution + Foundry Remappings → Absolute File Path ``` ### 2. Compilation * Builds a dependency graph of imported Solidity files. * Passes the graph to solc. * `.s.sol` emits ABI + bytecode; `.sol` emits ABI only. * Extracts metadata, NatSpec, and optionally the AST. ### 3. Code Generation * Emits a TypeScript (or JavaScript) module exporting a Tevm Contract instance. * Generates types for methods, events, and errors. * Maps Solidity types to TS (uint256 → bigint, address → string, etc.). * Preserves NatSpec as JSDoc for editor hovers. * Adds `.read` / `.write` method interfaces. ### 4. Caching * Stores compiled artifacts in `.tevm/`. * Hashes file content for invalidation. * Stores artifacts (ABI, bytecode) separately from generated code. * Invalidates only affected entries when deps or compiler settings change. ### 5. LSP & TS Plugin Integration * `@tevm/ts-plugin` hooks into the TypeScript compiler. * Provides type info for `.sol` imports from bundler outputs. * Powers auto-completion, go-to-definition, hover docs, and type checking. ### Internal Implementation Details #### Module Factory ```ts function moduleFactory(entry, source, remappings, libs) { const modules = new Map() const queue = [{ path: entry, source }] while (queue.length > 0) { const { path, source } = queue.shift() if (modules.has(path)) continue const imports = resolveImports(path, source, remappings, libs) modules.set(path, { id: path, source, imports: imports.map((i) => i.path) }) for (const imp of imports) { if (!modules.has(imp.path)) { queue.push({ path: imp.path, source: readFile(imp.path) }) } } } return modules } ``` #### Solidity Compiler Integration ```ts function compile(sources, settings) { const input = { language: 'Solidity', sources: Object.fromEntries( Array.from(sources.entries()).map(([path, { content }]) => [path, { content }]), ), settings: { outputSelection: { '*': { '*': ['abi', 'evm.bytecode', 'evm.deployedBytecode', 'metadata', 'userdoc', 'devdoc'] }, }, ...settings, }, } return solc.compile(JSON.stringify(input), { import: resolveImport }) } ``` #### Contract Instance Generation ```ts import { createContract } from 'tevm/contract' export const MyContract = createContract({ abi: [...], bytecode: '0x...', // only for .s.sol deployedBytecode: '0x...', // only for .s.sol }) ``` ### Further Reading * [Methods & Exports](/reference/bundler/methods) * [Troubleshooting](/reference/bundler/troubleshooting) ## Notable Methods & Exports Internal utilities for custom bundling workflows, debugging compilation, or extending Tevm. ### Core Bundler (`@tevm/base-bundler`) ```ts import { bundler } from '@tevm/base-bundler' import { createCache } from '@tevm/bundler-cache' import { createSolc } from '@tevm/solc' import { loadConfig } from '@tevm/config' const tevmBundler = bundler(config, console, fileAccessObj, solcInstance, cacheInstance) const { code, modules } = await tevmBundler.resolveTsModule( 'MyContract.s.sol', process.cwd(), false, // includeAst true, // includeBytecode ) ``` Available methods: * `resolveTsModule` - TypeScript output * `resolveEsmModule` - ESM output * `resolveCjsModule` - CommonJS output * `resolveDts` - TypeScript declarations Each has a `*Sync` variant. [Source: bundler.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/base-bundler/src/bundler.js) ### Import Resolution (`@tevm/resolutions`) ```ts import { moduleFactory, resolveImports } from '@tevm/resolutions' const imports = await resolveImports( '/path/to/Contract.sol', contractSourceCode, { '@openzeppelin/': 'node_modules/@openzeppelin/' }, ['lib', 'node_modules'], false, // async ) const modules = await moduleFactory( '/path/to/Contract.sol', contractSourceCode, remappings, libraryPaths, fileAccessObj, false, ) ``` * `resolveImports()` - parse Solidity imports into absolute paths. * `moduleFactory()` - build a dependency graph. [moduleFactory.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/resolutions/src/moduleFactory.js) | [resolveImports.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/resolutions/src/resolveImports.js) ### Compilation (`@tevm/compiler`) ```ts import { resolveArtifacts } from '@tevm/compiler' const { artifacts, modules, asts } = await resolveArtifacts( 'MyContract.s.sol', process.cwd(), console, config, true, // includeAst true, // includeBytecode fileAccessObj, solcInstance, ) ``` Also has `resolveArtifactsSync()`. [resolveArtifacts.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/compiler/src/resolveArtifacts.js) ### Caching (`@tevm/bundler-cache`) ```ts import { createCache } from '@tevm/bundler-cache' const cache = createCache('.tevm', fileAccessObj, process.cwd()) await cache.writeArtifacts(contractPath, compiledContracts) const artifacts = await cache.readArtifacts(contractPath) await cache.writeDts(contractPath, dtsContent) const dts = await cache.readDts(contractPath) ``` Methods: `readArtifacts`/`writeArtifacts`, `readDts`/`writeDts`, `readMjs`/`writeMjs`, `readCjs`/`writeCjs`. Each has a `*Sync` variant. [createCache.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/bundler-cache/src/createCache.js) ### Configuration (`@tevm/config`) ```ts import { loadConfig } from '@tevm/config' const config = await loadConfig(process.cwd()) config.remappings config.libs config.cacheDir ``` Merges Foundry remappings, tsconfig, and `tevm.config.json`. [loadConfig.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/config/src/loadConfig.js) ### Runtime (`@tevm/runtime`) ```ts import { generateTevmBody, generateTevmBodyDts } from '@tevm/runtime' const tsCode = generateTevmBody('MyContract', artifacts, 'tevm/contract', true) const dtsCode = generateTevmBodyDts('MyContract', artifacts, 'tevm/contract', true) ``` [generateTevmBody.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/runtime/src/generateTevmBody.js) | [generateTevmBodyDts.js](https://github.com/evmts/tevm-monorepo/blob/main/bundler-packages/runtime/src/generateTevmBodyDts.js) ### Creating a Custom Bundler ```ts import { loadConfig } from '@tevm/config' import { createSolc } from '@tevm/solc' import { createCache } from '@tevm/bundler-cache' import { bundler } from '@tevm/base-bundler' import fs from 'node:fs/promises' const fileAccess = { readFile: (path, enc) => fs.readFile(path, { encoding: enc }), writeFile: fs.writeFile, } const config = await loadConfig(process.cwd()) const cache = createCache('.tevm', fileAccess, process.cwd()) const solc = await createSolc() const myBundler = bundler(config, console, fileAccess, solc, cache) const result = await myBundler.resolveTsModule('path/to/Contract.sol', process.cwd(), false, true) console.log(result.code) ``` ### Further Reading * [Bundler Internals](/reference/bundler/internals) * [Troubleshooting](/reference/bundler/troubleshooting) * [bundler-packages source](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages) ## Bundler Overview ### Key Benefits * **Direct imports**: import `.sol` files like any TS module. * **Type safety**: full type checking for methods, args, and return values. * **IDE integration**: go-to-definition, auto-completion, hover docs. * **NatSpec**: contract docs appear in your editor. * **HMR**: Solidity changes update during development. * **Framework support**: Vite, Webpack, Next.js, Bun, and more. ### Available Plugins | Bundler | Plugin Import Path | Repository | | ------- | ----------------------------- | ------------------------------------------------------------------------------------------------- | | Vite | `tevm/bundler/vite-plugin` | [@tevm/vite-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/vite) | | Webpack | `tevm/bundler/webpack-plugin` | [@tevm/webpack-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/webpack) | | Rollup | `tevm/bundler/rollup-plugin` | [@tevm/rollup-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/rollup) | | ESBuild | `tevm/bundler/esbuild-plugin` | [@tevm/esbuild-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/esbuild) | | Bun | `tevm/bundler/bun-plugin` | [@tevm/bun-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/bun) | | Rspack | `tevm/bundler/rspack-plugin` | [@tevm/rspack-plugin](https://github.com/evmts/tevm-monorepo/tree/main/bundler-packages/rspack) | For plugin configuration, see the [Quickstart Guide](/getting-started/bundler). ### Prerequisites & Key Points * **Optional**: use `tevm generate contract` if you don't want a bundler hook. ```bash tevm generate contract tevm generate contract "ERC20Token" tevm generate contract --include "contracts/**/*.sol" --output "types/" ``` * **TypeScript**: add `@tevm/ts-plugin` to tsconfig for editor support. * **`.s.sol` for bytecode**: `.sol` produces ABI only; `.s.sol` includes deployable bytecode. * **Cache**: artifacts live in `.tevm/` — add it to `.gitignore`. The directory is ephemeral. * **Foundry/remappings**: configure in `tevm.config.json` (see [below](#bundler-configuration)). * **Next.js**: type-checker conflicts; use [codegen](/reference/bundler/troubleshooting#codegen-approach) or disable Next typechecking. ### Basic Usage ```ts import { Counter } from './Counter.sol' import { createMemoryClient } from 'tevm' const client = createMemoryClient() const deployed = await client.deployContract(Counter) const count = await deployed.read.count() await deployed.write.increment() await client.mine({ blocks: 1 }) const newCount = await deployed.read.count() ``` The imported value is a [Tevm Contract](/reference/contract) instance with abi, optional bytecode, deployment helpers, `.read` / `.write` methods, and event filters. ### Importing Dependencies ```ts import { MyToken } from './contracts/MyToken.sol' import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol' ``` ### Integration with Viem and Ethers :::code-group ```ts [viem] import { createPublicClient, http } from 'viem' import { mainnet } from 'viem/chains' import { MyToken } from './contracts/MyToken.sol' const client = createPublicClient({ chain: mainnet, transport: http() }) const balance = await client.readContract({ ...MyToken.read.balanceOf('0x123...'), address: '0x456...', }) const hash = await client.writeContract({ ...MyToken.write.transfer('0x789...', 1000n), address: '0x456...', }) ``` ```ts [ethers] import { ethers } from 'ethers' import { MyToken } from './contracts/MyToken.sol' const provider = new ethers.BrowserProvider(window.ethereum) const signer = await provider.getSigner() const tokenContract = new ethers.Contract('0x456...', MyToken.abi, signer) const balance = await tokenContract.balanceOf('0x123...') const tx = await tokenContract.transfer('0x789...', 1000n) ``` ::: ### Bundler Configuration ```json { "foundryProject": true, "libs": ["lib", "node_modules"], "remappings": { "@openzeppelin/": "node_modules/@openzeppelin/" }, "debug": false, "cacheDir": ".tevm", "jsonAsConst": ["**/*.abi.json"] } ``` | Option | Type | Description | | ---------------- | ------------------------ | ------------------------------------------------- | | `foundryProject` | `boolean \| string` | Enable Foundry (`true`) or path to `foundry.toml` | | `libs` | `string[]` | Library paths for Solidity imports | | `remappings` | `Record` | Custom import remappings | | `debug` | `boolean` | Extra debug logs and files in `.tevm` | | `cacheDir` | `string` | Artifact location (default: `.tevm`) | | `jsonAsConst` | `string \| string[]` | Glob patterns for `as const` JSON imports | ### Coming Soon Features #### Inline Solidity with `sol` Tag :::info This feature is coming soon and is not yet available in the current release. ::: ```ts import { sol } from 'tevm' const { bytecode, abi } = sol` pragma solidity ^0.8.19; contract Counter { uint256 private count = 0; function increment() public { count += 1; } function count() public view returns (uint256) { return count; } } ` ``` #### Network Imports via Macros :::info This feature is coming soon and is not yet available in the current release. ::: Build-time contract imports from any chain, offering stronger type safety than runtime imports. ```ts // contract-macros.js import { createMemoryClient, loadContract } from 'tevm' import { http } from 'viem' import { SourcifyABILoader, EtherscanABILoader } from 'tevm/whatsabi' const client = createMemoryClient({ fork: { transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'), blockNumber: 19000000n, }, }) export async function wethContract() { return loadContract(client, { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', followProxies: true, loaders: [new SourcifyABILoader(), new EtherscanABILoader({ apiKey: 'YOUR_ETHERSCAN_KEY' })], }) } // app.js import { wethContract } from './contract-macros.js' with { type: 'macro' } ``` Enable macros in `tevm.config.json` for security: ```json { "macros": true, "foundryProject": true, "libs": ["lib", "node_modules"] } ``` Full documentation: [Contract Loader](/api/whatsabi-integration#network-imports-via-macros). ### Further Reading * [Bundler Internals](/reference/bundler/internals) * [Methods & Exports](/reference/bundler/methods) * [Troubleshooting](/reference/bundler/troubleshooting) ## Bundler Troubleshooting ### Common Issues #### 1. Missing or Red Underlines in Editor Solidity imports show red underlines or no auto-completion. * Add `"plugins": [{ "name": "@tevm/ts-plugin" }]` to tsconfig.json. * In VS Code / Cursor, switch to the workspace TypeScript version: Command Palette → "TypeScript: Select TypeScript Version" → "Use Workspace Version". * For Vim/Neovim/other editors, ensure they use the workspace TS version. #### 2. Type-check Errors with Next.js Build works but Next.js typechecking fails on `.sol` imports. Option 1 - disable typechecking in `next.config.mjs`: ```js export default { typescript: { ignoreBuildErrors: true, }, } ``` Option 2 - use the [Codegen Approach](#codegen-approach) (recommended for Next.js). #### 3. "File Not Found" Errors Bundler can't resolve imports. * Check libs and remappings. * For Foundry projects, set `foundryProject: true` in `tevm.config.json`. * For npm packages, verify install and import path. * Add explicit remappings: ```json { "remappings": { "@customlib/": "node_modules/@customlib/", "local/": "./contracts/" } } ``` #### 4. Cache Stale Issues Solidity changes don't take effect. * Delete `.tevm/` and rebuild. * Add `.tevm` to `.gitignore`. #### 5. No Bytecode Available Deployment fails with missing bytecode. * Use the `.s.sol` extension for deployable contracts. Regular `.sol` produces ABI only. #### 6. Deployment Errors Even with bytecode, deployment fails. * Pass constructor args via `.deploy(...)`: ```ts const deployed = await client.deployContract(MyToken.deploy('TokenName', 'TKN', 18)) ``` #### 7. Test Runner Issues `.sol` imports fail in tests. * Most runners (Vitest) work once the plugin is configured. * Use the bundler matching your test environment (esbuild for Vitest, etc.). ### Codegen Approach ```bash npx tevm gen ``` Generates `.ts` files next to each `.sol`. Recommended when bundler hooks are impractical (e.g., Next.js with strict typechecking). ### Additional Resources * [GitHub Issues](https://github.com/evmts/tevm-monorepo/issues) * [Examples](https://github.com/evmts/tevm-monorepo/tree/main/examples) * [Next.js example](https://github.com/evmts/tevm-monorepo/tree/main/examples/next) ### Further Reading * [Bundler Overview](/reference/bundler/overview) * [Bundler Internals](/reference/bundler/internals) * [Methods & Exports](/reference/bundler/methods) ## Tevm Architecture Overview :::warning[Advanced Content] This page covers Tevm's internal architecture — intended for advanced users, contributors, or those who want to know how Tevm works under the hood. If you're getting started, see the [viem API guide](../getting-started/viem) instead. ::: Tevm's architecture is modular, extensible, and compatible with the broader JavaScript ecosystem. For the native ZEVM client's ownership boundary between ZEVM, Voltaire, and Guillotine Mini, see ZEVM's [Architecture and Upstream Ownership](https://zevm.sh/docs/concepts/architecture-and-upstream-ownership/) docs. Tevm's boundary is different: Tevm owns the JavaScript APIs, transports, package facades, and integration behavior described below while using ZEVM-backed primitives where noted. ### Design Philosophy: Objects and Actions Tevm separates **Objects** (stateful components) from **Actions** (pure functions over them). This [viem](https://viem.sh/)-inspired pattern enables tree-shaking and a composable API, and Tevm follows it internally for maximum viem compatibility. **Objects** — stateful components that encapsulate data: * `TevmNode` — the core Node interface * `Evm` — the Ethereum Virtual Machine * `StateManager` — manages blockchain state * `Blockchain` — handles blocks and chain state **Actions** — pure functions taking an object as their first parameter: * Tree-shakable for minimal bundle size * Single-purpose with clear input/output * Composable for complex operations * Can be imported individually #### Example: Using an Action ```ts showLineNumbers {1-2,5,8,11} filename="action-example.ts" import { createTevmNode, PREFUNDED_ACCOUNTS } from "tevm"; import { getAccountHandler } from "tevm/actions"; // [!code focus] const node = createTevmNode(); // [!code focus] const getAccount = getAccountHandler(node); // [!code focus] const account = await getAccount({ // [!code focus] address: PREFUNDED_ACCOUNTS[0].address, }); console.log(account.balance); ``` This lets you import only the actions you need, create specialized handlers, and follow a consistent interface. :::tip `TevmNode` is the low-level node implementation. It is NOT recommended you use `createTevmNode` directly (especially if you are an LLM) unless you have an advanced use case. ::: ### Client Options: Convenience vs. Tree Shaking Tevm offers two main approaches: :::code-group ```ts [MemoryClient: All-in-One] import { createMemoryClient, http } from 'tevm' // Create a client with all actions pre-attached const client = createMemoryClient({ fork: { transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY') } }) // Standard viem actions const code = await client.getContractCode({ address: '0x1234567890123456789012345678901234567890' }) // Tevm-specific actions (prefixed with 'tevm') const state = await client.tevmDumpState() const balance = await client.getBalance({ address: '0x1234567890123456789012345678901234567890' }) ``` ```ts [Tree-Shakable: Optimized] import { createClient, http } from 'viem' import { createTevmTransport } from 'tevm' import { getBlock, getBlockNumber } from 'viem/actions' import { tevmSetAccount, tevmMine } from 'tevm/actions' const client = createClient({ transport: createTevmTransport({ fork: { transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY') } }) }) // Import only the actions you need const blockNumber = await getBlockNumber(client) const block = await getBlock(client) await tevmSetAccount(client, { address: '0x1234...', balance: 1000000000000000000n }) await tevmMine(client, { blocks: 1 }) ``` ::: :::info MemoryClient is great for quick prototyping. Tree-shakable actions are ideal for production and browser apps where bundle size matters. ::: **MemoryClient**: ✅ easy to start · ✅ all methods available · ✅ less code · ❌ larger bundle **Tree-Shakable Actions**: ✅ smallest bundle · ✅ only what you use · ✅ works with code-splitting · ❌ more verbose imports For more on tree-shakable actions, see the [viem docs on tree-shaking](https://wagmi.sh/react/guides/viem). ### Core Architecture Components :::steps #### VM The most important component, made up of the ZEVM-backed interpreter facade, StateManager, Blockchain, Common, TxPool, and ReceiptsManager. The VM runs transactions and builds blocks, coordinating state through its subcomponents. It is NOT recommended you use the VM directly except for advanced use cases — use the `viem` API instead. #### Interpreter (EVM) Runs EVM bytecode with full support for active hardfork opcodes, built-in precompiles, and Tevm custom precompiles. Tevm's public EVM facade is backed by `@evmts/zevm/evm`, wrapped with Tevm-specific state, tracing, predeploy, logging, and profiling hooks. ```ts // Access via const evm = (await node.getVm()).evm; // Features evm.events.on("step", (data, next) => { console.log(`Opcode: ${data.opcode.name}`); next(); }); // Control execution gas limits, precompiles, etc. ``` For the most minimal way to execute bytecode, the EVM facade with the common and stateManager modules can be a good fit. For most use cases, prefer the viem API or MemoryClient. The EVM wraps ZEVM's JavaScript/Wasm-compatible EVM primitives rather than the older pre-ZEVM implementation, keeping Tevm's low-level types aligned with `@evmts/zevm` while preserving the higher-level viem-style API. #### State Manager Maintains account balances, contract code, and storage state with forking capability from live networks. Tevm's custom state layer built around ZEVM-compatible account, trie, and utility primitives. ```ts // Access via const stateManager = (await node.getVm()).stateManager; // Features await stateManager.putAccount(address, account); await stateManager.getAccount(address); await stateManager.getStorage(address, key); await stateManager.checkpoint(); // Create state snapshot await stateManager.revert(); // Revert to the latest checkpoint ``` The StateManager keeps low-level methods Tevm's VM needs while adding Tevm-specific behavior: fork-backed reads, snapshots, and state dumping. #### Blockchain Manages blocks, chain state, forked block lookup, and block production (auto, interval, manual mining). ```ts // Access via const blockchain = (await node.getVm()).blockchain; // Features await blockchain.getBlock(blockHash); await blockchain.getBlockByNumber(blockNumber); await blockchain.putBlock(block); await blockchain.getLatestBlock(); ``` A Tevm implementation backed by Tevm block types and ZEVM-compatible primitives. Not recommended to use directly — prefer viem actions, MemoryClient methods, or JSON-RPC unless you're building Tevm extensions. #### Common A config object holding chain-specific information such as hardfork and EIP info. #### Transaction Pool Manages pending transactions, orders them by gas price, and handles transaction validation. ```ts // Access via const txPool = await node.getTxPool(); // Features await txPool.add(transaction); await txPool.getTransactions(); const pendingTxs = txPool.getPendingTransactions(); const pendingNonces = txPool.getPendingNonce(address); ``` ::: Custom viem API actions for the transaction pool are planned but not high priority. Join the Telegram if you want this. #### Receipt Manager A cache for receipts and logs so Tevm doesn't have to re-execute transactions to derive them. Don't interact with it directly except for very advanced use cases (of which there may be none). #### Custom Tool Opportunities * **Transaction Simulators** — Preview transaction outcomes before sending to mainnet * **EVM Debuggers** — Step through transactions with full state visibility * **Local-first dApps** — Apps that work offline with optimistic updates * **Educational Tools** — Interactive EVM learning experiences * **CI/CD Integration** — Test smart contracts in continuous integration pipelines * **Gas Optimization** — Analyze contract gas usage patterns with precision * **Serverless Execution** — Run Ethereum nodes in serverless or edge environments * **State Snapshots** — Create, save, and restore blockchain state at precise points For examples, see [examples](../examples/viem.mdx). ### API Interfaces :::tip[Choose Your API Level] Tevm provides multiple API layers to suit different needs and styles. ::: | API Level | Description | Best For | | ---------------------- | ------------------------------------------------------------------ | ----------------------------------- | | **Viem Client API** | Standard viem actions plus Tevm-specific actions | Most application development | | **JSON-RPC API** | Standard Ethereum RPC methods plus Anvil and Tevm-specific methods | Direct RPC integration, tooling | | **Low-Level TevmNode** | Direct access to EVM, StateManager, Blockchain, etc. | Tool developers, deep customization | **API layers at a glance:** * Viem Client API (high-level): `client.getBalance()`, `client.tevmMine()`, `client.sendTransaction()` * JSON-RPC API: `eth_getBalance`, `anvil_mine`, `tevm_dumpState` * TevmNode API: `node.getVm()`, `node.getTxPool()`, `node.extend()` * Low-Level Components: `vm.evm.runCall()`, `stateManager.getAccount()`, `blockchain.putBlock()` :::code-group ```ts [Client API] import { createMemoryClient } from 'tevm' const client = createMemoryClient() // Standard viem actions const balance = await client.getBalance({ address: '0x123...' }) const blockNumber = await client.getBlockNumber() // Tevm-specific actions await client.tevmSetAccount({ address: '0x123...', balance: 1000000000000000000n }) await client.tevmMine() ``` ```ts [JSON-RPC API] import { createTevmNode } from 'tevm' import { requestEip1193 } from 'tevm/decorators' const node = createTevmNode().extend(requestEip1193()) // Standard Ethereum JSON-RPC methods const balance = await node.request({ method: 'eth_getBalance', params: ['0x123...', 'latest'] }) // Anvil-compatible methods await node.request({ method: 'anvil_setBalance', params: ['0x123...', '0x10000000000000000'] }) // Tevm-specific methods const state = await node.request({ method: 'tevm_dumpState', params: [] }) ``` ```ts [Low-Level API] import { createTevmNode } from 'tevm' import { createAddress } from 'tevm/address' import { createAccount } from 'tevm/utils' const node = createTevmNode() const vm = await node.getVm() // Direct EVM access vm.evm.events.on('step', (data, next) => { console.log(data.opcode.name, data.stack) next?.() }) // Direct state management await vm.stateManager.putAccount( createAddress('0x1234567890123456789012345678901234567890'), createAccount({ nonce: 0n, balance: 10000000000000000000n }) ) // Direct blockchain control const block = await vm.blockchain.getBlockByNumber(1n) ``` ::: :::info The Client API is the most developer-friendly and perfect for most applications. The Low-Level API provides maximum control but requires deeper understanding of EVM internals. ::: For component API details, see: * [State Manager](/reference/state) * [Transaction Pool](/reference/txpool) * [Blockchain](/reference/blockchain) * [EVM](/reference/evm) * [Receipt Manager](/reference/receipt-manager) ### Advanced Features :::steps #### Contract Utilities Tevm's type-safe contract interactions are Tevm-agnostic — they work with viem, ethers, and wagmi. We may upstream this abstraction to viem in the future. ```ts import { createContract } from "tevm/contract"; const erc20Contract = createContract({ abi: [ "function balanceOf(address owner) view returns (uint256)", "function transfer(address to, uint256 amount) returns (bool)", "event Transfer(address indexed from, address indexed to, uint256 value)", ], address: "0x123...", }); const balance = await erc20Contract.read.balanceOf("0x456..."); const txHash = await erc20Contract.write.transfer("0x789...", 1000n); ``` ::: For Tevm Bundler users, import Solidity directly with full type safety: ```ts import { ERC20 } from "./ERC20.sol"; const token = ERC20.withAddress("0x123..."); const decimals = await token.read.decimals(); ``` ### Extensibility Model :::details[Node Extension API] Tevm's plugin system allows adding new functionality to nodes: ```ts import { createTevmNode } from "tevm"; const node = createTevmNode().extend((baseNode) => { return { async simulateBulkTransactions(txs) { const results = []; for (const tx of txs) { const vm = await baseNode.getVm(); results.push(await vm.runTx({ tx })); } return results; }, async resetToSnapshot(snapshot) { const vm = await baseNode.getVm(); return vm.stateManager.revert(snapshot); }, }; }); const snapshot = await node.getVm().stateManager.checkpoint(); const results = await node.simulateBulkTransactions([tx1, tx2, tx3]); await node.resetToSnapshot(snapshot); ``` This model allows powerful customizations while preserving the core API. ::: ### Further Resources | Resource | Description | | ----------------------------------------------------------- | -------------------------------------------------- | | [TevmNode Interface Reference](/reference/node) | Detailed API reference for the core node interface | | [GitHub Repository](https://github.com/evmts/tevm-monorepo) | Source code and contributions | | [Custom Precompiles Guide](../advanced/custom-precompiles) | Learn how to extend the EVM | | [Performance Profiling](../advanced/performance-profiler) | Optimize your Ethereum applications | Next: [Create a Tevm Node](../core/create-tevm-node) · [Viem Integration](../examples/viem) · [Practical Examples](../examples/local-testing) ### What is Tevm Node? :::tip[Ethereum in JavaScript] Tevm Node is a complete Ethereum execution environment for JavaScript, backed by `@evmts/zevm` execution primitives and exposed through viem-compatible APIs. It runs in-memory in any JS environment with no native client binary, and includes buildtime tooling for importing EVM contracts into TypeScript. ::: Conceptually similar to Anvil/Hardhat but with more powerful TypeScript-native interop. Tevm is backed by ZEVM packages but is not the native ZEVM CLI client — see [Runtime Model and ZEVM](../core/runtime-model) and the [ZEVM docs](https://zevm.sh/docs). :::tip[You might already know most of the Tevm API!] If you know `viem` or `ethers`, you can use Tevm Node immediately — Tevm extends your library of choice. ::: ### What Makes Tevm Unique? * **Cross-Platform Compatibility** — Same code works everywhere JavaScript runs, including the browser, with zero native dependencies * **Fine-grained EVM Control** — Access execution at any level, from high-level transactions to individual opcodes and debug traces * **Enhanced User Experience** — Instantaneous gas estimation, optimistic UI, fork-backed reads, account impersonation, transaction simulation * **Type-safe Interactions** — Full TypeScript across the API, powered by `abitype` * **Direct Solidity Imports** — Write Solidity that interops with TypeScript via the [Tevm Bundler](/getting-started/bundler) * **Development Node Compatibility** — `tevm_*`, `eth_*`, `debug_*`, `txpool_*`, `engine_*`, `anvil_*`, `hardhat_*`, `ganache_*`, `evm_*` JSON-RPC methods ### Universal JavaScript Compatibility Runs in every JavaScript runtime: * **Node.js** — local dev, testing, CI/CD * **Browser** — advanced dApps with offline capability and real-time simulation * **Any JS Runtime** — Deno, Bun, Edge Functions, or any modern JS environment ### Integration With Popular Libraries Tevm works with `viem`, `wagmi`, and `ethers`: :::code-group ```ts [viem] import { createMemoryClient, http } from "tevm"; const client = createMemoryClient(); const balance = await client.getBalance({ address: "0x..." }); const blockNumber = await client.getBlockNumber(); await client.tevmMine({ blocks: 1 }); await client.tevmSetAccount({ address: "0x...", balance: 100000000000000000n, }); ``` ```ts [ethers] import { createMemoryClient } from "tevm"; import { requestEip1193 } from "tevm/decorators"; import { BrowserProvider, Wallet } from "ethers"; const client = createMemoryClient(); client.transport.tevm.extend(requestEip1193()); const provider = new BrowserProvider(client.transport.tevm); const signer = Wallet.createRandom().connect(provider); const blockNumber = await provider.getBlockNumber(); // Don't forget to mine after transactions await client.mine({ blocks: 1 }); ``` ::: ### How Tevm Compares | Feature | Tevm | Anvil | Hardhat | Ganache | | --------------------------------- | --------------- | -------- | --------------- | ---------- | | **Language** | JavaScript/Wasm | Rust | JavaScript/Rust | JavaScript | | **Browser Compatible** | ✅ | ❌ | ❌ | ❌ | | **Minimal Dependencies** | ✅ | ✅ | ❌ | ❌ | | **Mainnet Forking** | ✅ | ✅ | ✅ | ✅ | | **EVM Event Hooks** | ✅ | ❌ | ❌ | ❌ | | **Custom Precompiles** | ✅ | ✅ | ❌ | ❌ | | **viem Integration** | Native | Some | Minimal | Minimal | | **ethers Integration** | Native | Minimal | Minimal | Some | | **Debugging** | Advanced | Advanced | Advanced | Basic | | **TypeScript Support** | Full | Limited | Full | Full | | **Serverless Compatible** | ✅ | ❌ | ❌ | ✅ | | **Optimized forking performance** | ✅ | ❌ | ❌ | ❌ | ### Library Compatibility | Library | Support Level | Notes | | ---------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | [**viem**](../getting-started/viem.mdx) | Native | First-class native integration with all viem features | | [**ethers.js**](../getting-started/ethers.mdx) | Full | Both v5 and v6 via EIP-1193 provider | | [**web3.js**](https://github.com/web3/web3.js) | Full | Via EIP-1193 provider | | [**wagmi**](https://wagmi.sh/) | Full | Works as a wagmi connector | | [**thirdweb**](https://thirdweb.com/) | Full | Compatible with thirdweb's SDK | | [**Ponder**](https://ponder.sh/) | Full | Can be used to do advanced tracing in ponder handlers and import contracts into ponder config | | [**ZEVM**](https://zevm.sh/docs) | Native | Tevm's VM, transaction, receipt, txpool, common, RLP, trie, and utility facades are backed by `@evmts/zevm` packages | | Legacy low-level consumers | Compatible | Several Tevm low-level APIs keep familiar shapes for migration, but new docs and integrations should target Tevm and ZEVM-backed packages | | Any EIP-1193 library | Full | Standard provider interface | Next: [Why JS?](./why-run-ethereum-in-js) · [Architecture Overview](./architecture-overview) · [Create a Tevm Node](../core/create-tevm-node) ## Why Run An Ethereum Node in JavaScript? > You know what would make solving all these problems trivially easy? If we just were able to use Foundry in the browser That's what [Fucory](https://x.com/fucory) thought the day he started building Tevm — putting the Foundry API in the browser, then growing from there. Tevm's use case is simply TypeScript + EVM. > We believe every TypeScript user of the EVM who installs Viem will also install Tevm alongside it in the future * **🚀 Enhanced Performance** — Execute transactions locally with near-zero latency for gas estimation, simulation, and debugging. * **💻 Browser Compatibility** — Enable offline capabilities, optimistic UI, and real-time simulations in dApps. * **🔍 Debug Superpowers** — Step through EVM execution opcode by opcode; inspect memory and stack. * **🛠️ Familiar Developer Experience** — Works with viem, ethers, or any EIP-1193 compatible tool. :::info[Did you know?] Tevm Node is part of a larger ecosystem that includes [Tevm Bundler](https://tevm.sh/bundler) for direct Solidity imports into JavaScript/TypeScript. ::: ### Performance & Efficiency * **⚡ Optimized fork mode** — Benchmarked to outperform Anvil at executing calls in `forked` mode via more efficient storage slot retrieval. * **⚡ Zero network latency** — Local EVM execution eliminates RPC round-trips for near-instant simulations and gas estimation. * **Local-first gas estimation** — No loading spinner, no RPC credits burned. * **🔄 Powerful JS interop** — Simulate multiple transactions, plug directly into the EVM, or write custom contracts in JS. ### Optimistic updates Show users the expected state of their account — pending transactions, state changes, or snappy UI with a pending icon. Tevm makes optimistic state simple. ```typescript // create a client in rebase mode so it updates optimistic state as new blocks come in const client = createMemoryClient({ fork: { transport: http("https://mainnet.optimism.io"), rebase: true, }, common: optimism, }); // When we send a transaction to the network send it to Tevm too // We do not mine the transaction as we want it to just be in our mempool const txHash = await client.sendRawTransaction(tx); client.waitForTransactionReceipt({ hash: txHash }).then(() => { // remove the tx from optimistic state after it is included in chain const mempool = await client.transport.tevm.getTxPool(); await mempool.removeTxByHash(txHash); }); // continue to query the latest state by default await client.getBalance({ address, blockTag: "latest" }); // or query optimistic state with 'pending' block tag await client.getBalance({ address, blockTag: "pending" }); ``` #### Real-World Performance Benefits :::tip[Performance Comparison] Tevm's local execution provides instantaneous gas estimation. ::: ```typescript showLineNumbers {1-4,8-11} filename="performance-comparison.ts" const gasEstimate0 = await client.estimateGas({ ... }) // ~200ms as it fetches state (unless you prewarmed the cache) const gasEstimate0 = await client.estimateGas({ ... }) // ~Instant on future estimations with cache saved const gasEstimate0 = await client.estimateGas({ ... }) // ~Instant on future estimations with cache saved ``` Because Tevm plugs directly into wagmi this works via `useGasEstimate` too. ### Enhanced User Experiences JavaScript-based EVM execution enables new categories of dApp features: * **Maximally hackable** — Complete control including deep internals; supports almost any use case. * **⚡ Optimistic UI** — Show users the likely outcome of transactions before they're mined. * **🛡️ Reliable** — Near 100% test coverage; most reported bugs fixed in under 24 hours. * **🧮 Transaction Simulation** — Preview results of complex interactions before sending. * **🔐 Enhanced Privacy** — Process sensitive data locally without sending it to external services. ### Top Tier Devx #### Use the tools you already know Tevm plugs directly into `wagmi`, `viem`, and `ethers`. #### Interop with contracts effortlessly The **Tevm Bundler** (optional) makes TypeScript aware of how to process and compile Solidity. It's in the same category as `Wagmi CLI` or `Typechain`, but more powerful: * Natspec on hover * Typesafe contracts * tRPC-like experience — red underlines before you save the Solidity file * No external build tools — plugs into your existing JS pipeline * Reads foundry config for remappings and lib ##### Import Solidity directly The bundler has a compiler built in — no precompilation needed: ```typescript import { MyContract } from "./MyContract.sol"; console.log(MyContract.abi); ``` You can import from node\_modules or foundry projects. Tevm supports remappings, lib, and other advanced options. Unlike Foundry, Tevm supports node\_module resolution by default. ##### Use contracts via address If your contract is deployed, just reference it by address — Tevm pulls the ABI at build time (even for unverified contracts): ```typescript // create a macro file for your contracts import { client } from "./clients.js"; export const MyContract = await client.whatsabi(`0x...`); ``` ```typescript // import your macro using tevm and Tevm will fetch your contracts at buildtime import {MyContract} from './MyContract.js' as {type: 'tevm'} ``` #### Low level control of the EVM Tools like `anvil` run in a separate process over HTTP. Tevm runs in memory with direct node access — enabling programmability no other tool offers. ##### Run callbacks on every EVM step 🔬 Step through EVM execution opcode by opcode, inspect memory and stack, and see exactly what happens in your contracts. ```typescript filename="debug-example.ts" vm.evm.events.on("step", (data, next) => { console.log( `${data.pc.toString().padStart(5)}:`, `${data.opcode.name.padEnd(10)}`, `gas: ${data.gasLeft.toString().padStart(8)}`, `stack: ${data.stack.join(", ")}`, ); next(); }); ``` You can even modify EVM execution as it runs. ##### Mock EVM contracts with JavaScript contracts Write contracts in JavaScript. Precompiles are like Foundry cheat codes, but instead of a standard library you write arbitrary JS. Works nicely with the Tevm Bundler: ```typescript import { defineCall, definePrecompile, createContract, createMemoryClient, } from "tevm"; import { readFile } from "fs/promises"; const contract = createContract({ address: `0x${"1234".repeat(10)}`, humanReadableAbi: ["function readFile(string fileName) returns string"], }); const { precompile } = definePrecompile({ contract, call: defineCall(contract.abi, { readFile: ({ args }) => { return { data: await readFile(args.fileName, "utf8"), gasUsed: 0n, }; }, }), }); const memoryClient = createMemoryClient({ precompiles: [precompile()], }); ``` #### Deterministic Testing 🧪 Though built for application development, Tevm is great for testing too — fully reproducible environments with complete control over blockchain state, time, and mining. ### Solidity Imports Tevm Bundler (optional) creates the best devx for Solidity files in TypeScript: ```typescript import { MyContract } from "./MyContract.sol"; ``` ### JavaScript Ecosystem Integration :::note[Seamless Integration] Running Ethereum in JavaScript means you can leverage the entire JS ecosystem effortlessly. ::: * **🔤 TypeScript** — Type-safe contract interactions with full IntelliSense * **⚛️ UI Frameworks** — React, Vue, Svelte and other frontend libraries * **🏗️ Build Tools** — Vite, Webpack, ESBuild and other bundlers * **🧪 Testing** — Vitest support via Vite * **🔄 Runtimes** — Node.js, browsers, Electron, serverless functions * **📦 NPM Ecosystem** — Millions of packages in the npm registry * **🌐 Web APIs** — Browser storage, WebSockets, service workers, and more ### Ready to Get Started? Next: [Install Tevm](../getting-started/overview) · [Create a Tevm Node](../core/create-tevm-node) · [Run examples](../examples/viem) · [GitHub](https://github.com/evmts/tevm-monorepo) ## Bundler Quickstart Set up the Tevm bundler to import Solidity contracts directly into TypeScript/JavaScript. ### Overview Tevm bundler enables: * `.sol` files imported directly in your code * Full TypeScript type info for contract methods * Go-to-definition and hover docs for Solidity * Type-safe contract interaction ### Prerequisites * Node.js 18+ and npm/yarn/pnpm * A supported bundler (Vite, Webpack, Rollup, ESBuild, or Bun) ### Step 1: Install Tevm and TypeScript Plugin ```bash npm install tevm npm install -D @tevm/ts-plugin ``` ### Step 2: Configure Your Bundler The bundler plugin compiles Solidity during your build, so the JS output contains ABIs, bytecode, and TypeScript interfaces. :::tip Complex projects or Foundry imports? See [Advanced Configuration](#advanced-configuration) for `tevm.config.json`. The bundler creates a `.tevm` cache directory — add it to `.gitignore`: ```bash # .gitignore .tevm ``` ::: #### For Vite (recommended) ```ts // vite.config.ts import { defineConfig } from 'vite' import { vitePluginTevm } from 'tevm/bundler/vite-plugin' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react(), vitePluginTevm()], }) ``` #### For Other Bundlers :::details[Webpack] ```js // webpack.config.mjs import { webpackPluginTevm } from 'tevm/bundler/webpack-plugin' export default { plugins: [webpackPluginTevm()], } ``` ::: ::::details[Next.js] ```js // next.config.js const nextConfig = { webpack: (config) => config, typescript: { // LSP typechecking works in-editor but not during `next build` yet. // For build-time typechecking, generate .ts files via `npx evmts-gen`. ignoreBuildErrors: true, } } export default nextConfig ``` :::warning Next.js' type-checking pipeline may conflict with Solidity imports. If `next build` errors: 1. Set `typescript.ignoreBuildErrors: true` (above), or 2. Use codegen: `npx tevm gen` to generate explicit `.ts` files. ::: :::: \::: :::details[Rollup] ```js // rollup.config.js import { rollupPluginTevm } from 'tevm/bundler/rollup-plugin' export default { plugins: [rollupPluginTevm()], } ``` ::: :::details[Rspack] ```js // rspack.config.mjs import { rspackPluginTevm } from 'tevm/bundler/rspack-plugin' export default { plugins: [rspackPluginTevm()], } ``` ::: :::details[Bun] ```ts // plugins.ts import { plugin } from 'bun' import { tevmBunPlugin } from 'tevm/bundler/bun-plugin' plugin(tevmBunPlugin({})) ``` ```toml # bunfig.toml preload = ["./plugins.ts"] [test] preload = ["./plugins.ts"] ``` ::: :::details[ESBuild] ```js // build.mjs import { build } from 'esbuild' import { esbuildPluginTevm } from 'tevm/bundler/esbuild-plugin' build({ entryPoints: ['src/index.js'], outdir: 'dist', bundle: true, plugins: [esbuildPluginTevm()], }) ``` ::: ### Step 3: Configure TypeScript The TypeScript plugin enables editor features (completion, type checking, go-to-definition) for Solidity imports. Add to `tsconfig.json`: ```json { "compilerOptions": { "plugins": [ { "name": "@tevm/ts-plugin" } ] } } ``` ### Step 4: Configure Your Editor #### VS Code and Cursor Use the workspace TypeScript (the workspace install is what loads `@tevm/ts-plugin`): 1. Command Palette (Ctrl/Cmd+Shift+P, or ⌘K/Ctrl+K in Cursor) 2. "TypeScript: Select TypeScript Version" 3. "Use Workspace Version" #### Vim, Neovim, Other Editors Works automatically as long as the editor uses the project's workspace TypeScript (required to load `@tevm/ts-plugin`). ### Step 5: Create a Solidity Contract Name it `Counter.s.sol` to include bytecode (the `.s.sol` extension tells Tevm to generate deployable bytecode): :::note **`.sol` vs `.s.sol`:** * **`.sol`** — ABI only. For interacting with existing deployed contracts. Cannot be deployed (no bytecode). `import { Token } from './Token.sol'` * **`.s.sol`** — ABI + bytecode. Deployable or usable as an interface. Larger bundle. `import { Token } from './Token.s.sol'` ::: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Counter { uint256 private count = 0; function increment() public { count += 1; } function getCount() public view returns (uint256) { return count; } } ``` ### Step 6: Import and Use the Contract ```tsx // src/App.tsx import { Counter } from './Counter.s.sol' import { createMemoryClient } from 'tevm' import { useState, useEffect } from 'react' function App() { const [count, setCount] = useState(0n) const [client] = useState(() => createMemoryClient()) const [deployedAddress, setDeployedAddress] = useState('') useEffect(() => { const deploy = async () => { const deployed = await client.deployContract(Counter.deploy()) setDeployedAddress(deployed.address) setCount(await deployed.read.getCount()) } deploy() }, [client]) const handleIncrement = async () => { if (!deployedAddress) return const contract = Counter.withAddress(deployedAddress) await client.writeContract(contract.write.increment()) await client.mine({ blocks: 1 }) setCount(await client.readContract(contract.read.getCount())) } return (

Tevm Counter Example

Count: {count.toString()}

) } export default App ``` #### Type-Safe Method Calls ```ts // Read methods (view/pure) const balance = await client.readContract( ERC20.read.balanceOf('0x' + '20'.repeat(20)) ) // Write methods (state-changing) await client.writeContract( ERC20.write.transfer('0x' + '30'.repeat(20), 1000000n) ) // Events client.watchEvent( ERC20.events.Transfer({ from: '0x' + '20'.repeat(20), to: null // `null` is a wildcard }), (log) => console.log('Transfer event:', log) ) ``` ### What's Happening? When you import `Counter.s.sol`, Tevm: 1. Compiles via solc. 2. Generates TypeScript with ABI, bytecode, and a Contract instance. 3. Provides a fully typed interface. The `Counter` object is a [Tevm Contract](/reference/contract) with: * `abi` * `address` (if set via `withAddress()`) * `bytecode` — creation bytecode (undefined for `.sol`) * `deployedBytecode` — runtime bytecode (undefined for `.sol`) * `deploy()` — deployment data with constructor args * `read` — type-safe read methods * `write` — type-safe write methods * `events` — type-safe event filters * `withAddress()` — new instance with an address :::note `bytecode` and `deployedBytecode` are only available for `.s.sol` imports. Regular `.sol` files cannot be deployed. ::: ### Advanced Configuration #### Foundry Integration By default Tevm uses Node.js resolution. For Foundry projects or custom remappings, create `tevm.config.json`: ```json { "foundryProject": true, "libs": ["lib", "node_modules"], "remappings": { "@openzeppelin/": "node_modules/@openzeppelin/" }, "cacheDir": ".tevm" } ``` :::tip Add `.tevm` to `.gitignore`: ```bash # .gitignore .tevm ``` It's ephemeral — safe to delete, but will require recompiling on next build. ::: `"foundryProject": true` will: * Auto-read remappings from `foundry.toml`/`remappings.txt` * Include Foundry library paths (`lib/` by default) * Allow Foundry-style import paths * Merge with manual `remappings`/`libs` Manual config without Foundry: ```json { "foundryProject": false, "libs": [ "./contracts", "node_modules" ], "remappings": { "@openzeppelin/": "node_modules/@openzeppelin/", "ds-test/": "lib/forge-std/lib/ds-test/src/", "solmate/": "node_modules/solmate/src/" } } ``` Useful for mixed Foundry/JS projects, Forge libraries (OpenZeppelin, Solmate), or complex Solidity import paths. See the [Foundry example](https://github.com/evmts/tevm-monorepo/tree/main/examples/vite) for a complete project. ### Using Third-Party Contracts #### NPM Packages ```ts // Use .s.sol when bytecode is needed import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.s.sol' const myToken = await client.deployContract( ERC20.deploy("MyToken", "MTK") ) ``` #### Coming Soon Features ##### Network Imports with Macros :::note The macros-based network import feature is coming soon and is not yet available. ::: Upcoming: import contracts from any EVM network via build-time macros, resolved during build for better performance and type safety: ```ts // contract-macros.js import { createMemoryClient } from 'tevm' import { http } from 'viem' import { mainnet, optimism } from 'viem/chains' import { loadContract } from 'tevm' import { SourcifyABILoader, EtherscanABILoader } from 'tevm/whatsabi' // Use fixed block heights for reproducible builds const mainnetClient = createMemoryClient({ fork: { transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'), blockNumber: 19000000n } }) const optimismClient = createMemoryClient({ fork: { transport: http('https://mainnet.optimism.io'), blockNumber: 116000000n } }) const mainnetLoaders = [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: 'YOUR_ETHERSCAN_KEY' }) ] const optimismLoaders = [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: 'YOUR_ETHERSCAN_KEY', baseUrl: 'https://api-optimistic.etherscan.io/api' }) ] export async function wethContract() { return loadContract(mainnetClient, { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', followProxies: true, loaders: mainnetLoaders }) } export async function usdcContract() { return loadContract(optimismClient, { address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', followProxies: true, loaders: optimismLoaders }) } ``` Import with the `with {type: 'macro'}` attribute: ```ts import { wethContract } from './contract-macros.js' with { type: 'macro' } import { usdcContract } from './contract-macros.js' with { type: 'macro' } const wethBalance = await client.readContract({ ...wethContract.read.balanceOf('0x123...'), address: wethContract.address }) console.log(`USDC decimals: ${await client.readContract({ ...usdcContract.read.decimals(), address: usdcContract.address })}`) console.log(`Human readable ABI: ${usdcContract.humanReadableAbi}`) console.log(`Implementation address: ${usdcContract.proxyDetails?.[0]?.implementation || 'Not a proxy'}`) ``` Enable macros in `tevm.config.json` (required for security): ```json { "macros": true, "foundryProject": true, "libs": ["lib", "node_modules"] } ``` ABIs resolve at build time with full type safety. See [Contract Loader](/api/whatsabi-integration#network-imports-via-macros). ##### Inline Solidity with Template Literals :::note Inline Solidity is coming soon and is not yet available. ::: For prototyping, define contracts inline: ```ts import { sol } from 'tevm' const { Counter } = sol` // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Counter { uint256 private count = 0; function increment() public { count += 1; } function getCount() public view returns (uint256) { return count; } } ` const deployed = await client.deployContract(Counter.deploy()) await deployed.write.increment() const count = await deployed.read.getCount() ``` Same type-safety and features as file-based imports. ### Documentation Resources * Bundler Reference: * [Overview](/reference/bundler/overview) * [Internals](/reference/bundler/internals) * [Methods & Exports](/reference/bundler/methods) * [Troubleshooting](/reference/bundler/troubleshooting) * Full docs as plain text for LLMs: [https://node.tevm.sh/llms-full.txt](https://node.tevm.sh/llms-full.txt) ### Troubleshooting * **Red underlines in imported Solidity** — use the workspace TypeScript version. * **No bytecode available** — use `.s.sol` for deployable contracts. * **Deployment errors** — pass required constructor args to `.deploy()`. * **Compilation errors** — check Solidity code and version compatibility. * **TypeScript errors** — verify the TS plugin is configured. * **Red underlines in editor** — confirm workspace TS with plugin loaded. * **Import resolution failures** (Foundry-style imports like `@openzeppelin/contracts/...`): * Create `tevm.config.json` with `"foundryProject": true` * Check `foundry.toml` remappings * Add explicit remappings in `tevm.config.json` * **Test runners** — Vitest/Jest work out-of-the-box once the bundler plugin is configured. ### Next Steps * [Tevm Contracts](/reference/contract) * [Bundler Overview](/reference/bundler/overview) * [Bundler Internals](/reference/bundler/internals) * [Advanced Methods & APIs](/reference/bundler/methods) * [Troubleshooting](/reference/bundler/troubleshooting) * [Build with Tevm Node](/core/create-tevm-node) * [Examples repo](https://github.com/evmts/tevm-monorepo/tree/main/examples) ## Getting Started with Ethers.js :::note Tevm is built around viem but provides **full compatibility** with [ethers.js](https://docs.ethers.org/v6/) via its EIP-1193 provider interface. ::: ### Installation ::::steps #### Install Dependencies :::code-group ```bash [npm] npm install tevm ethers@latest ``` ```bash [pnpm] pnpm add tevm ethers@latest ``` ```bash [yarn] yarn add tevm ethers@latest ``` ```bash [bun] bun add tevm ethers@latest ``` ::: #### Create Tevm Client ```ts import { createMemoryClient, http } from "tevm"; import { optimism } from "tevm/common"; import { requestEip1193 } from "tevm/decorators"; const client = createMemoryClient({ fork: { transport: http("https://mainnet.optimism.io"), common: optimism, }, }); // Enable EIP-1193 compatibility for ethers client.transport.tevm.extend(requestEip1193()); await client.tevmReady(); ``` #### Connect Ethers ```ts import { BrowserProvider, Wallet } from "ethers"; const provider = new BrowserProvider(client.transport.tevm); const signer = Wallet.createRandom().connect(provider); await client.setBalance({ address: signer.address, value: 1000000000000000000n, // 1 ETH }); ``` #### Start Using Ethers ```ts const blockNumber = await provider.getBlockNumber(); console.log(`Current block: ${blockNumber}`); const balance = await provider.getBalance(signer.address); console.log(`Wallet balance: ${balance.toString()}`); ``` :::: ### Complete Example ```ts showLineNumbers {3,12-13,27-28,47-48,66} filename="ethers-with-tevm.ts" import { createMemoryClient, http, parseAbi } from "tevm"; import { optimism } from "tevm/common"; import { requestEip1193 } from "tevm/decorators"; // [!code focus] import { BrowserProvider, Wallet, Contract, formatEther } from "ethers"; import { parseUnits } from "ethers/utils"; const client = createMemoryClient({ fork: { transport: http("https://mainnet.optimism.io"), common: optimism, }, }); client.transport.tevm.extend(requestEip1193()); // [!code focus] await client.tevmReady(); const provider = new BrowserProvider(client.transport.tevm); const signer = Wallet.createRandom().connect(provider); const blockNumber = await provider.getBlockNumber(); console.log(`Current block number: ${blockNumber}`); const greeterContractAddress = "0x10ed0b176048c34d69ffc0712de06CbE95730748"; // [!code focus] const greeterAbi = parseAbi([ "function greet() view returns (string)", "function setGreeting(string memory _greeting) public", ]); const greeter = new Contract(greeterContractAddress, greeterAbi, signer); const getGreeting = async () => await greeter.greet(); const setGreeting = async (newGreeting) => { const tx = await greeter.setGreeting(newGreeting); return tx.hash; }; await client.setBalance({ address: signer.address, value: parseUnits("1.0", "ether"), }); console.log( `Wallet funded with: ${formatEther(await provider.getBalance(signer.address))} ETH`, ); console.log(`Original greeting: ${await getGreeting()}`); const txHash = await setGreeting("Hello from ethers.js and Tevm!"); // [!code focus] console.log(`Transaction sent: ${txHash}`); await client.mine({ blocks: 1 }); console.log(`Updated greeting: ${await getGreeting()}`); ``` :::warning Tevm requires manual mining — call `client.mine()` after each transaction. ::: :::details[Understanding the Code] **EIP-1193 Decorator** — `client.transport.tevm.extend(requestEip1193())` exposes the standard provider interface ethers expects. **Provider Creation** — `new BrowserProvider(client.transport.tevm)` accepts the decorated Tevm node as a standard Ethereum provider. **Hybrid API** — use Ethers for contract interaction (`Contract`, `provider`, `signer`) and Tevm for state control (`client.setBalance()`, `client.mine()`). **Mining Control** — explicit `client.mine({ blocks: 1 })` gives full timing control. ::: ### Common Patterns #### Contract Deployment ```ts showLineNumbers import { createMemoryClient } from "tevm"; import { requestEip1193 } from "tevm/decorators"; import { BrowserProvider, Wallet, ContractFactory, formatEther } from "ethers"; const client = createMemoryClient(); client.transport.tevm.extend(requestEip1193()); await client.tevmReady(); const provider = new BrowserProvider(client.transport.tevm); const signer = Wallet.createRandom().connect(provider); await client.setBalance({ address: signer.address, value: 10000000000000000000n, // 10 ETH }); console.log( `Wallet funded with: ${formatEther(await provider.getBalance(signer.address))} ETH`, ); const counterAbi = [ "function count() view returns (uint256)", "function increment() public", ]; const counterBytecode = "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220d5fb46adf6ce0cfd90fa4324ffd8c48b0fc6fb6c4cac9ca2c69c97e25f355c9d64736f6c63430008110033"; const counterFactory = new ContractFactory(counterAbi, counterBytecode, signer); const counterDeploy = await counterFactory.deploy(); console.log(`Deployment transaction: ${counterDeploy.deploymentTransaction().hash}`); await client.mine({ blocks: 1 }); const counter = await counterDeploy.waitForDeployment(); const counterAddress = await counter.getAddress(); console.log(`Counter deployed at: ${counterAddress}`); console.log(`Initial count: ${await counter.count()}`); const tx = await counter.increment(); console.log(`Increment transaction: ${tx.hash}`); await client.mine({ blocks: 1 }); console.log(`New count: ${await counter.count()}`); ``` #### Reading and Writing Contract Data ```ts import { createMemoryClient, http, parseAbi } from "tevm"; import { mainnet } from "tevm/common"; import { requestEip1193 } from "tevm/decorators"; import { BrowserProvider, Wallet, Contract, formatEther, formatUnits, } from "ethers"; const client = createMemoryClient({ fork: { transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"), common: mainnet, }, }); client.transport.tevm.extend(requestEip1193()); await client.tevmReady(); const provider = new BrowserProvider(client.transport.tevm); const signer = Wallet.createRandom().connect(provider); await client.setBalance({ address: signer.address, value: 100000000000000000000n, // 100 ETH }); const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; const usdcAbi = parseAbi([ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function totalSupply() view returns (uint256)", "function balanceOf(address owner) view returns (uint256)", ]); const usdc = new Contract(usdcAddress, usdcAbi, provider); const [name, symbol, decimals, totalSupply] = await Promise.all([ usdc.name(), usdc.symbol(), usdc.decimals(), usdc.totalSupply(), ]); console.log(`Contract: ${name} (${symbol})`); console.log(`Decimals: ${decimals}`); console.log(`Total Supply: ${formatUnits(totalSupply, decimals)} ${symbol}`); const daiAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; const daiUsdcBalance = await usdc.balanceOf(daiAddress); console.log( `DAI contract's USDC balance: ${formatUnits(daiUsdcBalance, decimals)} ${symbol}`, ); ``` #### Working with Events ```ts import { createMemoryClient } from "tevm"; import { requestEip1193 } from "tevm/decorators"; import { BrowserProvider, Wallet, Contract, formatUnits } from "ethers"; import { parseAbi } from "tevm"; const client = createMemoryClient(); client.transport.tevm.extend(requestEip1193()); await client.tevmReady(); const provider = new BrowserProvider(client.transport.tevm); const signer = Wallet.createRandom().connect(provider); await client.setBalance({ address: signer.address, value: 10000000000000000000n, }); const tokenAbi = parseAbi([ "constructor(string name, string symbol, uint8 decimals)", "function transfer(address to, uint256 amount) returns (bool)", "function balanceOf(address owner) view returns (uint256)", "event Transfer(address indexed from, address indexed to, uint256 value)", ]); const tokenBytecode = "0x608060405234801561001057600080fd5b506040516107fa3803806107fa83398101604081905261002f91610215565b600380546001600160a01b031916331790558151610052906004906020850190610076565b5081516100669060059060208401906100fd565b506006805460ff191660ff929092169190911790555061030c565b828054610082906102c8565b90600052602060002090601f0160209004810192826100a4576000855561010a565b82601f106100bd57805160ff19168380011785556100ea565b828001600101855582156100ea579182015b828111156100ea5782518255916020019190600101906100cf565b506100f69291506100f6565b5090565b5b808211156100f657600081556001016100f7565b828054610109906102c8565b90600052602060002090601f01602090048101928261012b5760008552610167565b82601f1061014457805160ff1916838001178555610171565b82800160010185558215610171579182015b82811115610171578251825591602001919060010190610156565b5061017d9291506101bd565b5090565b5b8082111561017d576000815560010161017e565b6000602082840312156101a657600080fd5b81516001600160a01b03811681146101bd57600080fd5b9392505050565b5b8082111561017d57600081556001016101be565b80516001600160a01b03811681146101eb57600080fd5b919050565b600080600060608486031215610205578283fd5b833590925060208401359160408401359050509250925092565b60008060006060848603121561022a578081fd5b835160208501516040860151919450919290910181111581146102ad57634e487b7160e01b600052601160045260246000fd5b809150509250925092565b634e487b7160e01b600052602260045260246000fd5b600181811c908216806102dc57607f821691505b6020821081036102fc57634e487b7160e01b600052602260045260246000fd5b50919050565b6104df8061031b6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806370a082311461004657806395d89b4114610079578063a9059cbb14610081575b600080fd5b610066610054366004610356565b6001600160a01b031660009081526001602052604090205490565b60405190815260200160405180910390f35b61007b610094565b005b61007b61008f366004610379565b610121565b6005805461009f906104a0565b80601f01602080910402602001604051908101604052809291908181526020018280546100cb906104a0565b80156101185780601f106100ed57610100808354040283529160200191610118565b820191906000526020600020905b8154815290600101906020018083116100fb57829003601f168201915b505050505081565b6001600160a01b03821660009081526001602052604081205461014a908363ffffffff61022916565b6001600160a01b038416600090815260016020526040812091909155610177908263ffffffff61024116565b6001600160a01b0383166000908152600160205260409020556101d0816040518060600160405280602381526020016104876023913960405180604001604052806029815260200161045e60299139600090339161025a565b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610216918252602082015260400190565b60405180910390a35050565b600081831061023857506000610239565b5b92915050565b6000828201610239825b8351602g2b6020600020905b905090565b60408051808201909152602d8152600080516020610457833981519152602082015290565b9050919050565b841515610297576001600160e01b031916600052601160045260246000fd5b5063ffffffff1690565b6000602082840312156102b357600080fd5b81356001600160a01b03811681146102ca57600080fd5b9392505050565b6000602082840312156102e357600080fd5b813560fc81168114t12870be52600052602260045260246000d5b602082108a5734e487b7160q0b600052602260045260246000fd5b50919050565b6004f3fe68747470733a2f2f6769646c6572732e696f2f6a616d657368756768657369o97365722f7468696e67736e6f626f6479656c7365686173a2646970667358221220f3acb75fca24514561f12a53c4a92042a9a6c524895dc1b01f3c3d2cda5d8ff364736f6c634300080a0033"; const tokenFactory = new ContractFactory(tokenAbi, tokenBytecode, signer); const token = await tokenFactory.deploy("Test Token", "TST", 18); await client.mine({ blocks: 1 }); const tokenAddress = await token.getAddress(); console.log(`Token deployed at: ${tokenAddress}`); token.on("Transfer", (from, to, value, event) => { console.log(`Transfer detected in block ${event.blockNumber}:`); console.log(` From: ${from}`); console.log(` To: ${to}`); console.log(` Value: ${formatUnits(value, 18)} TST`); }); const recipient = Wallet.createRandom().address; const tx = await token.transfer(recipient, 1000000000000000000n); // 1 TST console.log(`Transfer transaction: ${tx.hash}`); await client.mine({ blocks: 1 }); const balance = await token.balanceOf(recipient); console.log(`Recipient balance: ${formatUnits(balance, 18)} TST`); token.removeAllListeners(); ``` ### Key Differences from Standard Ethers Usage :::warning **Mining is manual** — transactions won't be auto-mined; call `client.mine()`. ::: 1. **Transaction Flow** — Real network: auto-mined. Tevm: call `client.mine()`. 2. **Account Management** — Real: real funds and nonces. Tevm: fund on demand via `client.setBalance()`. 3. **Environment Control** — Real: limited state manipulation. Tevm: full control. 4. **Performance** — Real: network calls + confirmations. Tevm: local, near-instant. 5. **Debugging** — Real: limited. Tevm: step-by-step EVM tracing. 6. **Determinism** — Real: variable. Tevm: 100% deterministic. ### Advanced Features :::steps #### EVM State Control ```ts await client.setBalance({ address: "0x123...", value: parseUnits("1000", "ether"), }); await client.setStorageAt({ address: contractAddress, index: 0, value: "0x1234...", }); await client.setNextBlockTimestamp(Date.now()); ``` #### Contract Debugging ```ts const tevmNode = await client.getTevmNode(); const vm = await tevmNode.getVm(); vm.evm.events.on("step", (data, next) => { console.log(`EVM Step: ${data.opcode.name}`); next(); }); ``` #### Fork Management ```ts await client.reset({ fork: { blockNumber: 42069n, transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"), }, }); ``` ::: ### Next Steps * [Complete Ethers.js integration guide](../examples/ethers) * [Forking capabilities](../core/forking) * [EVM state management](../core/managing-state) * [Direct Solidity imports](./bundler) * [What is Tevm Node?](../introduction/what-is-tevm-node) * [Creating a Tevm Node](../core/create-tevm-node) * [JSON-RPC interface](../api/json-rpc) * [Contract Bundler](../reference/bundler) * [Forking from mainnet](../examples/forking-mainnet) * [Custom precompiles](../advanced/custom-precompiles) * [Building a debugger UI](../examples/debugger-ui) :::warning[Default Manual Mining Mode] Tevm defaults to **manual mining**. Transactions land in the mempool but are not included in blocks until you mine. * With `client.sendTransaction()` or `tevmCall` with `addToMempool: true`, call `client.tevmMine()` to include the tx. * Use `addToBlockchain: true` for immediate inclusion (auto-mines). * See [Mining Modes](../core/mining-modes). ::: ### Getting Started ::::steps #### Install Tevm and Zod :::code-group ```bash [npm] npm install tevm zod ``` ```bash [pnpm] pnpm add tevm zod ``` ```bash [yarn] yarn add tevm zod ``` ```bash [bun] bun add 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 ```tsx filename="app/ralph-loop.mdx" type RalphLoopPageProps = { name?: string; rounds?: number; }; export default async function RalphLoopPage(props: RalphLoopPageProps) { const lines = await runRalphLoop(props); return
{lines.join("\n")}
; } ``` #### Add the imports ```tsx filename="app/ralph-loop.mdx" import { createMemoryClient, PREFUNDED_ACCOUNTS } from "tevm"; import { SimpleContract } from "tevm/contract"; import { z } from "zod"; ``` #### Define the Zod schemas ```tsx filename="app/ralph-loop.mdx" 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; ``` #### Run the Ralph loop ```tsx filename="app/ralph-loop.mdx" 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: ```ts import { createMemoryClient } from "tevm"; const memoryClient = createMemoryClient(); const blockNumber = await memoryClient.getBlockNumber(); ``` :::details[How it works] 1. `memoryClient` runs an Ethereum node entirely in memory (TypeScript + Wasm). 2. Query it via the [`viem` api](https://viem.sh). ::: ##### Fork client Fork an existing chain (like Anvil/Hardhat): ```typescript 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, }, }); ``` :::details[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. ```typescript 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, }, }); ``` :::details[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: ```typescript 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): ```typescript 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. ```ts showLineNumbers {1,11-13,22} filename="simple-contract.ts" 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 ``` :::details[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 Interface** — [viem](https://viem.sh) 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 ```ts // 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](https://viem.sh). Essentials: * `Clients` — `createClient`, `createPublicClient`, `createWalletClient`, `createTestClient`, `createMemoryClient`. The main abstraction. * `Actions` — `getBlockNumber`, `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? * [Viem Quickstart](./viem) * [Ethers Quickstart](./ethers) * [Bundler Quickstart](./bundler) * [What is Tevm Node?](../introduction/what-is-tevm-node) ## Community Here's what developers and industry leaders are saying about Tevm. ### Community Highlights :::tip[Developer Feedback] Tevm's approach of bringing Ethereum execution directly to JavaScript environments has resonated with developers. They report problems previously difficult being simple to solve with Tevm. ::: > "Fully @tevmtools pilled now. The beauty and harmony of the dev tooling 😍." — EulerLagrange.eth - ver/acc, [@Euler\_\_Lagrange](https://twitter.com/Euler__Lagrange) > "If you're building a blockchain application on the web, I'm almost certain there is a use case for Tevm. It might change everything, and at worse it would most likely improve your devX." — polarzero, [@0xpolarzero](https://twitter.com/0xpolarzero) > "Yeah, using bundling tools like Tevm with Viem offers a very nice experience here." — jxom, [@\_jxom](https://twitter.com/_jxom) > "Incredible! And also very cursed haha" — Darryl Yeo・d/acc, [@darryl\_\_yeo](https://twitter.com/darryl__yeo) > "tevm is the accessible version of reth exex for data engineers" — ilemi, [@andrewhong5297](https://twitter.com/andrewhong5297) > "Helios 🤝 Tevm" — ncitron.eth, [@NoahCitron](https://twitter.com/NoahCitron) > "Please tell me this is a shitpost" — Patrick Collins, [@PatrickAlphaC](https://twitter.com/PatrickAlphaC) > "I don't know if I should be impressed or scared" — James, [@lcm\_in\_pangea](https://twitter.com/lcm_in_pangea) ### Share Your Experience :::steps #### Join the Community * [Telegram Group](https://t.me/+ANThR9bHDLAwMjUx) * [Twitter/X](https://twitter.com/tevmtools) #### Report Success Stories * [Create an issue](https://github.com/evmts/tevm-monorepo/issues/new?labels=testimonial\&template=testimonial.md) with the "testimonial" label * Email [support@tevm.sh](mailto\:support@tevm.sh) ::: Your feedback helps us improve Tevm and guides our development priorities. ## Getting Started with Viem :::tip[Perfect for Viem Users] If you already use [viem](https://viem.sh), Tevm's API is nearly identical. ::: ### Installation ::::steps #### Install Dependencies :::code-group ```bash [npm] npm install tevm viem@latest ``` ```bash [pnpm] pnpm add tevm viem@latest ``` ```bash [yarn] yarn add tevm viem@latest ``` ```bash [bun] bun add tevm viem@latest ``` ::: #### Create Your Client Memory client: ```ts import { createMemoryClient } from "tevm"; const client = createMemoryClient(); ``` Or fork an existing chain: ```ts 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`](https://github.com/evmts/tevm-monorepo/blob/main/packages/memory-client/src/createMemoryClient.js) 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 ```ts const blockNumber = await client.getBlockNumber(); console.log(`Current block: ${blockNumber}`); ``` :::: ### Complete Example :::code-group ```ts [Forking Example] showLineNumbers {1-3,8-12,25-29,40,52} import { createMemoryClient, http } from "tevm"; // [!code focus] import { optimism } from "tevm/common"; // [!code focus] import { parseAbi, parseEther } from "viem"; // [!code focus] const client = createMemoryClient({ fork: { transport: http("https://mainnet.optimism.io"), // [!code focus] common: optimism, // [!code focus] }, // [!code focus] }); // [!code focus] 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)", // [!code focus] "function setGreeting(string memory _greeting) public", // [!code focus] ]); // [!code focus] 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({ // [!code focus] account, address: greeterContractAddress, abi: greeterAbi, functionName: "setGreeting", args: ["Hello from Tevm!"], chain: optimism, }); console.log(`Transaction sent: ${txHash}`); await client.mine({ blocks: 1 }); // [!code focus] const updatedGreeting = await client.readContract({ address: greeterContractAddress, abi: greeterAbi, functionName: "greet", }); console.log(`Updated greeting: ${updatedGreeting}`); ``` ```ts [Contract Deployment] showLineNumbers import { createMemoryClient } from "tevm"; import { parseAbi, parseEther, encodeAbiParameters } from "viem"; const client = createMemoryClient(); const deployerAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; await client.setBalance({ address: deployerAddress, value: parseEther("10"), }); const counterAbi = parseAbi([ "function increment() public", "function count() view returns (uint256)", ]); const counterBytecode = "0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220d5fb46adf6ce0cfd90fa4324ffd8c48b0fc6fb6c4cac9ca2c69c97e25f355c9d64736f6c63430008110033"; const deployHash = await client.sendTransaction({ account: deployerAddress, to: undefined, data: counterBytecode, }); await client.mine(); const receipt = await client.getTransactionReceipt({ hash: deployHash }); const contractAddress = receipt.contractAddress; console.log(`Contract deployed at: ${contractAddress}`); const initialCount = await client.readContract({ address: contractAddress, abi: counterAbi, functionName: "count", }); console.log(`Initial count: ${initialCount}`); await client.writeContract({ account: deployerAddress, address: contractAddress, abi: counterAbi, functionName: "increment", }); await client.mine(); const newCount = await client.readContract({ address: contractAddress, abi: counterAbi, functionName: "count", }); console.log(`New count: ${newCount}`); ``` ::: :::details[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 :::code-group ```ts [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 ``` ```ts [Tevm Extensions] await client.tevmReady(); await client.setBalance({ address: "0x...", value: 100n }); await client.setCode({ address: "0x...", bytecode: "0x..." }); await client.mine({ blocks: 1 }); await client.reset(); await client.impersonateAccount({ address: "0x..." }); ``` ```ts [EVM Debugging] await client.tevmContract({ address: '0x...', abi: [...], functionName: 'transfer', args: ['0x...', 100n], onStep: (data, next) => { console.log(`Opcode: ${data.opcode.name}`) console.log(`Stack: ${data.stack.join(', ')}`) next() } }) ``` ::: ### Common Patterns and Best Practices #### Creating multiple clients Commonly you'll use a viem client and a tevm client side by side: ```typescript 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: ```typescript 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. ```typescript 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: ```ts showLineNumbers {1-3,6-11} import { createClient, http } from "viem"; import { createTevmTransport } from "tevm"; // [!code focus] import { tevmCall, tevmDumpState } from "tevm/actions"; // [!code focus] const client = createClient({ transport: createTevmTransport({ // [!code focus] fork: { // [!code focus] transport: http("https://mainnet.optimism.io"), // [!code focus] }, // [!code focus] }), // [!code focus] }); 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)`. :::tip[Tree-Shaking Benefit] Significantly reduces bundle size when using only a subset of Tevm's features. ::: ### Using viem to talk to Tevm over HTTP Tevm can also run as an HTTP server (useful as an Anvil-like testing tool). CLI: ```bash npx tevm serve --fork-url https://mainnet.optimism.io ``` Or run as an HTTP/Express/Hono/Next.js server in Node.js or Bun: ```typescript 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 | :::note All tevm-specific actions are also available as individual imports from `tevm/actions` for tree-shaking. ::: ### Hook into the EVM Hook directly into EVM execution via `tevmCall` and `tevmContract`: ```ts showLineNumbers {7-16} await client.tevmContract({ address: greeterContractAddress, abi: greeterAbi, functionName: "setGreeting", args: ["Hello!"], onStep: (stepInfo, next) => { // [!code focus] console.log(`Executing: ${stepInfo.opcode.name} at PC=${stepInfo.pc}`); // [!code focus] console.log(`Stack: ${stepInfo.stack.map((val) => val.toString())}`); // [!code focus] console.log(`Memory: ${stepInfo.memory.toString("hex")}`); // [!code focus] next?.(); // [!code focus] }, // [!code focus] 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 * [Forking capabilities](/core/forking) * [State management](/core/managing-state) * [Mining modes](/core/mining-modes) * [Direct Solidity imports](/getting-started/bundler) * [Forking mainnet example](/examples/forking-mainnet) * [Building a debugger UI](/examples/debugger-ui) * [Local testing flows](/examples/local-testing) * [EVM events & hooks](/api/evm-events) * [Custom precompiles](/advanced/custom-precompiles) * [Transaction pool management](/advanced/txpool) * [Contract Bundler](/reference/bundler) ## Building a Debugger UI :::warning These docs have not been checked for correctness yet. Use with caution. ::: A minimal EVM debugger using [Svelte](https://svelte.dev) and Tevm Node. Shows live opcode execution, stack, memory, errors, and gas usage. ### Project Setup ```bash npm create vite@latest tevm-debugger -- --template svelte-ts cd tevm-debugger npm install tevm tevm/contract ``` ### Components #### EVMDebugger.svelte ```svelte
Gas Used: {gasUsed.toString()}

Current Step

{#if currentStep}
PC: {currentStep.pc}
Opcode: {currentStep.opcode.name}
Gas Left: {currentStep.gasLeft.toString()}
Depth: {currentStep.depth}
{/if}

Stack

{#if currentStep?.stack}
{#each currentStep.stack as item}
{item.toString(16)}
{/each}
{/if}

Errors

{#each errors as error}
{error}
{/each}

Execution History ({steps.length} steps)

{#each steps as step}
{step.opcode.name} (Gas: {step.gasLeft.toString()})
{/each}
``` #### App.svelte ```svelte

Tevm Debugger

``` ### Advanced Features #### Memory Viewer Component ```svelte
{#each rows as row, i}
{(startOffset + i * bytesPerRow).toString(16).padStart(8, '0')}: {#each row as byte}{formatByte(byte)}{/each} {#each row as byte}{formatAscii(byte)}{/each}
{/each}
``` #### Storage Viewer Component ```svelte
{#if storage.size > 0}
{#each [...storage] as [slot, value]}
{slot}: {value}
{/each}
{/if}
``` ### Usage Project structure: ``` tevm-debugger/ ├── src/ │ ├── lib/ │ │ ├── EVMDebugger.svelte │ │ ├── MemoryViewer.svelte │ │ └── StorageViewer.svelte │ ├── App.svelte │ └── main.ts └── package.json ``` Run `npm run dev`, then use the debugger: ```ts import { createImpersonatedTx } from 'tevm/tx' const deployTx = createImpersonatedTx({ data: bytecode }) await vm.runTx({ tx: deployTx }) const callTx = createImpersonatedTx({ to: '0x...', data: '0x...' }) await vm.runTx({ tx: callTx }) ``` ### Customization #### Adding Transaction History ```svelte

Transaction History

{#each $transactions as tx}
Hash: {tx.hash}
To: {tx.to}
Data: {tx.data}
{/each}
``` #### Adding Gas Profiling ```svelte

Gas Profile

{#each [...gasProfile] as [opcode, stats]} {/each}
OpcodeCountTotal Gas
{opcode}{stats.count}{stats.totalGas.toString()}
``` ### Related * [EVM Events](../api/evm-events) * [Performance Profiler](../advanced/performance-profiler) ## Using with Ethers.js v6 Tevm works with Ethers.js through Tevm's EIP-1193 provider interface. Use Tevm for local chain control, state setup, and mining; use ethers for providers, wallets, and contract instances. ### Install :::code-group ```bash [npm] npm install tevm ethers ``` ```bash [pnpm] pnpm add tevm ethers ``` ```bash [yarn] yarn add tevm ethers ``` ```bash [bun] bun add tevm ethers ``` ::: ### Create a Provider Create a memory client, extend its underlying Tevm node with the EIP-1193 request interface, then pass that node to ethers' `BrowserProvider`. ```ts import { createMemoryClient } from 'tevm' import { requestEip1193 } from 'tevm/decorators' import { BrowserProvider } from 'ethers' const client = createMemoryClient() client.transport.tevm.extend(requestEip1193()) await client.tevmReady() const provider = new BrowserProvider(client.transport.tevm, undefined, { // Ethers caches JSON-RPC reads briefly by default. Disable for // deterministic local tests where you manually mine blocks. cacheTimeout: -1, }) console.log(`Connected to block ${await provider.getBlockNumber()}`) ``` ### Fund a Wallet ```ts import { Wallet, formatEther, parseEther } from 'ethers' const signer = Wallet.createRandom().connect(provider) await client.setBalance({ address: signer.address, value: parseEther('10') }) const balance = await provider.getBalance(signer.address) console.log(`Wallet balance: ${formatEther(balance)} ETH`) ``` ### Deploy and Call a Contract Deploys a small compiled counter contract, mines the deployment, then calls it through an ethers `Contract` instance. ```ts import { ContractFactory } from 'ethers' const counterAbi = [ 'function number() view returns (uint256)', 'function increment() public', ] as const const counterBytecode = '0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220d5fb46adf6ce0cfd90fa4324ffd8c48b0fc6fb6c4cac9ca2c69c97e25f355c9d64736f6c63430008110033' const factory = new ContractFactory(counterAbi, counterBytecode, signer) const deployment = await factory.deploy() await client.mine({ blocks: 1 }) const counter = await deployment.waitForDeployment() console.log(`Counter deployed at ${await counter.getAddress()}`) console.log(`Initial count: ${await counter.number()}`) ``` ### Send a Transaction Transactions sent by ethers go through `eth_sendRawTransaction` and enter Tevm's mempool. Mine a block before waiting for the receipt. ```ts const tx = await counter.increment() await client.mine({ blocks: 1 }) await tx.wait() console.log(`Updated count: ${await counter.number()}`) ``` ### Complete Example ```ts filename="ethers-with-tevm.ts" import { createMemoryClient } from 'tevm' import { requestEip1193 } from 'tevm/decorators' import { BrowserProvider, ContractFactory, Wallet, formatEther, parseEther } from 'ethers' const client = createMemoryClient() client.transport.tevm.extend(requestEip1193()) await client.tevmReady() const provider = new BrowserProvider(client.transport.tevm, undefined, { cacheTimeout: -1 }) const signer = Wallet.createRandom().connect(provider) await client.setBalance({ address: signer.address, value: parseEther('10') }) console.log(`Wallet balance: ${formatEther(await provider.getBalance(signer.address))} ETH`) const counterAbi = [ 'function number() view returns (uint256)', 'function increment() public', ] as const const counterBytecode = '0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220d5fb46adf6ce0cfd90fa4324ffd8c48b0fc6fb6c4cac9ca2c69c97e25f355c9d64736f6c63430008110033' const factory = new ContractFactory(counterAbi, counterBytecode, signer) const deployment = await factory.deploy() await client.mine({ blocks: 1 }) const counter = await deployment.waitForDeployment() console.log(`Counter deployed at ${await counter.getAddress()}`) console.log(`Initial count: ${await counter.number()}`) const tx = await counter.increment() await client.mine({ blocks: 1 }) await tx.wait() console.log(`Updated count: ${await counter.number()}`) ``` ### Notes * Use `client.setBalance()`, `client.setStorageAt()`, `client.mine()`, and other Tevm test actions to control the local chain. * Use ethers providers, wallets, factories, and contracts for application code that already depends on ethers. * Tevm does not mine ethers transactions until you call `client.mine()` unless you configure automining. ## Forking Mainnet Example :::tip Set `MAINNET_RPC_URL` to a mainnet RPC endpoint before running these examples. ::: ### Basic Fork Setup Unknown accounts/contracts fetch from the remote on demand and cache locally. ```ts import { createTevmNode, http } from 'tevm' const rpcUrl = process.env.MAINNET_RPC_URL if (!rpcUrl) throw new Error('MAINNET_RPC_URL is required') const node = createTevmNode({ fork: { transport: http(rpcUrl)({}), }, loggingLevel: 'debug', }) await node.ready() ``` ### Account Impersonation ```ts import { callHandler } from 'tevm/actions' const result = await callHandler(node)({ from: '0x28C6c06298d514Db089934071355E5743bf21d60', to: '0x1234567890123456789012345678901234567890', value: 1000000000000000000n, // 1 ETH skipBalance: true, throwOnFail: false, }) ``` ### Working with Forked Contracts ```ts import { callHandler } from 'tevm/actions' import { encodeFunctionData, parseAbi } from 'viem' const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' const HOLDER_ADDRESS = '0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503' const RECIPIENT_ADDRESS = '0x1234567890123456789012345678901234567890' const ERC20_ABI = parseAbi([ 'function balanceOf(address) view returns (uint256)', 'function transfer(address to, uint256 amount) returns (bool)', ]) const balance = await callHandler(node)({ to: USDC_ADDRESS, data: encodeFunctionData({ abi: ERC20_ABI, functionName: 'balanceOf', args: [HOLDER_ADDRESS], }), }) // Simulate a local write; the remote chain is not affected. const transfer = await callHandler(node)({ from: HOLDER_ADDRESS, to: USDC_ADDRESS, data: encodeFunctionData({ abi: ERC20_ABI, functionName: 'transfer', args: [RECIPIENT_ADDRESS, 1n], }), skipBalance: true, throwOnFail: false, }) ``` ### Fork at Specific Block ```ts const node = createTevmNode({ fork: { transport: http(rpcUrl)({}), blockTag: 23_483_670n, }, }) const vm = await node.getVm() const block = await vm.blockchain.getBlock(23_483_670n) ``` ### Multiple Network Support ```ts const optimismNode = createTevmNode({ fork: { transport: http(process.env.OPTIMISM_RPC_URL!)({}) }, }) const arbitrumNode = createTevmNode({ fork: { transport: http(process.env.ARBITRUM_RPC_URL!)({}) }, }) ``` **Related** * [Forking Guide](../core/forking) * [State Management](../core/managing-state) * [JSON-RPC Support](../api/json-rpc) ## Local Testing :::warning These docs have not been checked for correctness yet. Use with caution. ::: Use Tevm Node for local testing of [smart contracts](https://ethereum.org/en/developers/docs/smart-contracts/) and [transactions](https://ethereum.org/en/developers/docs/transactions/). For background, see the [Smart Contract Testing Guide](https://ethereum.org/en/developers/docs/smart-contracts/testing/). ### Basic Test Setup ```ts import { createTevmNode } from 'tevm' import { createImpersonatedTx } from 'tevm/tx' import { expect, test } from 'vitest' // or jest, mocha, etc. test('Basic ETH transfer', async () => { const node = createTevmNode({ miningConfig: { type: 'auto' } }) await node.ready() const vm = await node.getVm() const tx = createImpersonatedTx({ from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', value: 1000000000000000000n, // 1 ETH }) const result = await vm.runTx({ tx }) expect(result.execResult.exceptionError).toBeUndefined() const account = await vm.stateManager.getAccount(tx.to) expect(account.balance).toBe(1000000000000000000n) }) ``` See the [EVM Execution Model](https://ethereum.org/en/developers/docs/evm/execution/) for more on transaction execution. ### Contract Testing #### Deployment & Interaction See the [Contract Deployment Guide](https://ethereum.org/en/developers/docs/smart-contracts/deploying/) for background. ```ts import { Contract } from 'tevm/contract' import { bytecode, abi } from './MyContract.json' test('Deploy and interact with contract', async () => { const node = createTevmNode() await node.ready() const vm = await node.getVm() const deployTx = createImpersonatedTx({ data: bytecode }) const result = await vm.runTx({ tx: deployTx }) expect(result.execResult.exceptionError).toBeUndefined() const contractAddress = result.createdAddress expect(contractAddress).toBeDefined() const contract = new Contract(contractAddress, abi) const callResult = await contract.read.getValue() expect(callResult).toBe(expectedValue) const tx = await contract.write.setValue([newValue]) const txResult = await vm.runTx({ tx }) expect(txResult.execResult.exceptionError).toBeUndefined() const updatedValue = await contract.read.getValue() expect(updatedValue).toBe(newValue) }) ``` #### Event Testing See the [Events and Logs Guide](https://ethereum.org/en/developers/docs/smart-contracts/anatomy/#events-and-logs) for background. ```ts test('Contract events', async () => { const node = createTevmNode() await node.ready() const contract = await deployContract(node) node.setFilter({ id: '0x1', address: contract.address, topics: [contract.interface.getEventTopic('ValueChanged')], }) const tx = await contract.write.setValue([123]) await vm.runTx({ tx }) const receipts = await node.getReceiptsManager() const logs = await receipts.getLogs({ fromBlock: 0n, toBlock: 'latest', address: contract.address, }) expect(logs.length).toBe(1) expect(logs[0].topics[0]).toBe(contract.interface.getEventTopic('ValueChanged')) }) ``` ### Complex Scenarios #### State Management See the [Ethereum State Guide](https://ethereum.org/en/developers/docs/evm/state-machine/). ```ts test('Complex state changes', async () => { const node = createTevmNode() await node.ready() const vm = await node.getVm() await vm.stateManager.checkpoint() try { await performStateChanges(vm) const intermediateState = await getState(vm) expect(intermediateState).toMatchSnapshot() await performMoreChanges(vm) await vm.stateManager.commit() } catch (error) { await vm.stateManager.revert() throw error } }) ``` #### Fork Testing See the [Forking Guide](../core/forking). ```ts test('Mainnet fork testing', async () => { const node = createTevmNode({ fork: { transport: http('https://mainnet.infura.io/v3/YOUR-KEY'), blockTag: 17_000_000n, }, }) await node.ready() node.setImpersonatedAccount('0x28C6c06298d514Db089934071355E5743bf21d60') const uniswap = new Contract(UNISWAP_ADDRESS, UNISWAP_ABI) const tx = await uniswap.write.swapExactTokensForTokens([/* ... */]) const result = await vm.runTx({ tx }) expect(result.execResult.exceptionError).toBeUndefined() }) ``` #### Time-based Testing See the [Block Time Guide](https://ethereum.org/en/developers/docs/blocks/blocks-and-time/). ```ts test('Time-dependent behavior', async () => { const node = createTevmNode({ miningConfig: { type: 'interval', interval: 1000 } }) await node.ready() const vm = await node.getVm() const contract = await deployTimeLock(vm) // Should fail let tx = await contract.write.withdraw() let result = await vm.runTx({ tx }) expect(result.execResult.exceptionError).toBeDefined() // Advance time for (let i = 0; i < 100; i++) { await vm.blockchain.putBlock(createBlock({ timestamp: Date.now() + i * 1000 })) } // Should succeed tx = await contract.write.withdraw() result = await vm.runTx({ tx }) expect(result.execResult.exceptionError).toBeUndefined() }) ``` ### Testing Utilities #### Account Management See the [Accounts Guide](https://ethereum.org/en/developers/docs/accounts/). ```ts async function setupAccounts(vm) { const accounts = [ '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ] for (const address of accounts) { await vm.stateManager.putAccount(address, { nonce: 0n, balance: 10000000000000000000n, // 10 ETH }) } return accounts } ``` #### Transaction Helpers See the [Transaction Types Guide](https://ethereum.org/en/developers/docs/transactions/transaction-types/). ```ts async function sendEth(vm, from, to, value) { const tx = createImpersonatedTx({ from, to, value }) return vm.runTx({ tx }) } async function deployContract(vm, bytecode, args = []) { const tx = createImpersonatedTx({ data: bytecode + encodeConstructor(args) }) const result = await vm.runTx({ tx }) return result.createdAddress } ``` ### Replaying Contracts with Shadow Events ```ts import { createTevmNode } from 'tevm' import { http } from 'viem' const node = createTevmNode({ fork: { transport: http('https://mainnet.infura.io/v3/YOUR-KEY') }, }) const receipt = await node.request({ method: 'eth_getTransactionReceipt', params: ['0x...'], // Original tx hash }) const block = await node.request({ method: 'eth_getBlockByNumber', params: [(receipt.blockNumber - 1n).toString(16), true], }) // Replay all transactions before our target tx for (let i = 0; i < receipt.transactionIndex; i++) { await node.getVm().runTx({ tx: block.transactions[i] }) } // Deploy modified contract with new event const modifiedBytecode = '0x...' await node.setAccount({ address: receipt.contractAddress, deployedBytecode: modifiedBytecode, }) // Run the target transaction (no createImpersonatedTx — these come from the block) const result = await node.getVm().runTx({ tx: block.transactions[receipt.transactionIndex], }) console.log(result.execResult.logs) ``` ### Estimating Gas for Token Approval ```ts import { createTevmNode } from 'tevm/node' import { createImpersonatedTx } from 'tevm/tx' import { encodeFunctionData } from 'viem' const node = createTevmNode() const vm = await node.getVm() const approveTx = createImpersonatedTx({ to: tokenAddress, data: encodeFunctionData({ abi: erc20ABI, functionName: 'approve', args: [spenderAddress, amount], }), }) const result = await vm.runTx({ tx: approveTx }) console.log('Gas used:', result.execResult.executionGasUsed) const transferFromTx = createImpersonatedTx({ to: tokenAddress, data: encodeFunctionData({ abi: erc20ABI, functionName: 'transferFrom', args: [ownerAddress, recipientAddress, amount], }), }) const transferResult = await vm.runTx({ tx: transferFromTx }) console.log('TransferFrom gas:', transferResult.execResult.executionGasUsed) ``` ### Related Topics * [Viem Testing Guide](https://viem.sh/docs/testing/overview.html) * [VM & Submodules](../api/vm-and-submodules) * [JSON-RPC Methods](../api/json-rpc) * [Contract reference](/reference/contract) * [State Management](../core/managing-state) ## Using with Viem Tevm integrates with [viem](https://viem.sh) as a local Ethereum environment with viem's familiar API surface. ### Integration Options Two approaches: :::code-group ```ts [Tree-shakable (Recommended)] // For production frontends — minimize bundle size import { createTevmTransport, tevmDumpState } from 'tevm' import { createClient } from 'viem' import { getBlockNumber } from 'viem/actions' const tevmTransport = createTevmTransport() const client = createClient({ transport: tevmTransport }) // Import viem actions individually await getBlockNumber(client) // Import tevm actions individually await tevmDumpState(client) ``` ```ts [Batteries-included] // All actions pre-attached — convenient for testing import { createMemoryClient } from 'tevm' const client = createMemoryClient() await client.getBlockNumber() await client.tevmDumpState() ``` ::: :::tip The tree-shakable approach enables smaller bundles since only imported actions are included. ::: ### Core Functionality * **Public Actions** — read blockchain state, query contracts, estimate gas ([below](#public-actions)) * **Wallet Actions** — send transactions, sign messages ([below](#wallet-actions)) * **Test Actions** — manipulate state for testing ([below](#test-actions)) * **Tevm Actions** — Tevm-specific EVM extensions ([below](#tevm-actions)) ### Public Actions Use [viem's public actions](https://viem.sh/docs/actions/public/introduction) to read from your local Tevm environment: ```ts import { createMemoryClient } from "tevm"; import { ERC20 } from "tevm/contract"; import { parseAbi } from "viem"; const client = createMemoryClient(); const tokenAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; const holderAddress = "0x1234567890123456789012345678901234567890"; const block = await client.getBlock(); console.log(`Block number: ${block.number}`); const balance = await client.getBalance({ address: holderAddress, }); const nonce = await client.getTransactionCount({ address: holderAddress, }); await client.setCode({ address: tokenAddress, bytecode: ERC20.deployedBytecode, }); const result = await client.readContract({ address: tokenAddress, abi: parseAbi(["function balanceOf(address) view returns (uint256)"]), functionName: "balanceOf", args: [holderAddress], }); ``` ### Wallet Actions Tevm supports all [viem wallet actions](https://viem.sh/docs/actions/wallet/introduction) with prefunded accounts: ```ts import { createMemoryClient, PREFUNDED_ACCOUNTS } from "tevm"; import { parseEther } from "viem"; const client = createMemoryClient({ account: PREFUNDED_ACCOUNTS[0], // 10000 ETH }); const hash = await client.sendTransaction({ to: "0x1234567890123456789012345678901234567890", value: parseEther("1"), }); const receipt = await client.waitForTransactionReceipt({ hash }); const { contractAddress } = await client.deployContract({ abi: parseAbi([ "function greet() view returns (string)", "function setGreeting(string) returns ()", ]), bytecode: "0x608060405234801561...", }); ```
Working with Custom Accounts ```ts import { createMemoryClient } from "tevm"; import { privateKeyToAccount } from "viem/accounts"; const account = privateKeyToAccount( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ); const client = createMemoryClient({ account }); await client.setBalance({ address: account.address, value: parseEther("10") }); const hash = await client.sendTransaction({ to: "0x1234567890123456789012345678901234567890", value: parseEther("1"), }); ```
### Test Actions All [viem test actions](https://viem.sh/docs/actions/test/introduction) are supported (Anvil/Hardhat compatible): ```ts import { createMemoryClient } from "tevm"; import { parseEther } from "viem"; const client = createMemoryClient(); await client.mine({ blocks: 5 }); await client.setBalance({ address: "0x1234567890123456789012345678901234567890", value: parseEther("100"), }); await client.setNextBlockTimestamp(1695311333n); await client.mine({ blocks: 1 }); // Snapshot and revert const snapshotId = await client.snapshot(); await client.setBalance({ address: "0x1234567890123456789012345678901234567890", value: parseEther("999"), }); await client.revert({ id: snapshotId }); const balance = await client.getBalance({ address: "0x1234567890123456789012345678901234567890", }); ``` ### Tevm Actions Tevm-specific actions for enhanced EVM control. #### Contract Interactions ```ts import { createMemoryClient } from "tevm"; import { ERC20 } from "tevm/contract"; import { parseAbi } from "viem"; const client = createMemoryClient(); const tokenAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; const holderAddress = "0x1234567890123456789012345678901234567890"; const result = await client.tevmContract({ abi: parseAbi(["function balanceOf(address) view returns (uint256)"]), address: tokenAddress, deployedBytecode: ERC20.deployedBytecode, functionName: "balanceOf", args: [holderAddress], }); // Low-level call const callResult = await client.tevmCall({ to: tokenAddress, deployedBytecode: ERC20.deployedBytecode, data: "0x70a08231000000000000000000000000" + holderAddress.slice(2), }); ``` #### Account Management ```ts import { createMemoryClient } from "tevm"; import { parseEther } from "viem"; const client = createMemoryClient(); const account = await client.tevmGetAccount({ address: "0x1234567890123456789012345678901234567890", }); // Set complex account state (EOA or contract) await client.tevmSetAccount({ address: "0xabcdef1234567890abcdef1234567890abcdef12", balance: parseEther("100"), nonce: 5n, deployedBytecode: "0x6080604052348015600e575f80fd5b00", state: { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000002", }, }); // Native ETH await client.tevmDeal({ account: "0x1234567890123456789012345678901234567890", amount: parseEther("10"), }); // ERC20 await client.tevmDeal({ erc20: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC account: "0x1234567890123456789012345678901234567890", amount: 1000000n, // 1 USDC (6 decimals) }); ``` #### State Management ```ts import { createMemoryClient } from "tevm"; const client = createMemoryClient(); const state = await client.tevmDumpState(); const savedState = await client.tevmDumpState(); await client.setBalance({ address: "0x1234567890123456789012345678901234567890", value: 123456789n, }); await client.tevmLoadState({ state: savedState }); await client.tevmMine({ blocks: 5 }); ``` :::warning `tevmDumpState` / `tevmLoadState` capture the complete VM state including the fork cache — more powerful than regular snapshots. ::: ### Inside the Memory Client A MemoryClient is a viem client with Tevm functionality. Build one from scratch: ```ts import { createTevmNode } from "tevm"; import { requestEip1193 } from "tevm/decorators"; import { tevmViemActions } from "tevm/memory-client"; import { http, custom, createClient, publicActions, testActions, walletActions } from "viem"; const rpcUrl = process.env.OPTIMISM_RPC_URL ?? "https://mainnet.optimism.io"; const forkTransport = http(rpcUrl)({}); const node = createTevmNode({ fork: { transport: forkTransport }, }).extend(requestEip1193()); const memoryClient = createClient({ transport: custom(node) }) .extend(tevmViemActions()) .extend(publicActions) .extend(walletActions) .extend(testActions({ mode: "anvil" })); ``` Key architectural components: 1. **EIP-1193 Compatibility Layer** — standard Ethereum provider interface 2. **In-Memory EVM** — full Ethereum Virtual Machine locally 3. **Viem Integration** — extends viem with EVM-specific capabilities ### Complete Action Reference
Public Actions — Read blockchain state ##### Contract Interactions * [`call`](https://viem.sh/docs/actions/public/call) — Call without sending a transaction * [`readContract`](https://viem.sh/docs/contract/readContract) — Read a constant/view method * [`simulateContract`](https://viem.sh/docs/contract/simulateContract) — Simulate a write without executing * [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas) — Estimate gas for a contract call * [`estimateGas`](https://viem.sh/docs/actions/public/estimateGas) — Estimate gas for a transaction * [`getBytecode`](https://viem.sh/docs/contract/getBytecode) — Get a contract's bytecode ##### Block & Transaction * [`getBlock`](https://viem.sh/docs/actions/public/getBlock) — Get a block by number or hash * [`getBlockNumber`](https://viem.sh/docs/actions/public/getBlockNumber) — Latest block number * [`getBlockTransactionCount`](https://viem.sh/docs/actions/public/getBlockTransactionCount) — Tx count for a block * [`getTransaction`](https://viem.sh/docs/actions/public/getTransaction) — Tx details by hash * [`getTransactionCount`](https://viem.sh/docs/actions/public/getTransactionCount) — Tx count (nonce) for address * [`getTransactionReceipt`](https://viem.sh/docs/actions/public/getTransactionReceipt) — Receipt by hash * [`waitForTransactionReceipt`](https://viem.sh/docs/actions/public/waitForTransactionReceipt) — Wait for mining ##### Account & Chain * [`getBalance`](https://viem.sh/docs/actions/public/getBalance) — Balance of an address * [`getChainId`](https://viem.sh/docs/actions/public/getChainId) — Chain ID * [`getGasPrice`](https://viem.sh/docs/actions/public/getGasPrice) — Current gas price * [`estimateFeesPerGas`](https://viem.sh/docs/actions/public/estimateFeesPerGas) — Estimate fees per gas unit * [`getStorageAt`](https://viem.sh/docs/actions/public/getStorageAt) — Storage slot value
Test Actions — Manipulate blockchain state ##### Block & Mining * [`mine`](https://viem.sh/docs/actions/test/mine) — Mine blocks * [`setAutomine`](https://viem.sh/docs/actions/test/setAutomine) — Enable/disable automatic mining * [`setIntervalMining`](https://viem.sh/docs/actions/test/setIntervalMining) — Mine at intervals * [`setBlockGasLimit`](https://viem.sh/docs/actions/test/setBlockGasLimit) — Block gas limit * [`setBlockTimestampInterval`](https://viem.sh/docs/actions/test/setBlockTimestampInterval) — Timestamp increment * [`setNextBlockBaseFeePerGas`](https://viem.sh/docs/actions/test/setNextBlockBaseFeePerGas) — Next block base fee * [`setNextBlockTimestamp`](https://viem.sh/docs/actions/test/setNextBlockTimestamp) — Next block timestamp ##### Account & State * [`setBalance`](https://viem.sh/docs/actions/test/setBalance) — Set address balance * [`setCode`](https://viem.sh/docs/actions/test/setCode) — Set contract bytecode * [`setNonce`](https://viem.sh/docs/actions/test/setNonce) — Set nonce * [`setStorageAt`](https://viem.sh/docs/actions/test/setStorageAt) — Set storage slot * [`setCoinbase`](https://viem.sh/docs/actions/test/setCoinbase) — Set miner address * [`setMinGasPrice`](https://viem.sh/docs/actions/test/setMinGasPrice) — Minimum gas price ##### State Management * [`snapshot`](https://viem.sh/docs/actions/test/snapshot) — Snapshot current state * [`revert`](https://viem.sh/docs/actions/test/revert) — Revert to snapshot * [`reset`](https://viem.sh/docs/actions/test/reset) — Reset fork * [`dumpState`](https://viem.sh/docs/actions/test/dumpState) — Export state * [`loadState`](https://viem.sh/docs/actions/test/loadState) — Import state
Wallet Actions — Send transactions, interact with accounts ##### Account Management * [`getAddresses`](https://viem.sh/docs/actions/wallet/getAddresses) — Get available addresses * [`requestAddresses`](https://viem.sh/docs/actions/wallet/requestAddresses) — Request address permission ##### Transaction Operations * [`prepareTransactionRequest`](https://viem.sh/docs/actions/wallet/prepareTransactionRequest) — Prepare tx * [`sendTransaction`](https://viem.sh/docs/actions/wallet/sendTransaction) — Send tx * [`sendRawTransaction`](https://viem.sh/docs/actions/wallet/sendRawTransaction) — Send signed tx * [`signTransaction`](https://viem.sh/docs/actions/wallet/signTransaction) — Sign tx ##### Signing * [`signMessage`](https://viem.sh/docs/actions/wallet/signMessage) — Sign a message * [`signTypedData`](https://viem.sh/docs/actions/wallet/signTypedData) — Sign typed data (EIP-712) ##### Chain Management * [`addChain`](https://viem.sh/docs/actions/wallet/addChain) — Add a chain * [`switchChain`](https://viem.sh/docs/actions/wallet/switchChain) — Switch chain ##### Permissions & Assets * [`getPermissions`](https://viem.sh/docs/actions/wallet/getPermissions) — Get permissions * [`requestPermissions`](https://viem.sh/docs/actions/wallet/requestPermissions) — Request permissions * [`watchAsset`](https://viem.sh/docs/actions/wallet/watchAsset) — Add a token
Tevm Actions — Enhanced EVM capabilities * `tevmCall` — Low-level EVM call * `tevmContract` — Contract call with detailed EVM info * `tevmDeploy` — Deploy with detailed results * `tevmGetAccount` — Detailed account info * `tevmSetAccount` — Set complex account state * `tevmDeal` — Add native ETH or ERC20 tokens * `tevmDumpState` — Export complete EVM state * `tevmLoadState` — Import complete EVM state * `tevmMine` — Mine blocks with options
### Next Steps * [Using with Ethers.js](/examples/ethers) * [Forking Mainnet](/examples/forking-mainnet) * [Local Testing](/examples/local-testing) * [TevmNode Interface](/core/tevm-node-interface) ## Creating a MemoryClient `createMemoryClient` bootstraps a complete Ethereum execution environment in JavaScript. ### Basic usage ```ts import { createMemoryClient } from "tevm"; const client = createMemoryClient(); // Optional: wait for ready (useful for profiling/debugging) await client.ready(); ``` ### With configuration ```ts import { createMemoryClient, http } from "tevm"; const rpcUrl = process.env.MAINNET_RPC_URL; if (!rpcUrl) { throw new Error("MAINNET_RPC_URL is required for fork mode"); } const client = createMemoryClient({ fork: { transport: http(rpcUrl)({}) }, miningConfig: { type: "auto" }, loggingLevel: "debug", }); await client.ready(); ``` :::tip All options for `createMemoryClient` also apply to `createTevmNode` and `createTevmTransport`. ::: ### Configuration Options #### Fork Configuration ```ts import { createMemoryClient, http } from "tevm"; const rpcUrl = process.env.MAINNET_RPC_URL; if (!rpcUrl) { throw new Error("MAINNET_RPC_URL is required for fork mode"); } const node = createMemoryClient({ fork: { transport: http(rpcUrl)({}), blockTag: 17_000_000n, // optional }, }); await node.ready(); ``` :::note Forked state is fetched lazily and cached locally. First access is slow, subsequent accesses are fast. ::: #### Mining Configuration ```ts // Auto: mine after each tx const node = createMemoryClient({ miningConfig: { type: "auto" } }); // Interval: mine every N ms const intervalNode = createMemoryClient({ miningConfig: { type: "interval", interval: 12_000 }, }); ``` #### Chain Configuration ```ts import { createMemoryClient } from "tevm"; import { Common } from "tevm/common"; const customNode = createMemoryClient({ common: Common.custom({ chainId: 1337, networkId: 1337 }), }); ``` Or use a preset: ```ts import { createMemoryClient } from "tevm"; import { mainnet, optimism, arbitrum, base } from "tevm/common"; const optimismNode = createMemoryClient({ common: optimism }); ```
Want to add your own network? Add it to `viem/chains` first, then open an issue on the Tevm repo to request inclusion.
#### Logging Configuration ```ts const node = createMemoryClient({ loggingLevel: "debug", // 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' }); node.logger.debug("Detailed debugging information"); node.logger.info("Informational message"); node.logger.warn("Warning!"); node.logger.error("Error encountered", { details: "Something went wrong" }); ``` #### Custom Precompiles ```ts import { definePrecompile, createContract, parseAbi } from "tevm"; const calculatorPrecompile = definePrecompile({ contract: createContract({ abi: parseAbi([ "function add(uint256 a, uint256 b) returns (uint256)", "function subtract(uint256 a, uint256 b) returns (uint256)", ]), address: "0xf2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2", }), call: async ({ data, gasLimit }) => { return { returnValue: new Uint8Array([0x01]), executionGasUsed: 200n, }; }, }); const node = createMemoryClient({ customPrecompiles: [calculatorPrecompile.precompile()], }); ``` #### Performance Profiling ```ts const node = createMemoryClient({ profiler: true }); await node.ready(); const vm = await node.getVm(); const performanceLogs = vm.evm.getPerformanceLogs(); ``` ### Complete Configuration Reference | Property | Type | Default | Description | | ---------------------------- | -------------------------------------------------------------- | ------------------ | ------------------------------------------------------------ | | `fork` | `{ transport: EIP1193RequestFn; blockTag?: BlockTag; }` | - | Enables forking from a live network or another Tevm instance | | `common` | `Common` | `tevmDevnet` | Chain configuration object | | `loggingLevel` | `"fatal" \| "error" \| "warn" \| "info" \| "debug" \| "trace"` | `"info"` | Logging verbosity level | | `miningConfig` | `{ type: 'auto' } \| { type: 'interval', interval: number }` | `{ type: 'auto' }` | Block mining behavior | | `customPrecompiles` | `Precompile[]` | `[]` | Additional precompiled contracts | | `allowUnlimitedContractSize` | `boolean` | `false` | Disables EIP-170 contract size checks | ### Best Practices #### Always pass a `common` when forking * Faster init (no chainId fetch). * Correct hardfork/EIP behavior per chain. Without one, `tevmDefault` is used. ```typescript import { createMemoryClient, http } from "tevm"; import { optimism } from "tevm/common"; const client = createMemoryClient({ common: optimism, fork: { transport: http() }, }); // op-stack-aware: enables l1DataFee calculation const { l1DataFee } = await client.call({ data }); ``` #### Choose the right mining config Default is manual mining (recommended). `auto` mines on each tx; `interval` mines every N ms; `gas` mines when a gas threshold is hit. ```ts const testNode = createMemoryClient({ miningConfig: { type: "auto" } }); const simulationNode = createMemoryClient({ miningConfig: { type: "interval", interval: 12_000 }, }); ``` #### Use debug logging when stuck Tevm produces many debug logs — pipe them through an LLM to triage. ```ts const client = createMemoryClient({ loggingLevel: "debug" }); ``` #### Call `client.ready()` when profiling Otherwise the first action absorbs init time. Tevm init is fast (no sync). ### Next Steps [Runtime Model](./runtime-model) · [Node Interface](./tevm-node-interface) · [Forking](./forking) · [State](./managing-state) · [Custom Precompiles](../advanced/custom-precompiles) ## Forking & Reforking Forking creates a local copy of an Ethereum (or any EVM) network at a point in time. Use it for: * Testing against production contracts and state * Debugging complex transactions * Developing with real-world data * Simulating DeFi protocols * "What-if" analysis ### Basic Forking #### Fork from Mainnet ```ts import { createTevmNode, http } from "tevm"; const node = createTevmNode({ fork: { transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY")({}), blockTag: "latest", }, }); await node.ready(); ``` #### Fork from Optimism ```ts import { createTevmNode, http } from "tevm"; import { optimism } from "tevm/common"; const node = createTevmNode({ fork: { transport: http("https://mainnet.optimism.io")({}), blockTag: "latest", }, common: optimism, }); await node.ready(); ``` #### Fork from a Specific Block ```ts import { createTevmNode, http } from "tevm"; const node = createTevmNode({ fork: { transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY")({}), blockTag: 17_500_000n, }, }); await node.ready(); ``` :::warning Call the transport with an empty object `({})` — required for the viem transport to initialize. ::: ### Fork Transport Options Tevm forks from any EIP-1193 compatible provider: ```ts import { createTevmNode, http } from "tevm"; import { createPublicClient, http as viemHttp } from "viem"; import { BrowserProvider } from "ethers"; // 1. Tevm http transport (recommended) const tevmNode1 = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); // 2. viem PublicClient const publicClient = createPublicClient({ transport: viemHttp("https://mainnet.infura.io/v3/YOUR-KEY"), }); const tevmNode2 = createTevmNode({ fork: { transport: publicClient }, }); // 3. Ethers.js Provider (wrap to match EIP-1193) const ethersProvider = new BrowserProvider(window.ethereum); const tevmNode3 = createTevmNode({ fork: { transport: { request: async ({ method, params }) => ethersProvider.send(method, params || []), }, }, }); ``` #### Provider Selection | Provider | Pros | Cons | | --------------------- | ---------------------------------------- | -------------------------------- | | `http` from Tevm | Optimized for Tevm, minimal dependencies | Limited middleware support | | Public RPC nodes | Free, easy to use | Rate limits, may be slow | | Alchemy/Infura/etc | Fast, reliable, archive data | Requires API key, may have costs | | Local Ethereum node | Full control, no rate limits | Resource intensive to run | | Another Tevm instance | Memory efficient | Adds complexity | :::note For large-scale testing, use a dedicated RPC provider (Alchemy, Infura) or your own node. ::: ### How Forking Works Tevm uses **lazy loading with local caching**: 1. **Initial Fork**: no immediate copy of the chain. 2. **Lazy Loading**: only accessed accounts/slots are fetched from the remote. 3. **Local Cache**: fetched data is cached locally; subsequent reads skip the network. 4. **Local Modifications**: writes are stored locally and override forked state. ```ts import { createTevmNode, http } from "tevm"; import { createAddress } from "tevm/address"; import { performance } from "node:perf_hooks"; const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); await node.ready(); const vm = await node.getVm(); const daiAddress = createAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"); const t0 = performance.now(); const daiContract = await vm.stateManager.getAccount(daiAddress); console.log(`First access: ${performance.now() - t0}ms`); const t1 = performance.now(); await vm.stateManager.getAccount(daiAddress); console.log(`Cached access: ${performance.now() - t1}ms`); ``` ### Reforking Strategies Two approaches: #### 1. Use a Node as Transport (recommended) Memory-efficient — reuses the source's cached state: ```ts import { createTevmNode, http, hexToBigInt } from "tevm"; import { requestEip1193 } from "tevm/decorators"; const sourceNode = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}), blockTag: 17_000_000n, }, }).extend(requestEip1193()); await sourceNode.ready(); const currentBlock = await sourceNode.request({ method: "eth_blockNumber" }); const forkNode = createTevmNode({ fork: { transport: sourceNode, blockTag: hexToBigInt(currentBlock), }, }); await forkNode.ready(); // Changes in forkNode do not affect sourceNode ``` Advantages: memory-efficient, avoids duplicate fetches, isolated, multi-fork from one source. #### 2. Deep Copy For full isolation (memory-intensive): ```ts import { createTevmNode, http } from "tevm"; const originalNode = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); await originalNode.ready(); const independentNode = await originalNode.deepCopy(); ``` :::warning `deepCopy` duplicates the entire state tree. Prefer using the node as transport when possible. ::: ### Working with Forked State #### Reading State ```ts import { createTevmNode, http } from "tevm"; import { createAddress } from "tevm/address"; import { formatEther, hexToBytes } from "viem"; const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); await node.ready(); const vm = await node.getVm(); // 1. EOA state const vitalikAddress = createAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); const vitalikAccount = await vm.stateManager.getAccount(vitalikAddress); if (vitalikAccount) { console.log(`Balance: ${formatEther(vitalikAccount.balance)} ETH`); console.log(`Nonce: ${vitalikAccount.nonce}`); } // 2. Contract code const uniswapV2Router = createAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"); const routerCode = await vm.stateManager.getContractCode(uniswapV2Router); console.log(`Code size: ${routerCode.length} bytes`); // 3. Storage const slot0 = await vm.stateManager.getContractStorage( uniswapV2Router, hexToBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), ); // 4. Low-level call const result = await vm.evm.runCall({ to: uniswapV2Router, data: hexToBytes("0xc45a0155"), // factory() gasLimit: 100000n, }); console.log("Factory address:", result.execResult.returnValue); ``` #### Modifying State ```ts import { createTevmNode, http } from "tevm"; import { createAddress } from "tevm/address"; import { createImpersonatedTx } from "tevm/tx"; import { parseEther, formatEther, hexToBytes } from "viem"; const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); await node.ready(); const vm = await node.getVm(); // 1. Modify balance const testAddress = createAddress("0x1234567890123456789012345678901234567890"); let account = await vm.stateManager.getAccount(testAddress); if (!account) { account = { nonce: 0n, balance: 0n, storageRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", }; } account.balance += parseEther("100"); await vm.stateManager.putAccount(testAddress, account); // 2. Impersonate node.setImpersonatedAccount("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); const tx = createImpersonatedTx({ from: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", to: testAddress, value: parseEther("1"), gasLimit: 21000n, }); const txResult = await vm.runTx({ tx }); console.log(`Success: ${!txResult.execResult.exceptionError}`); console.log(`Gas used: ${txResult.gasUsed}`); // 3. Direct storage write const uniswapV3Factory = createAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"); await vm.stateManager.putContractStorage( uniswapV3Factory, hexToBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), hexToBytes("0x0000000000000000000000001234567890123456789012345678901234567890"), ); ``` ### Advanced Use Cases * **DeFi protocol testing** — flash loans, liquidations, arbitrage * **Vulnerability analysis** — manipulate state to trigger edge cases * **Governance simulation** — DAO votes, proposals * **MEV strategy testing** — without risking real capital #### DeFi Protocol Example ```ts import { createTevmNode, http } from "tevm"; import { createAddress } from "tevm/address"; import { createImpersonatedTx } from "tevm/tx"; import { hexToBytes, parseEther } from "viem"; async function simulateFlashLoan() { const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); await node.ready(); const trader = createAddress("0x1234567890123456789012345678901234567890"); const vm = await node.getVm(); await vm.stateManager.putAccount(trader, { nonce: 0n, balance: parseEther("10"), storageRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", }); const aaveLendingPool = createAddress("0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9"); const flashLoanCalldata = hexToBytes("0xab9c4b5d..."); const tx = createImpersonatedTx({ from: trader, to: aaveLendingPool, data: flashLoanCalldata, gasLimit: 5000000n, }); const result = await vm.runTx({ tx }); console.log(`Success: ${!result.execResult.exceptionError}`); if (result.execResult.exceptionError) { console.log(`Error: ${result.execResult.exceptionError}`); } else { console.log(`Gas used: ${result.gasUsed}`); } const finalAccount = await vm.stateManager.getAccount(trader); console.log(`Final balance: ${finalAccount.balance}`); } simulateFlashLoan(); ``` ### Performance Optimization #### Efficient State Access ```ts import { createTevmNode, http } from "tevm"; import { createAddress } from "tevm/address"; const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}), blockTag: 17_000_000n, // Pin block to avoid moving target }, }); await node.ready(); async function optimizedStateAccess() { const vm = await node.getVm(); const stateManager = vm.stateManager; // Batch related requests const addresses = [ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI ].map(createAddress); const accounts = await Promise.all( addresses.map((addr) => stateManager.getAccount(addr)), ); // Parallel code + storage reads const usdcAddress = addresses[0]; const [code, slot0, slot1] = await Promise.all([ stateManager.getContractCode(usdcAddress), stateManager.getContractStorage(usdcAddress, Buffer.from("0".padStart(64, "0"), "hex")), stateManager.getContractStorage(usdcAddress, Buffer.from("1".padStart(64, "0"), "hex")), ]); return { accounts, code, storage: [slot0, slot1] }; } ``` #### Selective Forking Skip forking when you don't need real state: ```ts // Minimal local node const lightNode = createTevmNode({ common: { chainId: 1 }, }); // Conditional fork const conditionalFork = process.env.USE_FORK === "true" ? { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}), blockTag: "latest", } : undefined; const node = createTevmNode({ fork: conditionalFork }); ``` #### Cache Warmer Pre-fetch hot contracts: ```ts async function warmCache(node) { const vm = await node.getVm(); const contracts = [ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", // UniswapV2Router ].map(createAddress); await Promise.all( contracts.map((addr) => vm.stateManager.getAccount(addr).then(() => vm.stateManager.getContractCode(addr)), ), ); } ``` ### Best Practices #### 1. RPC Provider Setup ```ts // Call http transport with empty object const node = createTevmNode({ fork: { transport: http("https://ethereum.quicknode.com/YOUR-API-KEY")({}) }, }); // Pin a block for deterministic tests const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}), blockTag: 17_000_000n, }, }); ``` #### 2. Error Handling ```ts try { const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}) }, }); await node.ready(); } catch (error) { if (error.message.includes("rate limit")) { console.error("RPC rate limit exceeded."); } else if (error.message.includes("network")) { console.error("Network error."); } else { console.error("Fork init failed:", error); } } ``` #### 3. State Handling ```ts // Null-check accounts const account = await vm.stateManager.getAccount(address); if (account) { account.balance += parseEther("1"); await vm.stateManager.putAccount(address, account); } // Handle RPC failures try { const code = await vm.stateManager.getContractCode(contractAddress); } catch (error) { console.error("Failed to fetch contract code:", error); } ``` #### 4. Performance ```ts const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}), // 'latest' for recent txs; pinned number for reproducibility blockTag: process.env.NODE_ENV === "test" ? 17_000_000n : "latest", }, }); // First access: slow (RPC); subsequent: fast (cache). ``` #### 5. Testing Setup ```ts // Fresh fork per test (isolation) beforeEach(async () => { node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY")({}), blockTag: 17_000_000n, }, }); await node.ready(); }); // Or: shared base + deepCopy per test let baseNode; before(async () => { baseNode = createTevmNode({ fork: { /* ... */ } }); await baseNode.ready(); }); beforeEach(async () => { node = await baseNode.deepCopy(); }); ``` ### Next Steps [State Management](/core/managing-state) · [Mining Modes](/core/mining-modes) · [Transaction Pool](/advanced/txpool) · [Forking Example](/examples/forking-mainnet) ## Managing State Tevm exposes state management via two layers: a low-level `StateManager` and a high-level viem-style client API. ### State Management Approaches :::code-group ```typescript [Raw API] filename="raw-state-manager.ts" import { createTevmNode } from 'tevm' import { createAddress } from 'tevm/address' import { createAccount } from 'tevm/utils' const node = createTevmNode() const vm = await node.getVm() const stateManager = vm.stateManager // Read account state const address = createAddress('0x1234567890123456789012345678901234567890') const account = await stateManager.getAccount(address) if (account) { console.log({ balance: account.balance, nonce: account.nonce, codeHash: account.codeHash, storageRoot: account.storageRoot }) } // Create or update an account await stateManager.putAccount( address, createAccount({ nonce: 0n, balance: 10_000_000n }) ) // Delete an account await stateManager.deleteAccount(address) ``` ```typescript [Client API] filename="client-api.ts" import { createMemoryClient } from 'tevm' const client = createMemoryClient() await client.setBalance({ address: '0x1234567890123456789012345678901234567890', value: 1000000000000000000n }) const balance = await client.getBalance({ address: '0x1234567890123456789012345678901234567890' }) ``` ::: :::tip Raw API gives maximum control; client API is the recommended interface for most users (mirrors viem). ::: ### Contract State Management * Deploy bytecode * Read deployed code * Read/write storage slots * Clear storage or delete contracts :::code-group ```typescript [Raw API] filename="contract-management.ts" import { createAddress } from 'tevm/address' import { hexToBytes } from 'tevm/utils' const address = createAddress('0x1234567890123456789012345678901234567890') // Deploy contract code await stateManager.putCode(address, new Uint8Array([1, 2, 3])) // Read contract code const code = await stateManager.getCode(address) // Read storage slot const slot = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000000') const value = await stateManager.getStorage(address, slot) // Write storage const key = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000000') const newValue = hexToBytes('0x0000000000000000000000000000000000000000000000000000000000000001') await stateManager.putStorage(address, key, newValue) // Clear all storage await stateManager.clearStorage(address) ``` ```typescript [Client API] filename="client-contract-management.ts" import { createMemoryClient, hexToBytes } from 'tevm' const client = createMemoryClient() const contractAddress = '0x1234567890123456789012345678901234567890' await client.setCode({ address: contractAddress, bytecode: hexToBytes('0x608060405234801561001057600080fd5b50...') }) const code = await client.getCode({ address: contractAddress, blockTag: 'latest' }) const result = await client.call({ to: contractAddress, data: '0x70a08231000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266' }) ``` ::: ### Framework Integration You can combine viem/ethers with raw state access. :::code-group ```typescript [Viem] filename="viem-integration.ts" import { createMemoryClient } from 'tevm' const client = createMemoryClient() await client.setBalance({ address: '0x1234567890123456789012345678901234567890', value: 1000000000000000000n }) // Drop down to raw state manager const vm = await client.transport.tevm.getVm() const stateManager = vm.stateManager await stateManager.checkpoint() try { await stateManager.putStorage(address, key, value) await stateManager.commit() } catch (error) { await stateManager.revert() } ``` ```typescript [Ethers] filename="ethers-integration.ts" import { createTevmNode } from 'tevm' import { requestEip1193 } from 'tevm/decorators' import { BrowserProvider } from 'ethers' const node = createTevmNode().extend(requestEip1193()) const provider = new BrowserProvider(node) const balance = await provider.getBalance('0x1234...') const vm = await node.getVm() const stateManager = vm.stateManager ``` ::: :::note `createMemoryClient` implements viem's public actions. The `requestEip1193` decorator makes a Tevm node compatible with Ethers.js providers. ::: ### Advanced Features #### State Checkpoints Atomic changes that can be committed or reverted (transaction-like semantics): ```typescript filename="checkpoints.ts" const stateManager = (await node.getVm()).stateManager await stateManager.checkpoint() try { await stateManager.putAccount(address, account) await stateManager.putStorage(address, key, value) await stateManager.commit() } catch (error) { await stateManager.revert() console.error('State changes reverted:', error) } ``` #### State Persistence Dump and restore canonical state to any storage backend (localStorage, DB, etc.): ```typescript filename="persistence.ts" const state = await stateManager.dumpCanonicalGenesis() localStorage.setItem('tevmState', JSON.stringify(state)) const savedState = JSON.parse(localStorage.getItem('tevmState')) await stateManager.generateCanonicalGenesis(savedState) ``` #### Fork Mode Forked nodes lazy-load and cache state from a remote provider: ```typescript filename="fork-mode.ts" import { createTevmNode, http } from 'tevm' import { createAddress } from 'tevm/address' const rpcUrl = process.env.MAINNET_RPC_URL if (!rpcUrl) { throw new Error('MAINNET_RPC_URL is required for fork mode') } const node = createTevmNode({ fork: { transport: http(rpcUrl)({}) } }) const stateManager = (await node.getVm()).stateManager const address = createAddress('0x1234567890123456789012345678901234567890') // First access fetches from remote const account = await stateManager.getAccount(address) // Subsequent access uses cache const cachedAccount = await stateManager.getAccount(address) ``` ### Best Practices * Handle errors from state operations. * Use `deepCopy` for isolated test scenarios. * Group related changes with checkpoints. :::code-group ```typescript [Error Handling] filename="error-handling.ts" import { createAddress } from 'tevm/address' const address = createAddress('0x1234567890123456789012345678901234567890') try { const account = await stateManager.getAccount(address) if (!account) { console.log('Account does not exist yet') } else { console.log('Balance:', account.balance) } } catch (error) { console.error('State operation failed:', error) } ``` ```typescript [State Isolation] filename="state-isolation.ts" const isolatedState = await stateManager.deepCopy() await isolatedState.putAccount(address, account) // Original stateManager unaffected ``` ```typescript [Atomic Operations] filename="atomic-operations.ts" await stateManager.checkpoint() try { await stateManager.putAccount(address, account) await stateManager.putStorage(address, key, value) await stateManager.commit() } catch (error) { await stateManager.revert() console.error('Transaction reverted:', error) } ``` ::: :::warning Low-level state reads return `undefined` for missing local accounts. High-level actions such as `getAccountHandler` throw by default unless `throwOnFail: false` is set. ::: ### Related Resources [Runtime Model and ZEVM](./runtime-model) · [State Manager API](https://github.com/evmts/tevm-monorepo/blob/main/packages/state/docs/interfaces/StateManager.md) · [Account Management](../api/account-management) · [Contract Storage](/reference/contract) · [Forking Guide](../core/forking) ## Mining Modes Mining modes determine when transactions are included in blocks. Pick the mode that matches your scenario. ### Available Mining Modes * **Auto** — mine after each transaction * **Interval** — mine at fixed time intervals * **Manual** — mine only when explicitly requested * **Gas** — mine when accumulated gas crosses a threshold :::code-group ```typescript [Auto Mining] filename="auto-mining.ts" const node = createTevmNode({ miningConfig: { type: "auto" }, }); const txHash = await node.sendTransaction({ /* ... */ }); // Already confirmed const receipt = await node.getTransactionReceipt({ hash: txHash }); console.log("Block number:", receipt.blockNumber); ``` ```typescript [Interval Mining] filename="interval-mining.ts" const node = createTevmNode({ miningConfig: { type: "interval", interval: 12000, // 12 seconds, similar to Ethereum }, }); const txHash = await node.sendTransaction({ /* ... */ }); setTimeout(async () => { const receipt = await node.getTransactionReceipt({ hash: txHash }); console.log("Block number:", receipt.blockNumber); }, 12000); ``` ```typescript [Manual Mining] filename="manual-mining.ts" const node = createTevmNode({ miningConfig: { type: "manual" }, }); const txHash = await node.sendTransaction({ /* ... */ }); await node.mine(); const receipt = await node.getTransactionReceipt({ hash: txHash }); console.log("Block number:", receipt.blockNumber); ``` ```typescript [Gas-Based Mining] filename="gas-based-mining.ts" const node = createTevmNode({ miningConfig: { type: "gas", gasLimit: 15000000, }, }); // Transactions accumulate until total gas exceeds gasLimit await node.sendTransaction({ /* small tx */ }); await node.sendTransaction({ /* large tx that triggers mining */ }); ``` ::: **Best for:** * *Auto*: quick dev, immediate confirmation, instant-finality simulation. * *Interval*: time-dependent logic, realistic network conditions, pending queues. * *Manual*: deterministic tests, precise timing, mempool behavior. * *Gas*: gas-dependent behavior, block-fullness simulation, load testing. ### Changing Mining Modes You can switch modes at runtime: ```typescript filename="changing-mining-modes.ts" await node.setMiningConfig({ type: "interval", interval: 5000 }); await node.setMiningConfig({ type: "manual" }); ``` ### Event Handlers `mine()` accepts handlers to observe blocks, receipts, and logs in real time: ```typescript filename="mine-with-events.ts" import { createMemoryClient } from "tevm"; const client = createMemoryClient(); const result = await client.mine({ blockCount: 2, onBlock: (block, next) => { console.log(`Block #${block.header.number} mined:`, { hash: block.hash().toString("hex"), timestamp: block.header.timestamp, gasUsed: block.header.gasUsed, }); next?.(); }, onReceipt: (receipt, blockHash, next) => { console.log(`Receipt for tx ${receipt.transactionHash}:`, { blockHash, gasUsed: receipt.gasUsed, }); next?.(); }, onLog: (log, receipt, next) => { console.log(`Log from ${log.address}:`, { topics: log.topics, data: log.data, }); next?.(); }, }); ``` :::note Handlers run synchronously; always call `next()` to continue (middleware pattern). ::: ### Best Practices **Pick by use case:** * Development → `auto` (fastest feedback) * Testing → `manual` (deterministic) * Simulation → `interval` (realistic) * Load testing → `gas` (congestion) :::warning Performance trade-offs: `auto` is resource-intensive with many txs; `interval` delays processing; `gas` requires careful tuning. ::: ```typescript filename="testing-strategies.ts" const timeNode = createTevmNode({ miningConfig: { type: "interval", interval: 10000 } }); const deterministicNode = createTevmNode({ miningConfig: { type: "manual" } }); const gasNode = createTevmNode({ miningConfig: { type: "gas", gasLimit: 8000000 } }); ``` ### Example: Comparing Modes :::code-group ```typescript [Comparative Example] filename="mining-mode-comparison.ts" import { createTevmNode } from 'tevm' const autoNode = createTevmNode({ miningConfig: { type: 'auto' } }) const intervalNode = createTevmNode({ miningConfig: { type: 'interval', interval: 12000 } }) const manualNode = createTevmNode({ miningConfig: { type: 'manual' } }) await autoNode.sendTransaction({...}) // Mines immediately await intervalNode.sendTransaction({...}) // Mines after interval await manualNode.sendTransaction({...}) // Stays pending await manualNode.mine() // Now mined ``` ```typescript [Real-world Simulation] filename="real-world-simulation.ts" import { createTevmNode, http } from "tevm"; import { mainnet } from "tevm/common"; const node = createTevmNode({ fork: { transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"), common: mainnet, }, miningConfig: { type: "interval", interval: 12000 }, }); const txHash = await node.sendTransaction({ from: "0x123...", to: "0x456...", value: 1000000000000000000n, }); const pending = await node.getTxPoolContent(); console.log("Pending transactions:", pending); setTimeout(async () => { const receipt = await node.getTransactionReceipt({ hash: txHash }); console.log("Transaction mined in block:", receipt.blockNumber); }, 12000); ``` ::: ### Related [Transaction Pool](../advanced/txpool) · [Performance Profiler](../advanced/performance-profiler) · [Block Methods](../api/methods#block-methods) ## Runtime Model and ZEVM Tevm and [ZEVM](https://zevm.sh/docs) share execution primitives but are different products with different public interfaces. * Tevm is a JavaScript-first in-memory node, viem transport, EIP-1193 provider, and package family. * ZEVM is the native Zig client with CLI/runtime docs for trusted mode and light mode. * Tevm docs should describe Tevm APIs first, then link to ZEVM for the native client contract. ### Tevm Runtime Shapes #### Local writable node The default Tevm node is a writable local Ethereum execution environment. It supports local accounts, state mutation, transaction submission, mining controls, snapshots, traces, and the full JSON-RPC method set in the [JSON-RPC Guide](../api/json-rpc#supported-methods). This is the Tevm equivalent of ZEVM's trusted-mode development-node docs, with Tevm's JavaScript APIs and broader compatibility namespaces. Start here: [Creating a Tevm Node](./create-tevm-node) · [Mining Modes](./mining-modes) · [Managing State](./managing-state) · [Local Testing Flow](../examples/local-testing) #### Forked local node Forking is configuration on top of the writable local node, not a separate runtime. Tevm fetches remote state lazily, caches it locally, and stores local writes in an overlay. Start here: [Forking and Reforking](./forking) · [Forking Mainnet](../examples/forking-mainnet) · [Managing State](./managing-state) This matches the boundary in ZEVM's [State Fork and Snapshots](https://zevm.sh/docs/concepts/state-fork-and-snapshots/) docs: the fork source is a read source; local writes belong to the local overlay. #### Optional light-client consensus service Tevm can be configured with an injected consensus service for proof-aware reads. This is not the same as running the native ZEVM light-mode binary. In Tevm, the consensus service affects specific read paths and light-sync status inside a JavaScript node. When `consensus.mode` is `light-client`, these RPC paths use consensus/readiness/proof hooks: * `eth_chainId` * `eth_blockNumber` * `eth_getBalance` * `eth_getCode` * `eth_getStorageAt` * `eth_getTransactionCount` * `tevm_lightSyncStatus` * `zevm_lightSyncStatus` The rest of the Tevm node remains Tevm's JavaScript runtime surface unless the application restricts it. For the native ZEVM light-mode contract (startup checkpoints, selector rules, readiness gating, mode-unsupported error codes), see ZEVM's [Light Mode](https://zevm.sh/docs/concepts/light-mode/) and [Verified Light-Mode Reads](https://zevm.sh/docs/reference/json-rpc/verified-light-mode-reads/) docs. ### ZEVM Docs Cross-Reference ZEVM has a strong docs split that Tevm mirrors through Tevm-specific guides rather than copying native CLI details directly. | ZEVM docs area | Tevm version | | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Installation](https://zevm.sh/docs/quickstart/installation/) | [Getting Started](../getting-started/overview), [Creating a Tevm Node](./create-tevm-node), and package docs. Tevm installs from npm; native Zig build steps belong to ZEVM. | | [Run Trusted Mode](https://zevm.sh/docs/quickstart/run-trusted-mode/) | [Creating a Tevm Node](./create-tevm-node), [Local Testing Flow](../examples/local-testing), [Mining Modes](./mining-modes). | | [Forked Dev Node](https://zevm.sh/docs/quickstart/forked-dev-node/) | [Forking and Reforking](./forking), [Forking Mainnet](../examples/forking-mainnet). | | [Run Light Mode](https://zevm.sh/docs/quickstart/run-light-mode/) | Tevm's injectable consensus service and `tevm_lightSyncStatus`/`zevm_lightSyncStatus` compatibility docs. Use ZEVM's page for the native CLI light client. | | [Troubleshooting](https://zevm.sh/docs/quickstart/troubleshooting/) | Split by surface: [Bundler Troubleshooting](../reference/bundler/troubleshooting), [Forking and Reforking](./forking), JSON-RPC errors in [JSON-RPC Guide](../api/json-rpc#error-handling). | | [Runtime Modes](https://zevm.sh/docs/concepts/runtime-modes/) | This page. Tevm has local, forked local, and optional injected light-client consensus — not the same CLI mode selector as ZEVM. | | [Trusted Mode](https://zevm.sh/docs/concepts/trusted-mode/) | Tevm's local writable node docs: [Creating a Tevm Node](./create-tevm-node), [Mining Modes](./mining-modes), [Managing State](./managing-state), [Account Management](../api/account-management). | | [Light Mode](https://zevm.sh/docs/concepts/light-mode/) | Tevm's light-client consensus notes on this page plus `tevm_lightSyncStatus` in [JSON-RPC Guide](../api/json-rpc#tevm-and-zevm-compatibility). Use ZEVM docs for full native light-mode operation. | | [State Fork and Snapshots](https://zevm.sh/docs/concepts/state-fork-and-snapshots/) | [Forking and Reforking](./forking), [Managing State](./managing-state), including checkpoint, dump/load, snapshot, and revert flows. | | [Method Support by Mode](https://zevm.sh/docs/concepts/method-support-by-mode/) | [JSON-RPC Guide](../api/json-rpc#supported-methods). Tevm documents every registered method and calls out compatibility stubs/aliases. | | [Architecture and Upstream Ownership](https://zevm.sh/docs/concepts/architecture-and-upstream-ownership/) | [Architecture Overview](../introduction/architecture-overview), [VM and Submodules](../api/vm-and-submodules), and package references explaining which Tevm facades are backed by ZEVM packages. | | [Configuration Reference](https://zevm.sh/docs/reference/configuration/overview/) | [Creating a Tevm Node](./create-tevm-node#complete-configuration-reference), [Forking and Reforking](./forking), [Mining Modes](./mining-modes). Tevm config is JS options, not a ZEVM CLI JSON config. | | [JSON-RPC Overview](https://zevm.sh/docs/reference/json-rpc/overview/) | [JSON-RPC Guide](../api/json-rpc): EIP-1193 usage, error handling, exhaustive method index. | | [Core Reads](https://zevm.sh/docs/reference/json-rpc/core-reads/) | [Methods Overview](../api/methods), [JSON-RPC Guide](../api/json-rpc), and viem-compatible read actions on `createMemoryClient`. | | [Managed Dev Wallet](https://zevm.sh/docs/reference/json-rpc/managed-dev-wallet/) | [Account Management](../api/account-management), [Methods Overview](../api/methods), [JSON-RPC Guide](../api/json-rpc). | | [Simulation](https://zevm.sh/docs/reference/json-rpc/simulation/) | [Call API](../api/tevm-call), [Methods Overview](../api/methods), `eth_call`/`eth_estimateGas` in [JSON-RPC Guide](../api/json-rpc). | | [Transactions and Mining](https://zevm.sh/docs/reference/json-rpc/transactions-and-mining/) | [Mining Modes](./mining-modes), [Transaction Pool](../advanced/txpool), tx methods in [JSON-RPC Guide](../api/json-rpc). | | [Blocks, Receipts, and Logs](https://zevm.sh/docs/reference/json-rpc/blocks-receipts-and-logs/) | [Receipts and Logs](../advanced/receipts-and-logs), [Block reference](../reference/block), block/log methods in [JSON-RPC Guide](../api/json-rpc). | | [ZEVM Controls](https://zevm.sh/docs/reference/json-rpc/dev-controls/) | Control methods in [JSON-RPC Guide](../api/json-rpc#tevm-and-zevm-compatibility), [Account Management](../api/account-management), [Managing State](./managing-state), [Mining Modes](./mining-modes). | | [Unsupported and Deferred](https://zevm.sh/docs/reference/json-rpc/unsupported-and-deferred/) | Tevm does not inherit ZEVM's unsupported list. Tevm supports many debug, filter, subscription, txpool, Engine API, Anvil, Hardhat, Ganache, and EVM compatibility methods ZEVM marks deferred. Source of truth: [JSON-RPC Guide](../api/json-rpc#supported-methods). | ### Compatibility Rules Tevm's canonical custom namespace is `tevm_*`. ZEVM's canonical native namespace is `zevm_*`. Tevm exposes only low-risk `zevm_*` aliases where semantics match. Today that means `zevm_lightSyncStatus` as an alias for `tevm_lightSyncStatus`. Other ZEVM-native control names should not be assumed to work in Tevm unless they appear in [JSON-RPC Guide](../api/json-rpc#supported-methods). When writing Tevm docs: * Use `tevm_*` for Tevm-specific methods. * Mention `zevm_lightSyncStatus` only as a compatibility alias. * Link to [ZEVM docs](https://zevm.sh/docs) for native CLI mode selection, trusted/light mode operation, release metadata, and exact ZEVM JSON-RPC contracts. * Keep Tevm method support grounded in Tevm's registered handler map and JSON-RPC guide. ### Next Steps [Creating a Tevm Node](./create-tevm-node) · [Forking and Reforking](./forking) · [Managing State](./managing-state) · [JSON-RPC Guide](../api/json-rpc) · [ZEVM Docs](https://zevm.sh/docs) ## TevmNode Interface `TevmNode` is the core low-level API. It exposes the EVM, transaction pool, state, receipts, account impersonation, filters, and extensibility hooks. ### Interface Overview ```ts export type TevmNode< TMode extends "fork" | "normal" = "fork" | "normal", TExtended = {}, > = { // Logging & status readonly logger: Logger; status: "INITIALIZING" | "READY" | "SYNCING" | "MINING" | "STOPPED"; readonly ready: () => Promise; // Core components readonly getVm: () => Promise; readonly getTxPool: () => Promise; readonly getReceiptsManager: () => Promise; readonly miningConfig: MiningConfig; // Forking support readonly mode: TMode; readonly forkTransport?: { request: EIP1193RequestFn }; // Account management readonly getImpersonatedAccount: () => Address | undefined; readonly setImpersonatedAccount: (address: Address | undefined) => void; // Event filtering readonly setFilter: (filter: Filter) => void; readonly getFilters: () => Map; readonly removeFilter: (id: Hex) => void; // Extensibility readonly extend: ( decorator: (client: TevmNode) => TExtension, ) => TevmNode; // State management readonly deepCopy: () => Promise>; } & EIP1193EventEmitter & TExtended; ``` ### Key Capabilities * [EVM Access](#virtual-machine-access) * [Transaction Pool](#transaction-pool-management) * [Account Impersonation](#account-impersonation) * [Event Filtering](#event-filtering) * [Extensibility](#extensibility) ### Initialization & Status :::warning Always `await node.ready()` before using other components. ::: ```ts import { createTevmNode } from "tevm"; const node = createTevmNode(); await node.ready(); console.log(`Node status: ${node.status}`); // 'READY' node.logger.debug("Node successfully initialized"); ``` Status values: * `INITIALIZING` — starting up * `READY` — ready for use * `SYNCING` — synchronizing (usually fork mode) * `MINING` — mining a block * `STOPPED` — stopped or fatal error ### Virtual Machine Access ```ts import { createTevmNode } from "tevm"; import { createAddress } from "tevm/address"; import { createImpersonatedTx } from "tevm/tx"; import { hexToBytes } from "viem"; const node = createTevmNode(); await node.ready(); const vm = await node.getVm(); // Run a transaction const tx = createImpersonatedTx({ to: "0x1234567890123456789012345678901234567890", value: 1000000000000000000n, nonce: 0n, gasLimit: 21000n, gasPrice: 10000000000n, }); const txResult = await vm.runTx({ tx }); // Run a call directly on the EVM const evmResult = await vm.evm.runCall({ to: createAddress("0x1234567890123456789012345678901234567890"), caller: createAddress("0x5678901234567890123456789012345678901234"), data: hexToBytes( "0xa9059cbb000000000000000000000000abcdef0123456789abcdef0123456789abcdef0000000000000000000000000000000000000000000000008ac7230489e80000", ), gasLimit: 100000n, }); // Step-level tracing vm.evm.events.on("step", (data, next) => { console.log(`${data.pc}: ${data.opcode.name}`); next?.(); }); ``` ### Transaction Pool Management ```ts import { createTevmNode } from "tevm"; import { parseEther } from "viem"; const node = createTevmNode(); await node.ready(); const txPool = await node.getTxPool(); await txPool.add({ from: "0x1234567890123456789012345678901234567890", to: "0x5678901234567890123456789012345678901234", value: parseEther("1.5"), gasLimit: 21000n, maxFeePerGas: 30000000000n, }); const pendingTxs = await txPool.content(); const orderedTxs = await txPool.txsByPriceAndNonce(); const txsForAddress = await txPool.contentFrom( "0x1234567890123456789012345678901234567890", ); ``` ### Receipt & Log Management ```ts import { createTevmNode } from "tevm"; const node = createTevmNode(); await node.ready(); const receiptsManager = await node.getReceiptsManager(); const receipt = await receiptsManager.getReceiptByTxHash( "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", ); const transferLogs = await receiptsManager.getLogs({ fromBlock: 0n, toBlock: "latest", address: "0x1234567890123456789012345678901234567890", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer null, "0x0000000000000000000000005678901234567890123456789012345678901234", ], }); const subId = await receiptsManager.newLogSubscription({ address: "0x1234567890123456789012345678901234567890", topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"], }); receiptsManager.on("log", (log) => { if (log.subscriptionId === subId) { console.log("New transfer:", log); } }); ``` ### Account Impersonation Impersonate any address in fork mode without its private key: ```ts import { createTevmNode, http } from "tevm"; import { createImpersonatedTx } from "tevm/tx"; import { parseEther } from "viem"; const node = createTevmNode({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY") }, }); await node.ready(); node.setImpersonatedAccount("0x28C6c06298d514Db089934071355E5743bf21d60"); const vm = await node.getVm(); const tx = createImpersonatedTx({ from: "0x28C6c06298d514Db089934071355E5743bf21d60", to: "0x1234567890123456789012345678901234567890", value: parseEther("10"), gasLimit: 21000n, }); const txResult = await vm.runTx({ tx }); const currentImpersonated = node.getImpersonatedAccount(); node.setImpersonatedAccount(undefined); // stop ``` ### Event Filtering ```ts import { createTevmNode } from "tevm"; import { formatEther } from "viem"; const node = createTevmNode(); await node.ready(); // All Transfer events node.setFilter({ id: "0x1", fromBlock: 0n, toBlock: "latest", address: "0x1234567890123456789012345678901234567890", topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"], }); // Transfers to a specific recipient node.setFilter({ id: "0x2", fromBlock: 0n, toBlock: "latest", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", null, "0x0000000000000000000000001234567890123456789012345678901234567890", ], }); const filters = node.getFilters(); const receiptManager = await node.getReceiptsManager(); const logs = await receiptManager.getFilterLogs("0x1"); logs.forEach((log) => { const from = "0x" + log.topics[1].slice(26); const to = "0x" + log.topics[2].slice(26); const value = BigInt(log.data); console.log(`Transfer: ${from} -> ${to}: ${formatEther(value)} ETH`); }); node.removeFilter("0x1"); ``` ### Extensibility `extend` adds custom methods via decorators: ```ts import { createTevmNode } from "tevm"; import { createImpersonatedTx } from "tevm/tx"; import { parseEther, formatEther } from "viem"; const node = createTevmNode(); await node.ready(); const enhancedNode = node.extend((baseNode) => ({ async getBalance(address) { const vm = await baseNode.getVm(); const account = await vm.stateManager.getAccount(address); return account.balance; }, async transferETH(from, to, amount) { const vm = await baseNode.getVm(); const tx = createImpersonatedTx({ from, to, value: amount, gasLimit: 21000n }); const result = await vm.runTx({ tx }); return { success: !result.execResult.exceptionError, gasUsed: result.gasUsed, }; }, async getAllBalances(addresses) { const results = {}; for (const addr of addresses) { results[addr] = formatEther(await this.getBalance(addr)); } return results; }, })); const balance = await enhancedNode.getBalance("0x1234567890123456789012345678901234567890"); const transfer = await enhancedNode.transferETH( "0x1234567890123456789012345678901234567890", "0x5678901234567890123456789012345678901234", parseEther("1.5"), ); const balances = await enhancedNode.getAllBalances([ "0x1234567890123456789012345678901234567890", "0x5678901234567890123456789012345678901234", ]); ``` ### State Management `deepCopy` clones a node with identical but independent state: ```ts import { createTevmNode } from "tevm"; import { parseEther } from "viem"; const baseNode = createTevmNode(); await baseNode.ready(); const vm = await baseNode.getVm(); await vm.stateManager.putAccount("0x1234567890123456789012345678901234567890", { nonce: 0n, balance: parseEther("100"), codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", storageRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", }); const scenarioNode = await baseNode.deepCopy(); const scenarioVm = await scenarioNode.getVm(); await scenarioVm.stateManager.putAccount("0x1234567890123456789012345678901234567890", { nonce: 0n, balance: parseEther("200"), codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", storageRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", }); // Independent state — original is unchanged. ``` ### Practical Examples :::code-group ```ts [Contract Deployment] import { createTevmNode } from "tevm"; import { hexToBytes } from "viem"; const node = createTevmNode(); await node.ready(); const bytecode = "0x60806040..."; // truncated const vm = await node.getVm(); const deployResult = await vm.runTx({ tx: { nonce: 0n, gasLimit: 2000000n, gasPrice: 10000000000n, data: hexToBytes(bytecode), }, }); if (deployResult.execResult.exceptionError) { throw new Error(`Deployment failed: ${deployResult.execResult.exceptionError}`); } const contractAddress = deployResult.createdAddress; const callResult = await vm.runTx({ tx: { to: contractAddress, data: hexToBytes( "0x70a08231000000000000000000000000" + "1234567890123456789012345678901234567890".slice(2), ), // balanceOf(address) gasLimit: 100000n, }, }); ``` ```ts [Contract Debugging] import { createTevmNode } from "tevm"; import { hexToBytes } from "viem"; const node = createTevmNode(); await node.ready(); const vm = await node.getVm(); const opcodeCounts = {}; const memoryAccesses = []; const stackChanges = []; vm.evm.events.on("step", (data, next) => { opcodeCounts[data.opcode.name] = (opcodeCounts[data.opcode.name] || 0) + 1; if (["MLOAD", "MSTORE", "MSTORE8"].includes(data.opcode.name)) { memoryAccesses.push({ op: data.opcode.name, offset: data.stack[data.stack.length - 1]?.toString(16), }); } if (data.opcode.name === "PUSH1") { stackChanges.push({ pc: data.pc, op: data.opcode.name, value: data.opcode.pushValue?.toString(16), }); } next?.(); }); const result = await vm.runTx({ tx: { to: "0x1234567890123456789012345678901234567890", data: hexToBytes( "0xa9059cbb000000000000000000000000abcdef0123456789abcdef0123456789abcdef0000000000000000000000000000000000000000000000008ac7230489e80000", ), gasLimit: 100000n, }, }); console.log(`Gas used: ${result.gasUsed}`); console.log( "Top opcodes:", Object.entries(opcodeCounts).sort((a, b) => b[1] - a[1]).slice(0, 5), ); console.log("Memory accesses:", memoryAccesses.length); console.log("PUSH operations:", stackChanges.length); vm.evm.events.removeAllListeners("step"); ``` ```ts [Chain Simulation] import { createTevmNode } from "tevm"; import { parseEther } from "viem"; async function simulateICO() { const node = createTevmNode({ miningConfig: { type: "auto" } }); await node.ready(); const vm = await node.getVm(); const deployer = "0x1111111111111111111111111111111111111111"; const investors = [ "0x2222222222222222222222222222222222222222", "0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444", ]; await vm.stateManager.putAccount(deployer, { nonce: 0n, balance: parseEther("100"), codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", storageRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", }); for (const investor of investors) { await vm.stateManager.putAccount(investor, { nonce: 0n, balance: parseEther("10"), codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", storageRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", }); } for (const investor of investors) { await vm.runTx({ tx: { from: investor, to: deployer, // pretend this is the ICO contract value: parseEther("5"), gasLimit: 21000n, }, }); } const deployerAccount = await vm.stateManager.getAccount(deployer); console.log(`Deployer balance: ${deployerAccount.balance}`); for (const investor of investors) { const acc = await vm.stateManager.getAccount(investor); console.log(`Investor ${investor}: ${acc.balance}`); } } simulateICO().catch(console.error); ``` ::: ### Best Practices #### 1. Initialization Flow ```ts const node = createTevmNode(); await node.ready(); const vm = await node.getVm(); ``` #### 2. Error Handling ```ts try { const vm = await node.getVm(); const result = await vm.runTx({ tx: { /* ... */ } }); if (result.execResult.exceptionError) { console.error(`Execution failed: ${result.execResult.exceptionError}`); console.error(`At PC: ${result.execResult.exceptionError.pc}`); } } catch (error) { console.error("Unexpected error:", error.message); } ``` #### 3. Resource Management ```ts // Filters node.getFilters().forEach((_, id) => node.removeFilter(id)); // VM listeners const vm = await node.getVm(); vm.evm.events.removeAllListeners("step"); // Receipt subscriptions const receiptsManager = await node.getReceiptsManager(); receiptsManager.removeAllListeners("log"); ``` #### 4. State Isolation ```ts const baseNode = createTevmNode(); await baseNode.ready(); async function runTestCase(scenario) { const testNode = await baseNode.deepCopy(); // mutate testNode; baseNode unchanged } ``` #### 5. Performance ```ts const node = createTevmNode({ profiler: false }); const vm = await node.getVm(); const stateManager = vm.stateManager; const addresses = ["0x1111...", "0x2222...", "0x3333..."]; for (const address of addresses) { await stateManager.putAccount(address, { /* ... */ }); } await node.mine({ blocks: 1 }); ``` ### Type Safety `TevmNode` is fully typed: ```ts import type { TevmNode } from "tevm/node"; function setupNode(node: TevmNode) { return async () => { await node.ready(); if (node.mode === "fork") { node.setImpersonatedAccount("0x..."); return { mode: "fork", impersonatedAccount: node.getImpersonatedAccount() }; } return { mode: "normal" }; }; } function createEnhancedNode() { const baseNode = createTevmNode(); const enhancedNode = baseNode.extend((base) => ({ async getBalance(address: string): Promise { const vm = await base.getVm(); const account = await vm.stateManager.getAccount(address); return account.balance; }, })); return enhancedNode; } ``` ### Next Steps [Forking](./forking) · [State Management](./managing-state) · [Mining Modes](./mining-modes) · [JSON-RPC](../api/json-rpc) · [EVM Events](../api/evm-events) · [Custom Precompiles](../advanced/custom-precompiles) ## Account Management Two actions manage account state: `getAccountHandler` and `setAccountHandler`. ### getAccountHandler Retrieve the current state of an account. #### Parameters ```ts type GetAccountParams = { address: Address blockTag?: 'latest' | 'pending' | 'earliest' | number returnStorage?: boolean // expensive throwOnFail?: boolean // defaults to true } ``` #### Return Type ```ts type GetAccountResult = { address: Address nonce: bigint balance: bigint deployedBytecode: Hex storageRoot: Hex codeHash: Hex isContract: boolean isEmpty: boolean storage?: { [key: Hex]: Hex } // when returnStorage=true errors?: TevmGetAccountError[] } ``` #### Example ```ts import { createTevmNode } from 'tevm' import { getAccountHandler, setAccountHandler } from 'tevm/actions' import { parseEther } from 'viem' const node = createTevmNode() const address = '0x1234567890123456789012345678901234567890' await setAccountHandler(node)({ address, balance: parseEther('1') }) const account = await getAccountHandler(node)({ address, blockTag: 'latest', returnStorage: true }) console.log('Balance:', account.balance) console.log('Nonce:', account.nonce) if (account.isContract) { console.log('Code:', account.deployedBytecode) console.log('Storage:', account.storage) } ``` ### setAccountHandler Modify account state directly. #### Parameters ```ts type SetAccountParams = { address: Address nonce?: bigint balance?: bigint deployedBytecode?: Hex state?: { [key: Hex]: Hex } stateDiff?: { [key: Hex]: Hex } throwOnFail?: boolean // defaults to true } ``` #### Return Type ```ts type SetAccountResult = { errors?: TevmSetAccountError[] } ``` #### Examples ##### Setting Balance ```ts import { setAccountHandler } from 'tevm/actions' await setAccountHandler(node)({ address: '0x...', balance: parseEther('100') }) ``` ##### Deploying Contract Code ```ts await setAccountHandler(node)({ address: contractAddress, deployedBytecode: '0x...', state: { '0x0000...': '0x0000...' } }) ``` ##### Modifying Multiple Properties ```ts await setAccountHandler(node)({ address: '0x...', nonce: 5n, balance: parseEther('10'), state: { [slot1]: value1, [slot2]: value2 } }) ``` ### Best Practices Skip storage fetches unless needed: ```ts const account = await getAccountHandler(node)({ address: '0x...', returnStorage: false // default }) ``` Check existence before modifying: ```ts const account = await getAccountHandler(node)({ address, throwOnFail: false }) if (account.errors) { await setAccountHandler(node)({ address, balance: amount }) } else if (!account.isEmpty) { await setAccountHandler(node)({ address, balance: account.balance + amount }) } ``` By default, `getAccountHandler` throws when an account is missing. Use `throwOnFail: false` when probing for existence. Error handling: ```ts const result = await setAccountHandler(node)({ address: '0x...', balance: newBalance, throwOnFail: false }) if (result.errors) { console.error('Failed to set account:', result.errors) } ``` ### Related Topics * [State Management](../core/managing-state) * [Call API](./tevm-call) * [JSON-RPC Support](./json-rpc) ### See Also * [JSON-RPC API](/api/json-rpc) * [Client Types](#TODO) * [Actions Reference](/reference/actions) * [EIP-1193 Specification](https://eips.ethereum.org/EIPS/eip-1193) * [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) ## EVM Events Tevm Node exposes low-level EVM events for monitoring and debugging contract execution. ### Available Events ```ts type EVMEvent = { newContract: (data: { address: Address, code: Uint8Array }, next?: () => void) => void beforeMessage: (data: Message, next?: () => void) => void afterMessage: (data: EVMResult, next?: () => void) => void step: (data: InterpreterStep, next?: () => void) => void } ``` #### InterpreterStep ```ts interface InterpreterStep { pc: number // program counter opcode: { name: string // e.g. 'SSTORE', 'CALL' fee: number // base gas fee dynamicFee?: bigint // additional dynamic gas isAsync: boolean } gasLeft: bigint gasRefund: bigint stateManager: StateManager stack: Uint8Array[] returnStack: bigint[] // for RETURNSUB account: Account // account owning the running code address: Address depth: number // 0 for top-level call memory: Uint8Array memoryWordCount: bigint // size in 32-byte words codeAddress: Address // differs from address in DELEGATECALL/CALLCODE } ``` * [`Address`](/reference/address), [`StateManager`](/reference/state), [`Account`](/reference/node) #### NewContractEvent ```ts interface NewContractEvent { address: Address code: Uint8Array } ``` #### Message ```ts interface Message { to?: Address // undefined for contract creation value: bigint caller: Address gasLimit: bigint data: Uint8Array code?: Uint8Array codeAddress?: Address depth: number isStatic: boolean isCompiled: boolean // precompiled contract delegatecall: boolean callcode: boolean salt?: Uint8Array // CREATE2 authcallOrigin?: Address // AUTH } ``` #### EVMResult ```ts interface EVMResult { execResult: { returnValue: Uint8Array executionGasUsed: bigint gasRefund?: bigint exceptionError?: { error: string // e.g. 'revert', 'out of gas' errorType?: string } logs?: Log[] selfdestruct?: Record gas?: bigint } gasUsed: bigint // includes intrinsic costs createdAddress?: Address gasRefund?: bigint } ``` ### Using with tevmCall Family The recommended access pattern is through the tevmCall family. Two API styles: #### Client-based API ```ts import { createMemoryClient } from 'tevm' import { encodeFunctionData } from 'viem' const client = createMemoryClient() const result = await client.tevmCall({ to: contractAddress, data: encodeFunctionData({ abi, functionName: 'myFunction', args: [arg1, arg2] }), onStep: (step, next) => { console.log('EVM Step:', { pc: step.pc, opcode: step.opcode, gasLeft: step.gasLeft, stack: step.stack, depth: step.depth, }) next?.() }, onNewContract: (data, next) => { console.log('New contract deployed:', { address: data.address.toString(), codeSize: data.code.length, }) next?.() }, onBeforeMessage: (message, next) => { console.log('Executing message:', { to: message.to?.toString(), value: message.value.toString(), delegatecall: message.delegatecall, }) next?.() }, onAfterMessage: (result, next) => { console.log('Message result:', { gasUsed: result.execResult.executionGasUsed.toString(), returnValue: result.execResult.returnValue.toString('hex'), error: result.execResult.exceptionError?.error, }) next?.() } }) ``` #### Tree-shakable API ```ts import { tevmCall } from 'tevm/actions' import { createClient } from 'viem' import { createTevmNode } from 'tevm/node' import { requestEip1193 } from 'tevm/decorators' import { encodeFunctionData } from 'viem' const tevmTransport = createTevmTransport() const client = createClient({ transport: tevmTransport }) const result = await tevmCall(client, { to: contractAddress, data: encodeFunctionData({ abi, functionName: 'myFunction', args: [arg1, arg2] }), onStep: (step, next) => { console.log('EVM Step:', { pc: step.pc, opcode: step.opcode, gasLeft: step.gasLeft, stack: step.stack, depth: step.depth }) next?.() }, onNewContract: (data, next) => { console.log('New contract:', { address: data.address.toString(), codeSize: data.code.length }) next?.() }, onBeforeMessage: (message, next) => { console.log('Message:', { to: message.to?.toString(), value: message.value.toString(), delegatecall: message.delegatecall }) next?.() }, onAfterMessage: (result, next) => { console.log('Result:', { gasUsed: result.execResult.executionGasUsed.toString(), returnValue: result.execResult.returnValue.toString('hex'), error: result.execResult.exceptionError?.error, }) next?.() } }) ``` ### Advanced Examples #### Debug Tracer ```ts import { createMemoryClient } from 'tevm' import { tevmCall } from 'tevm/actions' const client = createMemoryClient() const trace = { steps: [], contracts: new Set(), errors: [] } await tevmCall(client, { to: contractAddress, data: '0x...', onStep: (step, next) => { trace.steps.push({ pc: step.pc, opcode: step.opcode.name, gasCost: step.opcode.fee, stack: step.stack.map(item => item.toString(16)), }) next?.() }, onNewContract: (data, next) => { trace.contracts.add(data.address.toString()) next?.() }, onAfterMessage: (result, next) => { if (result.execResult.exceptionError) { trace.errors.push({ error: result.execResult.exceptionError.error, returnData: result.execResult.returnValue.toString('hex'), }) } next?.() } }) console.log('Trace:', { stepCount: trace.steps.length, contracts: Array.from(trace.contracts), errors: trace.errors, }) ``` #### Gas Profiler ```ts import { createMemoryClient } from 'tevm' import { tevmCall } from 'tevm/actions' const client = createMemoryClient() const profile = { opcodes: new Map(), totalGas: 0n } await tevmCall(client, { to: contractAddress, data: '0x...', onStep: (step, next) => { const opName = step.opcode.name const gasCost = BigInt(step.opcode.fee) const stats = profile.opcodes.get(opName) || { count: 0, totalGas: 0n } stats.count++ stats.totalGas += gasCost profile.totalGas += gasCost profile.opcodes.set(opName, stats) next?.() } }) for (const [opcode, stats] of profile.opcodes) { console.log(`${opcode}:`, { count: stats.count, totalGas: stats.totalGas.toString(), percentageOfTotal: Number(stats.totalGas * 100n / profile.totalGas), }) } ``` #### Error Handling ```ts import { createMemoryClient } from 'tevm' import { tevmCall } from 'tevm/actions' const client = createMemoryClient() await tevmCall(client, { to: contractAddress, data: '0x...', onAfterMessage: (result, next) => { if (result.execResult.exceptionError) { const error = result.execResult.exceptionError switch (error.error) { case 'out of gas': console.error('Out of gas') break case 'revert': console.error('Reverted:', result.execResult.returnValue.toString('hex')) break case 'invalid opcode': console.error('Invalid opcode') break default: console.error('Unknown error:', error) } } next?.() } }) ``` ### Best Practices Always call `next?.()` to continue execution: ```ts onStep: (step, next) => { // process step next?.() } ``` Handle errors so one bad handler doesn't break execution: ```ts onStep: (step, next) => { try { // process step } catch (error) { console.error('Step handler error:', error) } next?.() } ``` Filter to what you need for performance: ```ts onStep: (step, next) => { if (step.opcode.name === 'SSTORE') { console.log('Storage write:', { key: step.stack[step.stack.length - 1].toString(16), value: step.stack[step.stack.length - 2].toString(16) }) } next?.() } ``` ### Mining Events Mining operations also emit events: ```ts import { createMemoryClient } from 'tevm' import { mine } from 'tevm/actions' const client = createMemoryClient() await client.mine({ blockCount: 1, onBlock: (block, next) => { console.log('Block:', { number: block.header.number, hash: block.hash().toString('hex'), timestamp: block.header.timestamp }) next?.() }, onReceipt: (receipt, blockHash, next) => { console.log('Receipt:', { txHash: receipt.transactionHash, blockHash, gasUsed: receipt.gasUsed }) next?.() }, onLog: (log, receipt, next) => { console.log('Log:', { address: log.address, topics: log.topics, data: log.data }) next?.() } }) ``` Mining handlers also require `next?.()` to continue. ### Related Topics * [Tevm Call API](../api/tevm-call) * [Methods](../api/methods) * [Mining Modes](../core/mining-modes) * [Performance Profiler](../advanced/performance-profiler) * [Gas Estimation](../api/methods) ## JSON-RPC Support Tevm Node provides [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-compatible [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) support for integration with Ethereum libraries and tools. ### EIP-1193 Provider ```ts import { createTevmNode } from 'tevm' import { requestEip1193 } from 'tevm/decorators' const node = createTevmNode().extend(requestEip1193()) const blockNum = await node.request({ method: 'eth_blockNumber', params: [] }) ``` ### Supported Methods For Tevm's runtime model and the mapping to ZEVM's native trusted/light-mode docs, see [Runtime Model and ZEVM](../core/runtime-model). Native ZEVM has its own mode-gated [JSON-RPC Overview](https://zevm.sh/docs/reference/json-rpc/overview/) and [Verified Light-Mode Reads](https://zevm.sh/docs/reference/json-rpc/verified-light-mode-reads/) contract; the index below is the Tevm source of truth. Engine API methods are only registered when `engineApi` is enabled. `tevm_*`, `hardhat_*`, and `ganache_*` development-control aliases share implementations with matching `anvil_*` methods. `tevm_contract` and `eth_sendUnsignedTransaction` are registered compatibility stubs returning unsupported-method errors. #### Tevm and ZEVM compatibility Native Tevm methods: * `tevm_call` * `tevm_contract` * `tevm_dumpState` * `tevm_getAccount` * `tevm_lightSyncStatus` * `tevm_loadState` * `tevm_mine` * `tevm_miner` * `tevm_setAccount` * `zevm_lightSyncStatus` Tevm aliases for development controls: * `tevm_addBalance` * `tevm_autoImpersonateAccount` * `tevm_deal` * `tevm_dealErc20` * `tevm_dropAllTransactions` * `tevm_dropTransaction` * `tevm_enableTraces` * `tevm_getAutomine` * `tevm_getIntervalMining` * `tevm_impersonateAccount` * `tevm_increaseTime` * `tevm_metadata` * `tevm_mineDetailed` * `tevm_nodeInfo` * `tevm_removeBlockTimestampInterval` * `tevm_removePoolTransactions` * `tevm_reset` * `tevm_revert` * `tevm_setAutomine` * `tevm_setBalance` * `tevm_setBlockGasLimit` * `tevm_setBlockTimestampInterval` * `tevm_setChainId` * `tevm_setCode` * `tevm_setCoinbase` * `tevm_setErc20Allowance` * `tevm_setIntervalMining` * `tevm_setLoggingEnabled` * `tevm_setMinGasPrice` * `tevm_setNextBlockBaseFeePerGas` * `tevm_setNextBlockTimestamp` * `tevm_setNonce` * `tevm_setPrevRandao` * `tevm_setRpcUrl` * `tevm_setStorageAt` * `tevm_setTime` * `tevm_snapshot` * `tevm_stopImpersonatingAccount` #### Ethereum * `eth_accounts` * `eth_blobBaseFee` * `eth_blobGasPrice` * `eth_blockNumber` * `eth_call` * `eth_chainId` * `eth_coinbase` * `eth_createAccessList` * `eth_estimateGas` * `eth_feeHistory` * `eth_gasPrice` * `eth_getBalance` * `eth_getBlockAccessList` * `eth_getBlockByHash` * `eth_getBlockByNumber` * `eth_getBlockReceipts` * `eth_getBlockTransactionCountByHash` * `eth_getBlockTransactionCountByNumber` * `eth_getCode` * `eth_getFilterChanges` * `eth_getFilterLogs` * `eth_getLogs` * `eth_getProof` * `eth_getStorageAt` * `eth_getStorageValues` * `eth_getTransactionByBlockHashAndIndex` * `eth_getTransactionByBlockNumberAndIndex` * `eth_getTransactionByHash` * `eth_getTransactionCount` * `eth_getTransactionReceipt` * `eth_getUncleByBlockHashAndIndex` * `eth_getUncleByBlockNumberAndIndex` * `eth_getUncleCountByBlockHash` * `eth_getUncleCountByBlockNumber` * `eth_getWork` * `eth_hashrate` * `eth_maxPriorityFeePerGas` * `eth_mining` * `eth_newBlockFilter` * `eth_newFilter` * `eth_newPendingTransactionFilter` * `eth_protocolVersion` * `eth_sendRawTransaction` * `eth_sendTransaction` * `eth_sendUnsignedTransaction` * `eth_sign` * `eth_signTransaction` * `eth_simulateV1` * `eth_simulateV2` * `eth_submitHashrate` * `eth_submitWork` * `eth_subscribe` * `eth_syncing` * `eth_uninstallFilter` * `eth_unsubscribe` #### Anvil * `anvil_addBalance` * `anvil_autoImpersonateAccount` * `anvil_deal` * `anvil_dealErc20` * `anvil_dropAllTransactions` * `anvil_dropTransaction` * `anvil_dumpState` * `anvil_enableTraces` * `anvil_getAutomine` * `anvil_getIntervalMining` * `anvil_impersonateAccount` * `anvil_increaseTime` * `anvil_loadState` * `anvil_metadata` * `anvil_mine` * `anvil_mineDetailed` * `anvil_nodeInfo` * `anvil_removeBlockTimestampInterval` * `anvil_removePoolTransactions` * `anvil_reset` * `anvil_revert` * `anvil_setAutomine` * `anvil_setBalance` * `anvil_setBlockGasLimit` * `anvil_setBlockTimestampInterval` * `anvil_setChainId` * `anvil_setCode` * `anvil_setCoinbase` * `anvil_setErc20Allowance` * `anvil_setIntervalMining` * `anvil_setLoggingEnabled` * `anvil_setMinGasPrice` * `anvil_setNextBlockBaseFeePerGas` * `anvil_setNextBlockTimestamp` * `anvil_setNonce` * `anvil_setPrevRandao` * `anvil_setRpcUrl` * `anvil_setStorageAt` * `anvil_setTime` * `anvil_snapshot` * `anvil_stopImpersonatingAccount` #### Hardhat * `hardhat_addBalance` * `hardhat_autoImpersonateAccount` * `hardhat_deal` * `hardhat_dealErc20` * `hardhat_dropAllTransactions` * `hardhat_dropTransaction` * `hardhat_dumpState` * `hardhat_enableTraces` * `hardhat_getAutomine` * `hardhat_getIntervalMining` * `hardhat_impersonateAccount` * `hardhat_increaseTime` * `hardhat_loadState` * `hardhat_metadata` * `hardhat_mine` * `hardhat_mineDetailed` * `hardhat_nodeInfo` * `hardhat_removeBlockTimestampInterval` * `hardhat_removePoolTransactions` * `hardhat_reset` * `hardhat_revert` * `hardhat_setAutomine` * `hardhat_setBalance` * `hardhat_setBlockGasLimit` * `hardhat_setBlockTimestampInterval` * `hardhat_setChainId` * `hardhat_setCode` * `hardhat_setCoinbase` * `hardhat_setErc20Allowance` * `hardhat_setIntervalMining` * `hardhat_setLoggingEnabled` * `hardhat_setMinGasPrice` * `hardhat_setNextBlockBaseFeePerGas` * `hardhat_setNextBlockTimestamp` * `hardhat_setNonce` * `hardhat_setPrevRandao` * `hardhat_setRpcUrl` * `hardhat_setStorageAt` * `hardhat_setTime` * `hardhat_snapshot` * `hardhat_stopImpersonatingAccount` #### Ganache * `ganache_addBalance` * `ganache_autoImpersonateAccount` * `ganache_deal` * `ganache_dealErc20` * `ganache_dropAllTransactions` * `ganache_dropTransaction` * `ganache_dumpState` * `ganache_enableTraces` * `ganache_getAutomine` * `ganache_getIntervalMining` * `ganache_impersonateAccount` * `ganache_increaseTime` * `ganache_loadState` * `ganache_metadata` * `ganache_mine` * `ganache_mineDetailed` * `ganache_nodeInfo` * `ganache_removeBlockTimestampInterval` * `ganache_removePoolTransactions` * `ganache_reset` * `ganache_revert` * `ganache_setAutomine` * `ganache_setBalance` * `ganache_setBlockGasLimit` * `ganache_setBlockTimestampInterval` * `ganache_setChainId` * `ganache_setCode` * `ganache_setCoinbase` * `ganache_setErc20Allowance` * `ganache_setIntervalMining` * `ganache_setLoggingEnabled` * `ganache_setMinGasPrice` * `ganache_setNextBlockBaseFeePerGas` * `ganache_setNextBlockTimestamp` * `ganache_setNonce` * `ganache_setPrevRandao` * `ganache_setRpcUrl` * `ganache_setStorageAt` * `ganache_setTime` * `ganache_snapshot` * `ganache_stopImpersonatingAccount` #### Debug * `debug_dumpBlock` * `debug_getBadBlocks` * `debug_getModifiedAccountsByHash` * `debug_getModifiedAccountsByNumber` * `debug_getRawBlock` * `debug_getRawHeader` * `debug_getRawReceipts` * `debug_getRawTransaction` * `debug_intermediateRoots` * `debug_preimage` * `debug_storageRangeAt` * `debug_traceBlock` * `debug_traceBlockByHash` * `debug_traceBlockByNumber` * `debug_traceCall` * `debug_traceChain` * `debug_traceState` * `debug_traceTransaction` #### EVM test-runner compatibility * `evm_increaseTime` * `evm_mine` * `evm_revert` * `evm_setBlockGasLimit` * `evm_setIntervalMining` * `evm_setNextBlockTimestamp` * `evm_snapshot` #### Engine API * `engine_exchangeCapabilities` * `engine_exchangeTransitionConfigurationV1` * `engine_forkchoiceUpdatedV1` * `engine_forkchoiceUpdatedV2` * `engine_forkchoiceUpdatedV3` * `engine_forkchoiceUpdatedV4` * `engine_getBlobsV1` * `engine_getBlobsV2` * `engine_getBlobsV3` * `engine_getClientVersionV1` * `engine_getPayloadBodiesByHashV1` * `engine_getPayloadBodiesByHashV2` * `engine_getPayloadBodiesByRangeV1` * `engine_getPayloadBodiesByRangeV2` * `engine_getPayloadV1` * `engine_getPayloadV2` * `engine_getPayloadV3` * `engine_getPayloadV4` * `engine_getPayloadV5` * `engine_getPayloadV6` * `engine_newPayloadV1` * `engine_newPayloadV2` * `engine_newPayloadV3` * `engine_newPayloadV4` * `engine_newPayloadV5` * `testing_buildBlockV1` #### Txpool, web3, net, rpc, and miner compatibility * `miner_start` * `miner_stop` * `net_listening` * `net_peerCount` * `net_version` * `rpc_modules` * `txpool_content` * `txpool_contentFrom` * `txpool_inspect` * `txpool_status` * `web3_clientVersion` * `web3_sha3` ### Client Integration #### Viem See [Viem Documentation](https://viem.sh/docs/clients/custom.html). ```ts import { createTevmTransport } from 'tevm' import { createPublicClient, custom } from 'viem' import { requestEip1193 } from 'tevm/decorators' const tevmTransport = createTevmTransport() const client = createPublicClient({ chain: mainnet, transport: tevmTransport }) ``` #### Ethers See [Ethers Documentation](https://docs.ethers.org/v6/api/providers/#Provider). ```ts import { createTevmNode } from 'tevm' import { BrowserProvider } from 'ethers' import { requestEip1193 } from 'tevm/decorators' const node = createTevmNode().extend(requestEip1193()) const provider = new BrowserProvider(node) ``` ### Error Handling JSON-RPC errors follow the [standard format](https://www.jsonrpc.org/specification#error_object) and are fully typed. See [JsonRpcError type](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/JsonRpcError.md): ```ts interface JsonRpcError { code: number message: string data?: unknown } ``` Common codes (see [ErrorCodes](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/variables/ErrorCodes.md)): * `-32700`: Parse error ([`ParseError`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/ParseError.md)) * `-32600`: Invalid request ([`InvalidRequest`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/InvalidRequest.md)) * `-32601`: Method not found ([`MethodNotFound`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/MethodNotFound.md)) * `-32602`: Invalid params ([`InvalidParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/InvalidParams.md)) * `-32603`: Internal error ([`InternalError`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/InternalError.md)) * `-32000` to `-32099`: Server error ([`ServerError`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/ServerError.md)) See [Error Handling Guide](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/variables/ErrorHandling.md). ### Best Practices Wrap RPC calls in try-catch. Estimate gas before sending ([ErrorCodes](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/variables/ErrorCodes.md)): ```ts const gasEstimate = await node.request({ method: 'eth_estimateGas', params: [tx] }) ``` Wait for receipts ([ethGetTransactionReceipt](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/functions/ethGetTransactionReceipt.md)): ```ts const txHash = await node.request({ method: 'eth_sendTransaction', params: [tx] }) const receipt = await node.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) ``` Filter efficiently: set block ranges, use specific [topics](https://docs.soliditylang.org/en/latest/abi-spec.html#events), clean up with [`eth_uninstallFilter`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/functions/ethUninstallFilterProcedure.md). See: * [Actions Documentation](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs) * [Type Definitions](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases) * [Function Reference](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/functions) * [Variables and Constants](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/variables) ### Related Topics * [Using with Viem](../examples/viem) * [Using with Ethers](../examples/ethers) * [Managing State](../core/managing-state) * [Receipts & Logs](../advanced/receipts-and-logs) * [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/) * [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) * [Tevm API Documentation](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs) ### Using Tevm Actions High-level actions from `tevm/actions` (see [actions docs](https://github.com/evmts/tevm-monorepo/tree/main/packages/actions/docs)): ```ts import { tevmCall, tevmMine, tevmGetAccount, tevmSetAccount, tevmDeal } from 'tevm/actions' import { createTevmNode } from 'tevm' const node = createTevmNode() const result = await tevmCall(node, { to: '0x...', data: '0x...', value: 0n, createTransaction: true }) await tevmMine(node) const account = await tevmGetAccount(node, { address: '0x...', blockTag: 'latest' }) await tevmSetAccount(node, { address: '0x...', balance: 100n, nonce: 0n, deployedBytecode: '0x...' }) // Native ETH await tevmDeal(node, { account: '0x...', amount: 1000000000000000000n // 1 ETH }) // ERC20 await tevmDeal(node, { erc20: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC mainnet account: '0x...', amount: 1000000n // 1 USDC (6 decimals) }) ``` Type references: * [`CallParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/CallParams.md) * [`MineParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/MineParams.md) * [`GetAccountParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/GetAccountParams.md) * [`SetAccountParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/SetAccountParams.md) * [`AnvilDealParams`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/type-aliases/AnvilDealParams.md) :::note Tevm actions require manual mining via [`tevmMine()`](https://github.com/evmts/tevm-monorepo/blob/main/packages/actions/docs/functions/mineHandler.md) by default. For automatic application, either use lower-level `vm.runCall` or configure `miningConfig: { type: 'auto' }`. ::: ### Optimistic Updates with Receipt Manager See [Ethereum Receipts](https://ethereum.org/en/developers/docs/transactions/transaction-receipts/). ```ts import { createTevmNode } from 'tevm' import { tevmCall, tevmMine } from 'tevm/actions' const node = createTevmNode() const receiptsManager = await node.getReceiptsManager() // Submit const { txHash } = await tevmCall(node, { method: 'eth_sendTransaction', params: [tx], createTransaction: true }) const pendingReceipt = await receiptsManager.getReceiptByTxHash(txHash) updateUI(pendingReceipt) const realReceipt = await node.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) if (receiptsAreDifferent(pendingReceipt, realReceipt)) { await receiptsManager.removeReceipt(txHash) updateUI(realReceipt) } // Rebase on new blocks node.on('block', async (blockNumber) => { const block = await node.request({ method: 'eth_getBlockByNumber', params: [blockNumber, true] }) const pendingTxs = await receiptsManager.getPendingTransactions() for (const tx of pendingTxs) { const result = await tevmCall(node, { ...tx, blockTag: 'pending' }) await receiptsManager.putReceipt(tx.hash, result) } await tevmMine(node) }) ``` ## Tevm Node Methods Reference for the main API methods on a Tevm Node instance: EVM interaction, state management, execution control. ### Core Methods #### Initialization ```ts import { createTevmNode, http } from 'tevm' const rpcUrl = process.env.MAINNET_RPC_URL if (!rpcUrl) { throw new Error('MAINNET_RPC_URL is required for fork mode') } const node = createTevmNode({ fork: { transport: http(rpcUrl)({}) } }) await node.ready() ``` :::warning Always `await node.ready()` before using the node. ::: #### Virtual Machine ```ts import { createImpersonatedTx } from 'tevm/tx' import { Block } from 'tevm/block' import { createAddress } from 'tevm/address' const vm = await node.getVm() const block = new Block() const tx = createImpersonatedTx({ impersonatedAddress: createAddress('0x1234567890123456789012345678901234567890'), nonce: 0n, gasLimit: 21064n, maxFeePerGas: 8n, maxPriorityFeePerGas: 1n, to: createAddress('0x5678901234567890123456789012345678901234'), value: 1000000000000000000n, // 1 ETH }) const result = await vm.runTx({ tx, block, skipNonce: true, skipBalance: true }) if (!result.execResult.exceptionError) { console.log('Gas used:', result.totalGasSpent) } ``` :::note The VM interface provides the lowest level of control for executing transactions and inspecting execution. ::: #### Transaction Pool ```ts const txPool = await node.getTxPool() await txPool.add({ from: '0x1234...', to: '0x5678...', value: 1000000000000000000n, }) // Sorted by gas price and nonce const pending = await txPool.txsByPriceAndNonce() // Raw access const allPending = txPool.getPendingTransactions() // Pending nonce for an address const nextNonce = txPool.getPendingNonce('0x1234...') ``` :::tip Pool transactions remain pending until mined. Mining behavior depends on your mining configuration. ::: #### Receipts & Logs ```ts import { hexToBytes } from 'tevm/utils' const receipts = await node.getReceiptsManager() const vm = await node.getVm() const fromBlock = await vm.blockchain.getBlock(0n) const toBlock = fromBlock const receipt = await receipts.getReceiptByTxHash( hexToBytes('0x1234567890123456789012345678901234567890123456789012345678901234'), ) const logs = await receipts.getLogs( fromBlock, toBlock, [hexToBytes('0x1234567890123456789012345678901234567890')], [hexToBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef')], ) ``` The logs API mirrors Ethereum's JSON-RPC pattern (filter by address, topics, block range). ### State Management #### Account Impersonation ```ts // Impersonate (fork mode only) node.setImpersonatedAccount('0x1234...') const impersonated = node.getImpersonatedAccount() // Stop node.setImpersonatedAccount(undefined) ``` :::warning Impersonation primarily affects the JSON-RPC layer, enabling `eth_sendRawTransaction` to execute as the impersonated account. Works best in fork mode. ::: #### Event Filtering ```ts node.setFilter({ id: '0x1', fromBlock: 0n, toBlock: 'latest', address: '0x1234...', topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // Transfer ], }) const filters = node.getFilters() node.removeFilter('0x1') const logs = await node.request({ method: 'eth_getFilterChanges', params: ['0x1'] }) ``` Topic `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` is keccak256 of `Transfer(address,address,uint256)`. ### Node Properties #### Status ```ts console.log(node.status) // 'INITIALIZING' | 'READY' | 'SYNCING' | 'MINING' | 'STOPPED' const waitForReady = async () => { while (node.status !== 'READY') { await new Promise(resolve => setTimeout(resolve, 100)) } } ``` #### Mode ```ts console.log(node.mode) // 'fork' or 'normal' if (node.mode === 'fork') { // fork-specific features like impersonation } else { // local-only features } ``` #### Logger ```ts node.logger.trace('Extremely detailed information') node.logger.debug('Detailed debugging information') node.logger.info('General information') node.logger.warn('Warning messages') node.logger.error('Error information') node.logger.fatal('Critical errors that stop execution') node.logger.info('Transaction processed', { hash: '0x1234...', from: '0x5678...', to: '0x9abc...', value: '1 ETH' }) ``` ### Advanced Actions The `tevm`-prefixed actions provide direct EVM execution with debugging hooks. #### tevmCall Low-level EVM call with execution tracing: ```ts import { tevmCall } from 'tevm/actions' import { encodeFunctionData } from 'viem' import { createMemoryClient } from 'tevm' const client = createMemoryClient() const result = await tevmCall(client, { to: '0x1234...', data: encodeFunctionData({ abi, functionName: 'myFunction', args: [arg1, arg2] }), onStep: (step, next) => { console.log(`Opcode: ${step.opcode.name}, PC: ${step.pc}`) next?.() }, onNewContract: (data, next) => { console.log(`New contract at: ${data.address.toString()}`) next?.() }, onBeforeMessage: (message, next) => { console.log(`Call to: ${message.to?.toString()}`) next?.() }, onAfterMessage: (result, next) => { console.log(`Return: ${result.execResult.returnValue.toString('hex')}`) next?.() } }) ``` :::tip `onStep` fires for every EVM instruction, enabling opcode-level tracing. ::: #### tevmContract High-level contract interaction with event monitoring: ```ts import { tevmContract } from 'tevm/actions' import { createMemoryClient } from 'tevm' const client = createMemoryClient() const result = await tevmContract(client, { abi, address: '0x1234...', functionName: 'myFunction', args: [arg1, arg2], onStep: (step, next) => { console.log(`Opcode: ${step.opcode.name}, Stack: ${step.stack.length}`) next?.() }, onNewContract: (data, next) => { console.log(`New contract: ${data.address.toString()}`) next?.() } }) ``` #### tevmDeploy Contract deployment with execution monitoring: ```ts import { tevmDeploy } from 'tevm/actions' import { createMemoryClient } from 'tevm' const client = createMemoryClient() const deployResult = await tevmDeploy(client, { abi, bytecode, args: [constructorArg1, constructorArg2], onStep: (step, next) => { console.log(`Executing: ${step.opcode.name}`) next?.() }, onNewContract: (data, next) => { console.log(`Deployed at: ${data.address.toString()}`) next?.() } }) console.log('Deployed:', deployResult.address) console.log('Gas used:', deployResult.gasUsed) ``` Debug use cases: gas profiling, contract testing, security auditing, educational EVM visualizers. ### Extensibility #### Custom Methods ```ts const enhancedNode = node.extend((baseNode) => ({ async getBalance(address: string) { const vm = await baseNode.getVm() const account = await vm.stateManager.getAccount(address) return account.balance }, })) const balance = await enhancedNode.getBalance('0x1234...') ``` :::tip Extension methods have full access to the base node's functionality. ::: #### State Cloning ```ts // Deep copy with independent state const nodeCopy = await node.deepCopy() // Fork from another node const forkedNode = createTevmNode({ fork: { transport: node } }) await nodeCopy.sendTransaction({ ... }) // Original state unchanged ``` State copying is useful for test scenarios, alternative execution paths, and state snapshots. ### JSON-RPC Support Tevm Node implements standard Ethereum JSON-RPC methods. #### EIP-1193 Interface ```ts import { requestEip1193 } from 'tevm/decorators' const node = createTevmNode().extend(requestEip1193()) const blockNumber = await node.request({ method: 'eth_blockNumber', params: [] }) const balance = await node.request({ method: 'eth_getBalance', params: ['0x1234...', 'latest'] }) ``` The EIP-1193 interface is compatible with libraries like Ethers.js. #### Action Methods ```ts import { ethActions } from 'tevm/decorators' const node = createTevmNode().extend(ethActions()) const blockNumber = await node.eth.getBlockNumber() const balance = await node.eth.getBalance('0x1234...') const hash = await node.eth.sendTransaction({ from: '0x1234...', to: '0x5678...', value: 1000000000000000000n }) ``` Action methods provide ergonomic TypeScript-typed alternatives to raw JSON-RPC. #### Common JSON-RPC Methods For the exhaustive list, see the [JSON-RPC Guide](./json-rpc#supported-methods). **State Access** * `eth_getBalance` * `eth_getCode` * `eth_getStorageAt` * `eth_getTransactionCount` **Block Methods** * `eth_blockNumber` * `eth_getBlockByHash` * `eth_getBlockByNumber` **Transaction Methods** * `eth_sendTransaction` * `eth_sendRawTransaction` * `eth_getTransactionByHash` * `eth_getTransactionReceipt` **Anvil Methods** * `anvil_impersonateAccount` * `anvil_stopImpersonatingAccount` * `anvil_mine` * `anvil_setBalance` **Tevm Methods** * `tevm_snapshot` * `tevm_revert` * `tevm_mine` * `tevm_setAccount` **Utility Actions** * `whatsabi` **\[Coming Soon]** Analyze contract bytecode, discover function selectors, resolve proxy implementations, and determine ABIs even for unverified contracts. See [Complete JSON-RPC API](../api/json-rpc). ## Package Reference Tevm Node is modular. Install the complete suite with `npm install tevm`, or individual packages as needed. ### Installation #### Complete Package ```bash npm install tevm ``` ```typescript import { createTevmNode } from 'tevm' import { callHandler } from 'tevm/actions' import { http } from 'tevm' ``` #### Individual Packages ```bash npm install @tevm/node @tevm/actions ``` ```typescript import { createTevmNode } from '@tevm/node' import { callHandler } from '@tevm/actions' ``` ### Core Packages #### Node & Client * **@tevm/node** - Core node implementation; blockchain and state management; mining and block production. * **@tevm/memory-client** - In-memory Ethereum client with Viem-compatible interface; local state and transaction handling. #### EVM & Execution * **@tevm/vm** - VM coordinating EVM execution, block building, receipts, and state transitions; backed by ZEVM-compatible EVM, tx, common, and block primitives; emits VM/EVM lifecycle events. * **@tevm/evm** - Low-level EVM operations: ZEVM-backed bytecode execution, gas accounting, step events, custom precompiles, profiling, and direct `runCall` access. #### State & Storage * **@tevm/state** - Account state, storage manipulation, fork-backed reads, snapshots, state roots, and proof support. * **@tevm/blockchain** - Block management, chain reorganization, header validation. ### Transaction Handling #### Transaction Processing * **@tevm/tx** - ZEVM-backed transaction facade; legacy, EIP-2930, EIP-1559, EIP-4844, EIP-7702 helpers; Tevm impersonated transactions for local execution and fork testing. * **@tevm/txpool** - Pending transaction queue, ordering, replacement handling, `txpool_*` JSON-RPC support. #### Block & Receipt Management * **@tevm/block** - Block creation, validation, chain management. * **@tevm/receipt-manager** - Receipt storage, event logging, gas tracking; `eth_getTransactionReceipt`, `eth_getBlockReceipts`, filters, subscriptions. ### Client Integration #### Communication * **@tevm/jsonrpc** - Standard Ethereum and custom Tevm methods, error handling; HTTP, WebSocket, rate-limit, and load-balance transports. * **@tevm/http-client** - HTTP client; remote node communication, request batching, error handling. #### Actions & Procedures * **@tevm/actions** - High-level actions for contract interaction, account management, state queries; `eth_*`, `tevm_*`, `debug_*`, `engine_*`, `txpool_*`, `anvil_*`, `hardhat_*`, `ganache_*`, `evm_*` handlers. * **@tevm/procedures** - State manipulation, chain operations, utility functions. ### Smart Contract Tools #### Contract Interaction * **@tevm/contract** - ABI handling, function encoding, event parsing. * **@tevm/precompiles** - Standard and custom precompiles; gas calculation. #### Contract Management * **@tevm/predeploys** - Pre-deployed standard and network-specific contracts. ### Utilities & Helpers #### Core Utilities * **@tevm/utils** - Address handling, data encoding, type conversion; ZEVM-backed utilities and EIP-7702 authorization helpers. * **@tevm/common** - Chain configurations and network parameters; ZEVM-backed Common object plus viem chain definitions. #### Development Tools * **@tevm/decorators** - Function decorators for method extension and behavior modification. * **@tevm/errors** - Error types, messages, stack traces. #### Data Structures * **@tevm/rlp** - ZEVM-backed RLP encoding/decoding for serialization, network protocol, storage. * **@tevm/trie** - ZEVM-backed Merkle Patricia Tree for state storage, proof verification, tree manipulation. ### Development & Debugging #### Logging & Debugging * **@tevm/logger** - Configurable levels, output formatting, debug information. * **@tevm/effect** - Side effect handling, async operations, error recovery. #### Storage & Types * **@tevm/sync-storage-persister** - State synchronization, persistence, cache management. * **@tevm/client-types** - Interface and type definitions. ### Best Practices * **Start simple** - Begin with the complete `tevm` package. Split only to optimize bundle size. * **Bundle optimization** - Import from subpaths (`tevm/actions`) and use tree-shaking-friendly imports. * **Version management** - Keep all `@tevm/*` packages on the same version. * **Development** - Use TypeScript; leverage provided type definitions. ### Related Topics * [Architecture Overview](../introduction/architecture-overview) * [API Reference](./methods) * [GitHub Repository](https://github.com/evmts/tevm-monorepo) ## Call API The Call API covers \~90% of use cases (with mining) and provides an EVM call interface with extensive configuration. :::warning[Transaction Inclusion] Tevm defaults to manual mining mode: * With `addToMempool: true` (or deprecated `createTransaction: true`), you must call `client.tevmMine()` to include the transaction * `addToBlockchain: true` auto-mines for immediate inclusion * You cannot use both `addToMempool` and `addToBlockchain` ::: ### Basic Usage Two API styles: #### Client-based API ```ts import { createMemoryClient } from 'tevm' const client = createMemoryClient() const result = await client.tevmCall({ from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH data: '0x' }) ``` #### Tree-shakable API ```ts import { tevmCall } from 'tevm/actions' import { createTevmTransport } from 'tevm/memory-client' import { createTevmNode } from 'tevm/node' import { requestEip1193 } from 'tevm/decorators' const client = createClient({ transport: createTevmTransport() }) const result = await tevmCall(client, { from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH data: '0x' }) ``` ### Parameters ```ts type CallParams = { to?: Address // required except contract deployment data?: Hex value?: bigint // wei gas?: bigint blockTag?: 'latest' | 'pending' | 'earliest' | number createTransaction?: 'on-success' | 'always' | 'never' | boolean // DEPRECATED addToMempool?: 'on-success' | 'always' | 'never' | boolean // requires mining addToBlockchain?: 'on-success' | 'always' | 'never' | boolean // auto-mines skipBalance?: boolean createAccessList?: boolean createTrace?: boolean from?: Address // defaults to first account maxFeePerGas?: bigint maxPriorityFeePerGas?: bigint stateOverrideSet?: StateOverrideSet blockOverrideSet?: BlockOverrideSet onStep?: (data: InterpreterStep, next?: () => void) => void onNewContract?: (data: NewContractEvent, next?: () => void) => void onBeforeMessage?: (data: Message, next?: () => void) => void onAfterMessage?: (data: EVMResult, next?: () => void) => void } ``` ### Return Type ```ts type CallResult = { rawData: Hex // return data executionGasUsed: bigint totalGasSpent?: bigint // includes intrinsic costs txHash?: Hex logs?: Log[] createdAddress?: Address accessList?: Record> trace?: TraceResult errors?: TevmCallError[] } ``` ### Examples #### Simple Contract Call ```ts import { createMemoryClient, PREFUNDED_ACCOUNTS } from 'tevm' import { ERC20 } from 'tevm/contract' import { encodeFunctionData, decodeFunctionResult, parseAbi } from 'viem' const abi = parseAbi(['function balanceOf(address account) view returns (uint256 balance)']) const client = createMemoryClient() const result = await client.tevmCall({ to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH from: PREFUNDED_ACCOUNTS[0].address, deployedBytecode: ERC20.deployedBytecode, data: encodeFunctionData({ abi, functionName: 'balanceOf', args: [PREFUNDED_ACCOUNTS[0].address] }) }) const balance = decodeFunctionResult({ abi, functionName: 'balanceOf', data: result.rawData }) ``` #### Contract Deployment ```ts import { createMemoryClient, PREFUNDED_ACCOUNTS } from 'tevm' import { SimpleContract } from 'tevm/contract' import { encodeDeployData } from 'viem' const client = createMemoryClient() const result = await client.tevmCall({ from: PREFUNDED_ACCOUNTS[0].address, data: encodeDeployData(SimpleContract.deploy(2n)), addToBlockchain: true // auto-mines }) console.log('Deployed at:', result.createdAddress) ``` #### State Override ```ts import { createMemoryClient, PREFUNDED_ACCOUNTS } from 'tevm' const client = createMemoryClient() const result = await client.tevmCall({ from: PREFUNDED_ACCOUNTS[0].address, to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', data: '0x', stateOverrideSet: { [PREFUNDED_ACCOUNTS[0].address]: { balance: 4096n, nonce: 2n, state: {} } } }) ``` #### Debug Trace ```ts import { createMemoryClient } from 'tevm' const client = createMemoryClient() const result = await client.tevmCall({ from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', data: '0x', createTrace: true }) if (result.trace) { result.trace.structLogs.forEach(log => { console.log(log.op, log.stack, log.memory) }) } ``` #### EVM Event Handlers Monitor execution in real-time: ```ts import { createMemoryClient } from 'tevm' import { encodeFunctionData } from 'viem' const client = createMemoryClient() const result = await client.tevmCall({ from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', to: contractAddress, data: encodeFunctionData({ abi, functionName: 'myFunction', args: [arg1, arg2] }), onStep: (step, next) => { console.log( `${step.opcode.name} at PC=${step.pc}`, `Gas: ${step.gasLeft.toString()}`, `Stack depth: ${step.stack.length}` ) next?.() }, onNewContract: (data, next) => { console.log(`New contract: ${data.address.toString()}`) next?.() }, onBeforeMessage: (message, next) => { console.log(`Call to: ${message.to?.toString()}`, `Value: ${message.value.toString()}`, `Gas limit: ${message.gasLimit.toString()}`) next?.() }, onAfterMessage: (result, next) => { console.log(`Result: ${result.execResult.returnValue.toString('hex')}`, `Gas used: ${result.execResult.executionGasUsed.toString()}`) if (result.execResult.exceptionError) { console.error(`Error: ${result.execResult.exceptionError.error}`) } next?.() } }) ``` ### Higher Level APIs While `tevmCall` ## VM & Submodules [Tevm Node](https://github.com/evmts/tevm-monorepo) is built from submodules usable together as an in-memory Ethereum node or independently. High-level APIs (`createMemoryClient`, viem actions, JSON-RPC) sit on top of these. ### Overview Runtime submodules: 1. **VM** - Coordinates transaction execution, block building, state changes, receipts, event hooks. 2. **EVM** - Runs EVM bytecode via `@evmts/zevm/evm`. 3. **Blockchain** - Local block storage, forked block resolution, canonical heads. 4. **StateManager** - Accounts, code, storage, snapshots, fork-backed reads. 5. **TxPool** - Pending transactions including EIP-4844 and EIP-7702 shapes. 6. **ReceiptsManager** - Receipts and logs for JSON-RPC queries, filters, subscriptions. 7. **Common, RLP, Trie, Tx, Utils** - Tevm facades over ZEVM-compatible primitives. ### ZEVM Backing Tevm migrated its low-level EVM, transaction, receipt, txpool, common, RLP, trie, and utility primitives to `@evmts/zevm`. Tevm retains its own public package boundaries and viem-style ergonomics; low-level docs describe Tevm facades over ZEVM-backed primitives. ### EVM Module The EVM module handles bytecode execution and state transitions. Exported from `tevm/evm`, backed by `@evmts/zevm/evm`. ```ts import { createImpersonatedTx } from 'tevm/tx' const vm = await node.getVm() const evm = vm.evm // Direct EVM execution const result = await evm.runCall({ to, data, value: 0n, caller }) // Full transaction via VM const tx = createImpersonatedTx({ impersonatedAddress: caller, to, data, gasLimit: 1_000_000n, }) const txResult = await vm.runTx({ tx }) ``` #### Key Features * **State management**: Executes against Tevm's account, code, and storage state. * **Gas metering**: Tracks execution gas, intrinsic gas, refunds, fee behavior. * **Precompiles**: Built-in plus Tevm custom precompiles. * **Tracing**: Step, message, call trace, prestate trace, flat trace, mux trace, four-byte trace. * **EIP support**: EIP-4844 blob transactions and EIP-7702 EOA code transactions via `tevm/tx`. ### Blockchain Module Manages block lookup, canonical heads, forked block resolution, local block writes. Tevm implementation working with Tevm block types and ZEVM-compatible constants/utilities. ```ts const chain = (await node.getVm()).blockchain const latest = await chain.getCanonicalHeadBlock() const blockByNumber = await chain.getBlock(1234n) const blockByHash = await chain.getBlock(blockHash) await chain.putBlock(localBlock) await chain.delBlock(blockHash) ``` #### Fork Support ```ts const forkedBlock = await chain.getBlock(blockNumber) // Local blocks override forked blocks once written await chain.putBlock(localBlock) ``` ### StateManager Accounts, storage, contract code, state roots, snapshots, fork-backed reads. ```ts const state = (await node.getVm()).stateManager const account = await state.getAccount(address) await state.putAccount(address, account) await state.putContractCode(address, bytecode) const code = await state.getContractCode(address) await state.putContractStorage(address, key, value) const storageValue = await state.getContractStorage(address, key) await state.checkpoint() await state.commit() await state.revert() ``` ### Transaction Pool Pending transactions before block inclusion. Backed by `@evmts/zevm/txpool`; broadens fee classification for fee-market-shaped transactions including EIP-7702. ```ts const pool = await node.getTxPool() await pool.add(tx) await pool.addUnverified(tx) const senderTxs = await pool.getBySenderAddress(sender) const byHash = pool.getByHash([txHash]) const ordered = await pool.txsByPriceAndNonce({ baseFee: 10n, allowedBlobs: 6 }) ``` JSON-RPC exposes `txpool_content`, `txpool_contentFrom`, `txpool_inspect`, `txpool_status`. ### ReceiptsManager Transaction receipts, block receipt lookup, event logs. ```ts const receipts = await node.getReceiptsManager() const blockReceipts = await receipts.getReceipts(blockHash) const receiptResult = await receipts.getReceiptByTxHash(txHash) const logs = await receipts.getLogs(fromBlock, toBlock, addresses, topics) ``` Supports pre-Byzantium, post-Byzantium, and EIP-4844 blob receipt shapes. ### JSON-RPC Integration Submodules power Tevm's RPC handlers: * `eth_*` standard JSON-RPC, including `eth_getProof`, `eth_blobBaseFee`, `eth_simulateV1`, `eth_simulateV2`. * `tevm_*` native calls, account/state management, mining, light sync status. * `debug_*` trace, raw block/header/receipt/transaction, storage range, preimage, modified account methods. * `anvil_*`, `hardhat_*`, `ganache_*`, `evm_*` development-node compatibility. * `engine_*` Engine API payload and forkchoice methods (when `engineApi` enabled). * `txpool_*`, `web3_*`, `net_*`, `rpc_modules` broader client compatibility. ### Best Practices * **Use high-level APIs first**: Prefer `createMemoryClient`, viem actions, or JSON-RPC. * **Drop down deliberately**: Use `vm.runTx`, `evm.runCall`, txpool, or receipts directly for tooling, debuggers, or extensions. * **Handle snapshots carefully**: Use checkpoints and `evm_snapshot`/`evm_revert` flows for test isolation. * **Choose the right trace**: `debug_traceCall`/`debug_traceTransaction` for RPC-compatible tracing; `onStep`, `onBeforeMessage`, `onAfterMessage` for in-process instrumentation. ### Related Topics * [JSON-RPC Support](./json-rpc) * [Managing State](../core/managing-state) * [Transaction Pool](../advanced/txpool) * [Receipts & Logs](../advanced/receipts-and-logs) * [EVM Events](./evm-events) * [EVM Opcodes Reference](https://www.evm.codes/) * [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf) ## Contract Loader :::warning[Coming Soon] The Contract Loader is under development and not yet available in the latest release. This documentation outlines the planned API. ::: Contract Loaders are [actions](/reference/actions) that extract ABI information from contract bytecode (including unverified contracts). Powered by [WhatsABI](https://github.com/shazow/whatsabi), available as [runtime actions](#usage) and [buildtime macros](#network-imports-via-macros): * Discover function selectors from bytecode * Look up function signatures from selectors * Resolve proxy contracts automatically * Access verified ABIs from Sourcify, Etherscan, and other sources Features: * Works with unverified contracts (extracts selectors from bytecode) * Resolves proxy implementations (detects/follows proxy patterns) * Uses multiple sources (Sourcify, Etherscan, bytecode analysis) * Fully typed API ### Usage :::warning Contract macros are disabled by default. Enable by adding `"macros": true` to your `tevm.config.json`. ::: :::code-group ```ts [Tree-shakable API] import { loadContract, createMemoryClient, http } from 'tevm' import { MultiABILoader, EtherscanABILoader, SourcifyABILoader } from 'tevm/whatsabi' const client = createMemoryClient({ fork: { transport: http('https://mainnet.optimism.io') } }) const contract = await loadContract(client, { address: '0x00000000006c3852cbEf3e08E8dF289169EdE581', // Seaport followProxies: true, loaders: [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: 'YOUR_ETHERSCAN_KEY' }) ] }) console.log(`Address: ${contract.address}`) console.log(`ABI entries: ${contract.abi.length}`) console.log(`Human readable ABI: ${contract.humanReadableAbi}`) console.log(`Deployed bytecode available: ${Boolean(contract.deployedBytecode)}`) console.log(`Implementation (if proxy): ${contract.proxyDetails?.[0]?.implementation || 'Not a proxy'}`) const owner = await client.readContract({ ...contract.read.owner(), address: contract.address }) ``` ```ts [Client Extension API] import { createClient, contractLoaderExtension } from 'tevm' import { http } from 'viem' import { BlockscoutABILoader, SourcifyABILoader } from 'tevm/whatsabi' const client = createClient({ transport: http('https://mainnet.optimism.io') }).extend(contractLoaderExtension({ followProxies: true, loaders: [ new SourcifyABILoader(), new BlockscoutABILoader({ apiKey: 'YOUR_BLOCKSCOUT_KEY' }) ] })) const contract = await client.loadContract({ address: '0x00000000006c3852cbEf3e08E8dF289169EdE581' }) const balance = await client.readContract({ ...contract.read.balanceOf('0x123...'), address: contract.address }) // Override defaults per call const anotherContract = await client.loadContract({ address: '0x456...', followProxies: false }) ``` ::: ### Network Imports via Macros Import contracts from any network at build time. :::tip Build-time macros avoid network requests at runtime and provide stronger type safety. Tevm's macro system is modeled after [Bun's macros](https://bun.sh/docs/bundler/macros) and works with all bundlers supported by [Tevm's bundler plugins](/reference/bundler) (Webpack, Rollup, Vite, ESBuild, etc.). ::: :::warning Macros use the [Import Attributes syntax](https://github.com/tc39/proposal-import-attributes) (Stage 3 TC39) with `with {type: 'tevm'}`. Disabled by default — enable via `"macros": true` in `tevm.config.json`. ::: #### Creating Contract Macros Create a file exporting contracts via `loadContract`: ```ts import { createClient, createMemoryClient } from "tevm"; import { http } from "viem"; import { mainnet } from "viem/chains"; import { loadContract } from "tevm"; import { EtherscanABILoader, SourcifyABILoader } from "tevm/whatsabi"; // Pin block height for hermetic builds const client = createMemoryClient({ fork: { transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"), blockNumber: 19000000n, }, }); const loaders = [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: "YOUR_ETHERSCAN_KEY" }), ]; export const usdc = await loadContract(client, { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", followProxies: true, includeBytecode: true, includeSourceCode: true, loaders, }); export const weth = await loadContract(client, { address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", followProxies: true, includeBytecode: true, includeSourceCode: true, loaders, }); ``` #### Using Contract Macros Import with `with { type: 'tevm' }`: ```ts import { usdc } from "./contract-macros.js" with { type: "tevm" }; import { weth } from "./contract-macros.js" with { type: "tevm" }; import { createMemoryClient } from "tevm"; const client = createMemoryClient(); const usdcBalance = await client.readContract({ ...usdc.read.balanceOf("0x123..."), address: usdc.address, }); const wethDeposit = await client.writeContract({ ...weth.write.deposit(), value: 1000000000000000000n, }); ``` :::warning Macros in third-party packages (node\_modules) are blocked. Package authors should pre-build contracts before publishing. ::: #### How Macros Work 1. **Build-time execution**: Bundlers execute `type: 'macro'` imports during build. 2. **Contract resolution**: WhatsABI fetches and analyzes the contract, resolving ABIs and proxies. 3. **Static code generation**: The import is replaced with statically generated ABI/metadata. 4. **Type generation**: TypeScript types for methods, events, properties. #### Benefits * Build-time resolution (no runtime network requests) * Full TypeScript type safety * Proxy resolution * Works with unverified contracts via bytecode analysis * IDE autocomplete and hover documentation ### How It Works 1. **Bytecode analysis**: WhatsABI extracts function selectors by analyzing jump tables and bytecode patterns. Works for unverified contracts. 2. **Signature lookup**: Selectors are looked up in signature databases (matches `transfer`, `approve`, etc.). 3. **Proxy detection**: Detects ERC-1967, Transparent, Beacon patterns; resolves and follows implementations. 4. **On-chain source verification**: Attempts to fetch verified sources from Sourcify, Etherscan, etc. based on chain ID. ### Parameters | Parameter | Type | Description | | ---------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `Address` | **Required**. The contract address to analyze | | `followProxies` | `boolean` | Whether to automatically follow proxy contracts. Default: `false` | | `includeBytecode` | `boolean` | Whether to include contract bytecode in the returned contract. Default: `false` | | `includeSourceCode` | `boolean` | Whether to include contract source code as [SolcInputSources](https://github.com/evmts/tevm-monorepo/blob/00e1397cf07f637a93b7eaf51178bda50f5c5d6e/bundler-packages/solc/src/solcTypes.ts#L319). Default: `false` | | `loaders` | `ABILoader[]` | Array of ABI loaders to use for resolving contract ABIs. See [Available Loaders](#available-loaders) | | `enableExperimentalMetadata` | `boolean` | Whether to include experimental metadata like event topics. Default: `false` | | `signatureLookup` | `SignatureLookup \| false` | Custom signature lookup or `false` to disable. Default: uses WhatsABI's default lookup | | `onProgress` | `(phase: string, ...args: any[]) => void` | Progress callback | | `onError` | `(phase: string, error: Error) => boolean \| void` | Error callback | ### Return Value Returns a [Tevm Contract](/reference/contract) instance: | Property | Type | Description | | --------------- | -------------------------------------------------------------------- | ------------------------------------------------------------ | | `abi` | `Abi` | The resolved ABI from bytecode or verified sources | | `address` | `Address` | The contract address (may differ if proxies followed) | | `read` | `Object` | Type-safe read methods for view/pure functions | | `write` | `Object` | Type-safe write methods for state-changing functions | | `events` | `Object` | Type-safe event filters for subscription | | `withAddress` | `Function` | Create a new instance with a different address | | `abiLoadedFrom` | `{name: string, url?: string}` | Where the ABI was loaded from | | `proxyDetails` | `Array<{name: string, implementation?: Address, selector?: string}>` | If proxy, details about the implementation | | `sources` | `SolcInputSources` | If `includeSourceCode` is `true`, contract source code files | ### Available Loaders Import from `tevm/whatsabi`: #### MultiABILoader Tries multiple loaders until one succeeds. ```ts import { MultiABILoader } from "tevm/whatsabi"; const loader = new MultiABILoader([ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: "YOUR_ETHERSCAN_KEY" }), ]); const contract = await loadContract(client, { address: "0x123...", loaders: [loader], }); ``` #### EtherscanABILoader Loads ABIs from Etherscan and similar explorers. ```ts import { EtherscanABILoader } from "tevm/whatsabi"; const etherscan = new EtherscanABILoader({ apiKey: "YOUR_ETHERSCAN_KEY" }); const polygonscan = new EtherscanABILoader({ apiKey: "YOUR_POLYGONSCAN_KEY", baseUrl: "https://api.polygonscan.com/api", }); ``` #### SourcifyABILoader Loads ABIs from Sourcify's decentralized repository. ```ts import { SourcifyABILoader } from "tevm/whatsabi"; const sourcify = new SourcifyABILoader(); const customSourcify = new SourcifyABILoader({ baseUrl: "https://your-sourcify-server.com", }); ``` #### BlockscoutABILoader Loads ABIs from Blockscout explorers. ```ts import { BlockscoutABILoader } from "tevm/whatsabi"; const blockscout = new BlockscoutABILoader({ apiKey: "YOUR_BLOCKSCOUT_KEY" }); const customBlockscout = new BlockscoutABILoader({ baseUrl: "https://your-blockscout-instance.com", }); ``` ### Examples #### Resolving a Proxy Contract ```ts import { loadContract, createMemoryClient } from "tevm"; import { http } from "viem"; import { SourcifyABILoader, EtherscanABILoader } from "tevm/whatsabi"; const client = createMemoryClient({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY") }, }); const contract = await loadContract(client, { address: "0x4f8AD938eBA0CD19155a835f617317a6E788c868", followProxies: true, loaders: [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: "YOUR_KEY" }), ], }); console.log(`Original: 0x4f8AD938eBA0CD19155a835f617317a6E788c868`); console.log(`Implementation: ${contract.address}`); console.log(`Proxies detected: ${contract.proxyDetails.length}`); ``` :::note If the address is a proxy, the returned `address` is the implementation, not the proxy. ::: #### Working with Unverified Contracts ```ts import { loadContract, createMemoryClient } from "tevm"; import { http } from "viem"; const client = createMemoryClient({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY") }, }); const contract = await loadContract(client, { address: "0xUnverifiedContractAddress", loaders: [], // bytecode analysis only }); contract.abi .filter((item) => item.type === "function") .forEach((func) => console.log(`- ${func.name || "Unknown"}`)); ``` #### Creating a Contract Macro ```ts // aave-macro.js import { createClient } from "tevm"; import { http } from "viem"; import { optimism } from "viem/chains"; import { loadContract } from "tevm"; import { SourcifyABILoader, EtherscanABILoader } from "tevm/whatsabi"; const client = createClient({ chain: optimism, transport: http("https://mainnet.optimism.io"), }); const loaders = [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: "YOUR_ETHERSCAN_KEY", baseUrl: "https://api-optimistic.etherscan.io/api", }), ]; export const aaveV3Pool = await loadContract(client, { address: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", // Aave V3 Pool on Optimism followProxies: true, includeBytecode: true, includeSourceCode: true, loaders, }); ``` ```ts // app.js import { aaveV3Pool } from "./aave-macro.js" with { type: "tevm" }; import { createPublicClient, http } from "viem"; import { optimism } from "viem/chains"; const client = createPublicClient({ chain: optimism, transport: http() }); const reserves = await client.readContract({ ...aaveV3Pool.read.getReservesList(), address: aaveV3Pool.address, }); console.log(`Aave reserves on Optimism: ${reserves.length}`); ``` #### Using Multiple Loaders ```ts import { loadContract, createMemoryClient } from "tevm"; import { http } from "viem"; import { MultiABILoader, SourcifyABILoader, EtherscanABILoader, BlockscoutABILoader, } from "tevm/whatsabi"; const client = createMemoryClient({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY") }, }); const multiLoader = new MultiABILoader([ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: "YOUR_ETHERSCAN_KEY" }), new BlockscoutABILoader({ apiKey: "YOUR_BLOCKSCOUT_KEY" }), ]); const contract = await loadContract(client, { address: "0xVerifiedContractAddress", followProxies: true, loaders: [multiLoader], onProgress: (phase) => console.log(`Phase: ${phase}`), }); const name = await client.readContract({ ...contract.read.name(), address: contract.address, }); ``` #### Working with Source Code ```ts import { loadContract, createMemoryClient } from "tevm"; import { http } from "viem"; import { SourcifyABILoader, EtherscanABILoader } from "tevm/whatsabi"; const client = createMemoryClient({ fork: { transport: http("https://mainnet.infura.io/v3/YOUR-KEY") }, }); const contract = await loadContract(client, { address: "0xVerifiedContractAddress", followProxies: true, includeBytecode: true, includeSourceCode: true, loaders: [ new SourcifyABILoader(), new EtherscanABILoader({ apiKey: "YOUR_ETHERSCAN_KEY" }), ], }); if (contract.sources) { Object.keys(contract.sources).forEach((fileName) => { console.log(`- ${fileName}`); const sourceContent = contract.sources[fileName].content; console.log(`First 100 chars: ${sourceContent.substring(0, 100)}...`); }); } const balance = await client.readContract({ ...contract.read.balanceOf("0x123..."), address: contract.address, }); ``` #### Progress Tracking ```ts import { loadContract, createMemoryClient } from "tevm"; import { http } from "viem"; import { SourcifyABILoader } from "tevm/whatsabi"; const progressTracker = (phase, ...args) => { switch (phase) { case "bytecode": console.log("Analyzing bytecode..."); break; case "proxy": console.log("Checking proxy patterns..."); break; case "abi": console.log("Loading ABI..."); break; } }; const contract = await loadContract(client, { address: "0xLargeContractAddress", followProxies: true, loaders: [new SourcifyABILoader()], onProgress: progressTracker, }); ``` ### When to Use Contract Loader vs Direct Solidity Imports **Use Contract Loader when:** * Working with third-party contracts (source of truth managed elsewhere) * Needing latest contract implementation * Dealing with unverified contracts (extract from bytecode) * Interacting with proxy contracts (auto-resolve implementations) * Building exploratory tools * Creating SDKs for protocols (macros generate type-safe interfaces at build time) **Use Direct Solidity Imports when:** * You own the contract code * Need fully hermetic, deterministic builds * Working with fixed contract versions * Need complete control over ABI generation :::tip Contract Loader is perfect for exploratory tools, frontends that work with any contract, or debugging contract interactions. As a macro, it validates contracts at build time with full type safety. ::: #### Ensuring Hermetic Builds 1. **Pin block heights**: Use `createMemoryClient` with `blockNumber`: ```ts const client = createMemoryClient({ fork: { transport: http("https://mainnet.infura.io"), blockNumber: 19000000n, }, }); ``` 2. **Generate and commit contracts**: ```bash tevm generate contract-loader --address 0x123... --output ./src/contracts ``` 3. **Use published npm packages** when available: ```bash npm install @uniswap/v3-core ``` :::note[Feedback] This API is in the planning phase. Share feedback via [GitHub](https://github.com/evmts/tevm-monorepo/issues/new?labels=enhancement\&template=feature_request.md\&title=%5BFeature%5D%3A+Contract+Loader+Feedback) or [Telegram](https://t.me/+ANThR9bHDLAwMjUx). ::: ## Custom Precompiles Tevm lets you register JavaScript functions as EVM precompiles at fixed addresses — useful for crypto, oracles, complex math, bridges, custom data structures, and test helpers. ### Quick Start #### Basic Example ```typescript showLineNumbers filename="simple-precompile.ts" import { createTevmNode, definePrecompile, PREFUNDED_ACCOUNTS } from 'tevm' import { createAddress } from 'tevm/address' import { createContract } from 'tevm/contract' import { hexToBytes, parseAbi } from 'tevm/utils' import { createImpersonatedTx } from 'tevm/tx' import { EvmError, EvmErrorMessage } from 'tevm/evm' const customPrecompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function double(bytes) returns (bytes)']), address: '0x0000000000000000000000000000000000000123' }), call: async ({ data }) => { const input = Array.from(hexToBytes(data)) return { returnValue: new Uint8Array(input.map(byte => Number(byte) * 2)), executionGasUsed: 200n, } }, }) const node = createTevmNode({ customPrecompiles: [customPrecompile.precompile()], }) const tx = createImpersonatedTx({ impersonatedAddress: createAddress(PREFUNDED_ACCOUNTS[0].address), to: customPrecompile.contract.address, data: '0x00', gasLimit: 100000n, maxFeePerGas: 10n, maxPriorityFeePerGas: 1n, }) const vm = await node.getVm() const result = await vm.runTx({ tx }) ``` :::warning[Modern Gas Rules] When calling `vm.runTx()` directly, build a valid transaction for the active hardfork. Prague uses EIP-1559 fees, so `maxFeePerGas` must be at least the block base fee and the gas limit must cover intrinsic calldata gas. ::: :::tip[Type Safety] `definePrecompile` provides full type safety based on the provided ABI. ::: #### Complex Example ```typescript showLineNumbers filename="advanced-precompile.ts" import { createTevmNode, definePrecompile } from 'tevm' import { createAddress } from 'tevm/address' import { createContract } from 'tevm/contract' import { parseAbi, hexToBytes } from 'tevm/utils' import { createImpersonatedTx } from 'tevm/tx' import { EvmError, EvmErrorMessage } from 'tevm/evm' import { keccak256 } from 'tevm/crypto' const hashPrecompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function hash(bytes) returns (bytes32)']), address: '0x0000000000000000000000000000000000000321' }), call: async ({ data, gasLimit }) => { const gasPerByte = 10n const gasUsed = BigInt(hexToBytes(data).length) * gasPerByte + 100n const hash = keccak256(data) return { returnValue: hash, executionGasUsed: gasUsed } }, }) const node = createTevmNode({ customPrecompiles: [customPrecompile.precompile(), hashPrecompile.precompile()], }) ``` ### Precompile Interface Every precompile needs an ABI/address and a call handler: ```typescript showLineNumbers filename="interface.ts" const contract = createContract({ abi: parseAbi(['function myFunction(uint256) returns (uint256)']), address: '0x0000000000000000000000000000000000000123' }) ``` ```typescript showLineNumbers filename="handler.ts" const call = async ({ data, gasLimit }: PrecompileInput): Promise => { return { returnValue: new Uint8Array([/* result data */]), executionGasUsed: 1000n, // Optional: exceptionError for when the operation fails } } ``` ```typescript showLineNumbers filename="register.ts" const myPrecompile = definePrecompile({ contract, call }) const node = createTevmNode({ customPrecompiles: [myPrecompile.precompile()] }) ``` ### Example Implementations #### JavaScript State Example ```typescript showLineNumbers filename="state-precompile.ts" const storage = new Map() const statePrecompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function store(bytes32,bytes32)']), address: '0x0000000000000000000000000000000000000124' }), call: async ({ data, gasLimit }) => { const bytes = hexToBytes(data) const key = bytes.slice(0, 32) const value = bytes.slice(32, 64) storage.set(Buffer.from(key).toString('hex'), value) return { returnValue: new Uint8Array(), executionGasUsed: 200n } }, }) ``` :::tip[Persistent Storage] This demonstrates JavaScript-side state persisting across precompile calls. Use normal EVM storage from Solidity if the data must be visible as contract storage. ::: #### Gas Calculation Example ```typescript showLineNumbers filename="gas-precompile.ts" const gasPrecompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function processWithGas(bytes)']), address: '0x0000000000000000000000000000000000000125' }), call: async ({ data, gasLimit }) => { const gasUsed = BigInt(hexToBytes(data).length * 100) if (gasUsed > gasLimit) { return { returnValue: new Uint8Array(), exceptionError: new EvmError(EvmErrorMessage.OUT_OF_GAS), executionGasUsed: gasLimit, } } return { returnValue: new Uint8Array(), executionGasUsed: gasUsed } }, }) ``` :::warning[Gas Handling] Always check gas before executing and return the exact amount used. ::: #### Error Handling Example ```typescript showLineNumbers filename="error-precompile.ts" const errorPrecompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function process(bytes)']), address: '0x0000000000000000000000000000000000000126' }), call: async ({ data, gasLimit }) => { try { if (data === '0x') { throw new Error('Empty input not allowed') } return { returnValue: processData(data), executionGasUsed: 200n } } catch (error) { return { returnValue: new Uint8Array(), exceptionError: new EvmError(`Precompile error: ${error.message}`), executionGasUsed: gasLimit, } } }, }) ``` #### Multiple Precompiles Example ```typescript showLineNumbers filename="multiple-precompiles.ts" const precompileA = definePrecompile({ contract: createContract({ abi: parseAbi(['function processA() returns (bytes)']), address: '0x0000000000000000000000000000000000000127' }), call: async () => ({ returnValue: new Uint8Array([1]), executionGasUsed: 200n }), }) const precompileB = definePrecompile({ contract: createContract({ abi: parseAbi(['function processB() returns (bytes)']), address: '0x0000000000000000000000000000000000000128' }), call: async () => ({ returnValue: new Uint8Array([2]), executionGasUsed: 200n }), }) const node = createTevmNode({ customPrecompiles: [precompileA.precompile(), precompileB.precompile()], }) ``` ### Use Cases * Cryptographic operations (encryption, hashing, signature verification) * Oracle / external data simulation for local testing * Complex math that would be gas-intensive in Solidity * Cross-chain bridge verification logic * Custom data structures (trees, graphs, etc.) * Testing helpers (time manipulation, state snapshots) ### Best Practices #### Gas Calculation Calculate gas based on actual work performed, similar to native EVM ops. ```typescript showLineNumbers filename="gas-best-practices.ts" const precompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function process(bytes)']), address: createAddress('0x0000000000000000000000000000000000000123') }), call: async ({ data, gasLimit }) => { const baseGas = 100n const dataGas = BigInt(hexToBytes(data).length * 10) const totalGas = baseGas + dataGas if (totalGas > gasLimit) { return { returnValue: new Uint8Array(), exceptionError: new EvmError(EvmErrorMessage.OUT_OF_GAS), executionGasUsed: gasLimit, } } return { returnValue: processData(data), executionGasUsed: totalGas } }, }) ``` #### Error Handling Use appropriate error types and include detailed info. ```typescript showLineNumbers filename="error-best-practices.ts" const precompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function process(bytes32,uint256)']), address: createAddress('0x0000000000000000000000000000000000000123') }), call: async ({ data, gasLimit }) => { try { if (hexToBytes(data).length < 36) { return { returnValue: new Uint8Array(), exceptionError: new EvmError('Invalid input: insufficient data'), executionGasUsed: 100n, } } return { returnValue: result, executionGasUsed: gasUsed } } catch (error) { console.error('Precompile execution error:', error) return { returnValue: new Uint8Array(), exceptionError: new EvmError(error.message || 'Unknown precompile error'), executionGasUsed: Math.min(100n, gasLimit), } } }, }) ``` #### State Management For precompiles that maintain state across calls via EVM storage: ```typescript showLineNumbers filename="state-best-practices.ts" const statePrecompile = definePrecompile({ contract: createContract({ abi: parseAbi(['function getData(bytes32) returns (bytes32)', 'function setData(bytes32,bytes32)']), address: createAddress('0x0000000000000000000000000000000000000124') }), call: async ({ data, gasLimit }) => { const vm = await node.getVm() const stateManager = vm.stateManager const address = createAddress(statePrecompile.contract.address) const selector = data.slice(0, 4) const isGetData = selector[0] === 0x9b & selector[1] === 0x18 & selector[2] === 0x30 & selector[3] === 0x4c if (isGetData) { const key = data.slice(4, 36) const value = await stateManager.getStorage(address, key) return { returnValue: value, executionGasUsed: 200n } } else { const key = data.slice(4, 36) const value = data.slice(36, 68) await stateManager.putStorage(address, key, value) return { returnValue: new Uint8Array(), executionGasUsed: 500n } } }, }) ``` #### Performance Cache results for expensive deterministic operations. ```typescript showLineNumbers filename="performance-best-practices.ts" const precompile = definePrecompile({ // Contract definition... call: async ({ data, gasLimit }) => { const cacheKey = data.toString() if (resultsCache.has(cacheKey)) { return resultsCache.get(cacheKey) } const result = performExpensiveOperation(data) resultsCache.set(cacheKey, { returnValue: result, executionGasUsed: gasUsed }) return { returnValue: result, executionGasUsed: gasUsed } }, }) ``` ### Related Resources * [Contract Reference](/reference/contract) * [State Management](/core/managing-state) * [JSON-RPC Support](../api/json-rpc) * [EVM Precompiles Reference](https://www.evm.codes/precompiled) ## Performance & Profiler Tevm Node ships a built-in profiler that tracks execution time, gas, and other metrics per EVM operation. ### Quick Start #### Basic Setup ```typescript showLineNumbers filename="basic-profiling.ts" import { createTevmNode } from 'tevm' const node = createTevmNode({ profiler: { enabled: true, includeOpcodes: true, includePrecompiles: true, } }) const vm = await node.getVm() await vm.runTx({ /* transaction details */ }) const logs = vm.evm.getPerformanceLogs() vm.evm.clearPerformanceLogs() ``` #### Complete Example ```typescript showLineNumbers filename="contract-profiling.ts" import { createTevmNode } from 'tevm' const node = createTevmNode({ profiler: { enabled: true, includeOpcodes: true, includePrecompiles: true, includeMemory: true, includeStorage: true, callStackDepth: 10, } }) const vm = await node.getVm() const deployTx = createTx({ /* deployment transaction */ }) await vm.runTx({ tx: deployTx }) const contractAddress = deployTx.createdAddress vm.evm.clearPerformanceLogs() const callTx = createTx({ to: contractAddress, data: '0xa9059cbb000000000000000000000000123...', }) await vm.runTx({ tx: callTx }) const logs = vm.evm.getPerformanceLogs() const analysis = analyzePerformance(logs) console.log(`Total execution time: ${analysis.totalTime}ms`) console.log(`Total gas used: ${analysis.totalGas}`) console.log(`Hotspots: ${JSON.stringify(analysis.hotspots)}`) ``` ### Profiler Configuration * **Opcode Tracking** — execution time and gas per opcode * **Call Stack Analysis** — full call tree * **Gas Analysis** — most gas-hungry operations * **Memory Usage** — allocation and access patterns * **Storage I/O** — SLOAD/SSTORE impact * **Precompile Usage** — built-in and custom precompiles :::tip[Configuration Options] For general use, opcode + call tracking is a good balance between detail and overhead. ::: ### Log Types All log entries share a base shape: ```typescript showLineNumbers filename="log-types.ts" interface PerformanceLog { type: 'opcode' | 'precompile' | 'call' | 'create' startTime: number // Performance.now() start endTime: number // Performance.now() end executionTime: number // ms gasUsed?: bigint } ``` ```typescript showLineNumbers filename="opcode-logs.ts" interface OpcodeLog extends PerformanceLog { type: 'opcode' opcode: string // e.g. "ADD", "SSTORE" pc: number stack?: bigint[] } ``` ```typescript showLineNumbers filename="call-logs.ts" interface CallLog extends PerformanceLog { type: 'call' from: string to: string value: bigint input: Uint8Array depth: number } ``` ```typescript showLineNumbers filename="precompile-logs.ts" interface PrecompileLog extends PerformanceLog { type: 'precompile' address: string name: string input: Uint8Array } ``` ### Performance Analysis #### Opcode Analysis ```typescript showLineNumbers filename="opcode-analysis.ts" const opcodeStats = logs .filter(log => log.type === 'opcode') .reduce((acc, log) => { const key = log.opcode acc[key] = acc[key] || { count: 0, totalTime: 0, totalGas: 0n } acc[key].count++ acc[key].totalTime += log.executionTime acc[key].totalGas += log.gasUsed ?? 0n return acc }, {}) const expensiveOpsByTime = Object.entries(opcodeStats) .sort(([, a], [, b]) => b.totalTime - a.totalTime) .slice(0, 5) .map(([opcode, stats]) => ({ opcode, count: stats.count, totalTimeMs: stats.totalTime.toFixed(2), avgTimeMs: (stats.totalTime / stats.count).toFixed(2) })) console.table(expensiveOpsByTime) ``` :::note[Common Hotspots] Storage ops (SLOAD, SSTORE), modular math (MULMOD, ADDMOD), and external calls (CALL, DELEGATECALL) are typically the most expensive. ::: #### Call Tree Analysis ```typescript showLineNumbers filename="call-analysis.ts" const callTree = logs .filter(log => log.type === 'call') .map(log => ({ from: log.from, to: log.to, executionTime: log.executionTime, gasUsed: log.gasUsed, depth: log.depth })) const contractTimes = callTree.reduce((acc, call) => { acc[call.to] = (acc[call.to] || 0) + call.executionTime return acc }, {}) const slowestContracts = Object.entries(contractTimes) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([address, time]) => `${address}: ${time.toFixed(2)}ms`) ``` #### Gas Analysis ```typescript showLineNumbers filename="gas-analysis.ts" const gasTimeline = logs .filter(log => log.gasUsed !== undefined) .map(log => ({ timestamp: log.startTime, gasUsed: log.gasUsed, type: log.type })) const gasEfficiencyByType = gasTimeline.reduce((acc, log) => { acc[log.type] = acc[log.type] || { totalGas: 0n, totalTime: 0, count: 0 } acc[log.type].totalGas += log.gasUsed acc[log.type].totalTime += log.executionTime acc[log.type].count++ return acc }, {}) Object.entries(gasEfficiencyByType).forEach(([type, stats]) => { const gasPerMs = Number(stats.totalGas) / stats.totalTime console.log(`${type}: ${gasPerMs.toFixed(2)} gas/ms (${stats.count} operations)`) }) ``` ### Use Cases #### Smart Contract Optimization ```typescript showLineNumbers filename="contract-optimization.ts" const vm = await node.getVm() const deployTx = createTx({ data: contractBytecode }) await vm.runTx({ tx: deployTx }) const contractAddress = deployTx.createdAddress vm.evm.clearPerformanceLogs() const functionCallTx = createTx({ to: contractAddress, data: encodeFunctionData({ abi, functionName: 'expensiveFunction', args: [param1, param2] }) }) await vm.runTx({ tx: functionCallTx }) const logs = vm.evm.getPerformanceLogs() const hotspots = identifyHotspots(logs) console.log('Optimization targets:', hotspots) ``` #### Compare Different Implementations ```typescript showLineNumbers filename="implementation-comparison.ts" async function compareImplementations(implementations) { const results = [] const vm = await node.getVm() for (const impl of implementations) { vm.evm.clearPerformanceLogs() const deployTx = createTx({ data: impl.bytecode }) await vm.runTx({ tx: deployTx }) const callTx = createTx({ to: deployTx.createdAddress, data: impl.encodedFunctionCall }) await vm.runTx({ tx: callTx }) const logs = vm.evm.getPerformanceLogs() results.push(analyzePerformance(logs, impl.name)) } return compareResults(results) } // Example output: // Implementation A: 1.2ms, 45,000 gas // Implementation B: 0.8ms, 32,000 gas (29% improvement) ``` #### Gas Optimization ```typescript showLineNumbers filename="gas-optimization.ts" const contractGasUsage = logs .filter(log => log.type === 'opcode' && log.gasUsed) .reduce((acc, log) => { const pcKey = log.pc.toString().padStart(4, '0') acc[pcKey] = acc[pcKey] || { opcode: log.opcode, count: 0, totalGas: 0n } acc[pcKey].count++ acc[pcKey].totalGas += log.gasUsed return acc }, {}) const gasHotspots = Object.entries(contractGasUsage) .sort(([, a], [, b]) => Number(b.totalGas - a.totalGas)) .slice(0, 10) ``` ### Best Practices #### Targeted Profiling Profile specific operations in isolation for accurate results. ```typescript showLineNumbers filename="targeted-profiling.ts" vm.evm.clearPerformanceLogs() await vm.runTx({ tx: specificOperationTx }) const logs = vm.evm.getPerformanceLogs() const analysis = analyzePerformance(logs) vm.evm.clearPerformanceLogs() ``` #### Memory Management :::warning[Handle Large Log Volumes] Profiling generates a lot of data. Clear logs periodically. ::: ```typescript showLineNumbers filename="memory-management.ts" async function profileWithMemoryManagement(operations) { const results = [] for (const op of operations) { vm.evm.clearPerformanceLogs() await vm.runTx({ tx: op.tx }) const logs = vm.evm.getPerformanceLogs() const summary = summarizeLogs(logs) results.push({ operation: op.name, summary }) vm.evm.clearPerformanceLogs() } return results } ``` #### Comparative Analysis Always measure before/after optimization. ```typescript showLineNumbers filename="comparative-analysis.ts" async function measureOptimizationImpact(originalCode, optimizedCode) { vm.evm.clearPerformanceLogs() await runImplementation(originalCode) const beforeMetrics = analyzePerformance(vm.evm.getPerformanceLogs()) vm.evm.clearPerformanceLogs() await runImplementation(optimizedCode) const afterMetrics = analyzePerformance(vm.evm.getPerformanceLogs()) return { timeImprovement: (1 - afterMetrics.totalTime / beforeMetrics.totalTime) * 100, gasImprovement: (1 - Number(afterMetrics.totalGas) / Number(beforeMetrics.totalGas)) * 100, details: compareMetrics(beforeMetrics, afterMetrics) } } ``` #### Production Use :::warning[Profiler Overhead] The profiler adds overhead. Use selective options in production. ::: ```typescript showLineNumbers filename="production-profiling.ts" const node = createTevmNode({ profiler: { enabled: process.env.ENABLE_PROFILING === 'true', includeOpcodes: false, samplingRate: 0.01, // Profile 1% of operations includeMemory: false, includeStorage: false, includeHighLevelCalls: true } }) ``` ### Related Resources * [VM & Submodules](../api/vm-and-submodules) * [Gas Estimation](../api/methods) * [Transaction Pool](./txpool) * [Solidity Optimizer](https://docs.soliditylang.org/en/latest/internals/optimizer.html) ## Receipts & Logs Tevm Node manages transaction receipts and event logs through the ReceiptsManager and filter system — enabling event listening, status tracking, and log filtering. ### Quick Start :::code-group ```typescript [Basic Usage] showLineNumbers filename="basic-receipt-usage.ts" import { createTevmNode, PREFUNDED_ACCOUNTS } from 'tevm' import { callHandler, mineHandler } from 'tevm/actions' import { hexToBytes } from 'tevm/utils' const node = createTevmNode() const receiptsManager = await node.getReceiptsManager() const callResult = await callHandler(node)({ addToMempool: true, from: PREFUNDED_ACCOUNTS[0].address, to: '0x2345678901234567890123456789012345678901', value: 1000000000000000000n, }) await mineHandler(node)({}) if (!callResult.txHash) { throw new Error('Transaction was not added to the mempool') } const receiptResult = await receiptsManager.getReceiptByTxHash( hexToBytes(callResult.txHash) ) if (receiptResult) { const [receipt, blockHash, txIndex, logIndex] = receiptResult console.log({ status: 'status' in receipt ? receipt.status : undefined, gasUsed: receipt.cumulativeBlockGasUsed, logs: receipt.logs }) } ``` ```typescript [Log Filtering] showLineNumbers filename="log-filtering.ts" import { createTevmNode } from 'tevm' import { createAddress } from 'tevm/address' import { hexToBytes } from 'tevm/utils' const node = createTevmNode() const receiptsManager = await node.getReceiptsManager() const vm = await node.getVm() const fromBlock = await vm.blockchain.getBlock(0n) const toBlock = await vm.blockchain.getCanonicalHeadBlock() const contractAddress = createAddress('0x1234567890123456789012345678901234567890') // Filter by contract address const addressLogs = await receiptsManager.getLogs( fromBlock, toBlock, [contractAddress.toBytes()], undefined ) // Filter by event topic (Transfer) const eventTopic = hexToBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef') const topicLogs = await receiptsManager.getLogs( fromBlock, toBlock, undefined, [eventTopic] ) console.log(`Found ${addressLogs.length} logs from the contract`) console.log(`Found ${topicLogs.length} Transfer events`) ``` ::: ### Receipt Types Tevm supports receipts across all Ethereum hard forks: * **Pre-Byzantium** — uses state root for transaction results * **Post-Byzantium** — uses status codes (success/failure) * **EIP-4844** — includes blob gas information ```typescript showLineNumbers filename="receipt-types.ts" interface PreByzantiumReceipt { stateRoot: Uint8Array // Merkle root after transaction cumulativeBlockGasUsed: bigint logs: Log[] // No status field } interface PostByzantiumReceipt { status: number // 1 = success, 0 = failure cumulativeBlockGasUsed: bigint logs: Log[] } interface EIP4844Receipt extends PostByzantiumReceipt { blobGasUsed: bigint blobGasPrice: bigint } ``` ```typescript showLineNumbers filename="receipt-handling.ts" function processReceipt(receiptResult) { if (!receiptResult) return 'Receipt not found' const [receipt] = receiptResult if ('status' in receipt) { return `Transaction ${receipt.status === 1 ? 'succeeded' : 'failed'}` } else { return `Transaction included with state root: 0x${Buffer.from(receipt.stateRoot).toString('hex')}` } } ``` :::tip[Type Detection] Always check the receipt structure before accessing properties — the type depends on the active hard fork. ::: ### Working with Event Logs #### Contract Deployment ```typescript showLineNumbers filename="deploy-contract.ts" import { createTevmNode, PREFUNDED_ACCOUNTS } from 'tevm' import { callHandler, mineHandler } from 'tevm/actions' import { SimpleContract } from 'tevm/contract' import { encodeDeployData } from 'viem' const node = createTevmNode() const deployResult = await callHandler(node)({ addToMempool: true, from: PREFUNDED_ACCOUNTS[0].address, data: encodeDeployData(SimpleContract.deploy(2n)), throwOnFail: false, }) await mineHandler(node)({}) const contractAddress = deployResult.createdAddress if (!contractAddress) { throw new Error('Contract deployment failed') } ``` #### Emit Events ```typescript showLineNumbers filename="emit-events.ts" import { PREFUNDED_ACCOUNTS } from 'tevm' import { callHandler, mineHandler } from 'tevm/actions' import { SimpleContract } from 'tevm/contract' import { encodeFunctionData } from 'viem' const contract = SimpleContract.withAddress(contractAddress) const callResult = await callHandler(node)({ blockTag: 'pending', addToMempool: true, from: PREFUNDED_ACCOUNTS[0].address, to: contractAddress, data: encodeFunctionData(contract.write.set(42n)), gas: 100000n, throwOnFail: false, }) await mineHandler(node)({}) ``` #### Query Logs ```typescript showLineNumbers filename="query-logs.ts" import { createAddress } from 'tevm/address' import { hexToBytes } from 'tevm/utils' const vm = await node.getVm() const receiptsManager = await node.getReceiptsManager() const fromBlock = await vm.blockchain.getBlock(0n) const toBlock = await vm.blockchain.getCanonicalHeadBlock() const contract = createAddress(contractAddress) // 1. All logs from the contract const contractLogs = await receiptsManager.getLogs( fromBlock, toBlock, [contract.toBytes()], undefined ) // 2. Logs for a specific event const setEventSignature = '0x012c78e2b84325878b1bd9d250d772cfe5bda7722d795f45036fa5e1e6e303fc' const setLogs = await receiptsManager.getLogs( fromBlock, toBlock, [contract.toBytes()], [hexToBytes(setEventSignature)] ) // 3. Logs with wildcard topics const wildcardLogs = await receiptsManager.getLogs( fromBlock, toBlock, [contract.toBytes()], [null] ) ``` #### Process Log Data ```typescript showLineNumbers filename="decode-logs.ts" // ReceiptsManager logs are returned with their block and transaction metadata. for (const { log, block, txIndex, logIndex } of setLogs) { const [address, topics, data] = log const value = BigInt('0x' + Buffer.from(data).toString('hex')) console.log(`Contract: 0x${Buffer.from(address).toString('hex')}`) console.log(`Topic: 0x${Buffer.from(topics[0]).toString('hex')}`) console.log(`Value: ${value}`) console.log(`Block: ${block.header.number}, TxIndex: ${txIndex}, LogIndex: ${logIndex}`) } ``` ### Advanced Features #### Complex Filtering Ethereum logs support up to 4 topics: ```typescript showLineNumbers filename="complex-filtering.ts" const fromAddress = '0x1234567890123456789012345678901234567890' const toAddress = '0x2345678901234567890123456789012345678901' // undefined = wildcard const topics = [ hexToBytes('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'), // Transfer hexToBytes('0x000000000000000000000000' + fromAddress.slice(2)), hexToBytes('0x000000000000000000000000' + toAddress.slice(2)) ] const filteredLogs = await receiptsManager.getLogs( fromBlock, toBlock, [contractAddress.toBytes()], topics ) ``` #### Multiple Addresses ```typescript showLineNumbers filename="multi-contract-filtering.ts" const tokenAddress = createAddress('0x1234567890123456789012345678901234567890') const marketplaceAddress = createAddress('0x2345678901234567890123456789012345678901') const transferEvent = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' const transfers = await receiptsManager.getLogs( fromBlock, toBlock, [tokenAddress.toBytes(), marketplaceAddress.toBytes()], [hexToBytes(transferEvent)] ) const tokenEvents = transfers.filter(log => Buffer.from(log.address).toString('hex') === Buffer.from(tokenAddress.toBytes()).toString('hex') ) const marketplaceEvents = transfers.filter(log => Buffer.from(log.address).toString('hex') === Buffer.from(marketplaceAddress.toBytes()).toString('hex') ) ``` #### Receipt Indexing :::note[Internal Indexing] ReceiptsManager maintains indexes for efficient queries: tx hash → receipt, block hash → receipts, address → logs, topics → logs. ::: ```typescript showLineNumbers filename="receipt-indexing.ts" // Receipt by tx hash const receipt = await receiptsManager.getReceiptByTxHash(txHash) // All receipts in a block const block = await vm.blockchain.getCanonicalHeadBlock() const receipts = await receiptsManager.getReceipts(block.hash()) // Built-in limits const GET_LOGS_LIMIT = 10000 // Max logs returned const GET_LOGS_LIMIT_MEGABYTES = 150 // Max response size const GET_LOGS_BLOCK_RANGE_LIMIT = 2500 // Max block range ``` ### Best Practices * **Efficient Queries** — use specific filters and limited block ranges * **Handle Null Results** — always check for null/undefined receipts * **Type Safety** — check receipt type before accessing fields * **Pagination** — paginate large log queries #### Efficient Log Queries ```typescript showLineNumbers filename="efficient-queries.ts" const latestBlock = await vm.blockchain.getCanonicalHeadBlock() const blockNumber = latestBlock.header.number // Last 100 blocks only const fromBlock = await vm.blockchain.getBlock( blockNumber - 100n > 0n ? blockNumber - 100n : 0n ) const logs = await receiptsManager.getLogs( fromBlock, latestBlock, [contractAddress.toBytes()], [eventTopic] ) ``` #### Proper Error Handling ```typescript showLineNumbers filename="error-handling.ts" async function safeGetReceipt(txHash) { try { const receiptResult = await receiptsManager.getReceiptByTxHash(txHash) if (receiptResult === null) { console.log('Receipt not found - transaction may be pending or not exist') return null } const [receipt] = receiptResult return receipt } catch (error) { console.error('Error retrieving receipt:', error.message) return null } } ``` #### Working with Receipt Types ```typescript showLineNumbers filename="type-safety.ts" function getTransactionStatus(receipt) { if (!receipt) return 'Unknown' if ('status' in receipt) { return receipt.status === 1 ? 'Success' : 'Failed' } else if ('stateRoot' in receipt) { const emptyRoot = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' const actualRoot = '0x' + Buffer.from(receipt.stateRoot).toString('hex') return actualRoot === emptyRoot ? 'Likely Failed' : 'Likely Success' } return 'Unknown Format' } ``` ### Related Resources * [JSON-RPC Support](../api/json-rpc) * [VM & Submodules](../api/vm-and-submodules) * [Transaction Pool](./txpool) * [Solidity Events](https://docs.soliditylang.org/en/latest/contracts.html#events) ## Transaction Pool The TxPool manages pending transactions before block inclusion. Tevm's facade is backed by `@evmts/zevm/txpool` and adds Tevm-specific fee handling for modern fee-market transactions. ### Quick Start ```ts import { createTevmNode, PREFUNDED_ACCOUNTS } from 'tevm' import { createAddress } from 'tevm/address' import { createImpersonatedTx } from 'tevm/tx' const node = createTevmNode() const txPool = await node.getTxPool() const tx = createImpersonatedTx({ impersonatedAddress: createAddress(PREFUNDED_ACCOUNTS[0].address), to: createAddress('0x2345678901234567890123456789012345678901'), value: 1000000000000000000n, // 1 ETH gasLimit: 21000n, maxFeePerGas: 20000000000n, maxPriorityFeePerGas: 20000000000n, nonce: 0n, }) await txPool.addUnverified(tx) ``` ### Key Features * **Transaction Validation** — nonce, balance, gas checks * **Transaction Replacement** — replace pending tx with higher gas price * **Nonce Ordering** — correct sequence per account * **Automatic Pruning** — removes stale txs * **Performance Optimized** — handles large volumes * **Modern Transaction Support** — legacy, EIP-2930, EIP-1559, EIP-4844, EIP-7702, and Tevm impersonated transactions * **JSON-RPC Access** — `txpool_content`, `txpool_contentFrom`, `txpool_inspect`, `txpool_status` ### Core Concepts #### Pool Limits ```ts const LIMITS = { MAX_POOL_SIZE: 5000, // Maximum total transactions MAX_TXS_PER_ACCOUNT: 100, // Maximum per account MIN_GAS_PRICE: 100000000n, // 0.1 GWei minimum TX_MAX_DATA_SIZE: 128 * 1024, // 128KB max transaction size } ``` #### Transaction Lifecycle 1. **Addition** — via `add()` or `addUnverified()` 2. **Validation** — optional nonce, balance, gas checks 3. **Storage** — stored and ordered by nonce 4. **Pruning** — removed after `POOLED_STORAGE_TIME_LIMIT` (20 minutes) ### Detailed Usage #### Adding Transactions ```ts // Full validation try { await txPool.add(tx) } catch (error) { if (error.message.includes('insufficient balance')) { console.error('Account has insufficient funds') } } // No validation (faster) await txPool.addUnverified(tx) ``` #### Transaction Replacement Submit a new tx with the same nonce and higher gas price (>= 10% bump): ```ts const originalTx = createImpersonatedTx({ // ... base transaction params ... maxFeePerGas: 20000000000n, nonce: 0n, }) const replacementTx = createImpersonatedTx({ // ... same params as original ... maxFeePerGas: 30000000000n, // At least 10% higher nonce: 0n, }) await txPool.addUnverified(originalTx) await txPool.addUnverified(replacementTx) // Replaces originalTx ``` :::note Replacements must bump gas price by at least `MIN_GAS_PRICE_BUMP_PERCENT` (10%). ::: #### Querying Transactions ```ts const senderTxs = await txPool.getBySenderAddress(senderAddress) const txHashes = [hash1, hash2] const specificTxs = txPool.getByHash(txHashes) const orderedTxs = await txPool.txsByPriceAndNonce({ baseFee: currentBaseFee, allowedBlobs: 6, // For EIP-4844 }) ``` #### Block Processing ```ts import { mineHandler } from 'tevm/actions' await mineHandler(node)() txPool.removeNewBlockTxs(newBlocks) ``` ### Advanced Features #### Pool Management ```ts txPool.start() // Start processing txPool.stop() // Stop (keep transactions) txPool.close() // Clear all txPool.cleanup() // Manual cleanup of old txs ``` #### Transaction Types Supports: Legacy, EIP-2930 (Access Lists), EIP-1559 (Fee Market), EIP-4844 (Blob), EIP-7702 (EOA Code), and Tevm Impersonated Transactions. ### Best Practices #### 1. Transaction Creation ```ts import { PREFUNDED_ACCOUNTS, parseEther, parseGwei } from 'tevm' import { createAddress } from 'tevm/address' import { createImpersonatedTx } from 'tevm/tx' const tx = createImpersonatedTx({ impersonatedAddress: createAddress(PREFUNDED_ACCOUNTS[0].address), to: createAddress('0x2345678901234567890123456789012345678901'), value: parseEther('1'), gasLimit: 21000n, maxFeePerGas: parseGwei('20'), maxPriorityFeePerGas: parseGwei('2'), nonce: 0n, }) ``` #### 2. Error Handling ```ts try { await txPool.add(tx) } catch (error) { switch (true) { case error.message.includes('insufficient balance'): break case error.message.includes('nonce too low'): break case error.message.includes('gas price too low'): break default: // unknown error } } ``` #### 3. Performance Optimization * Use `addUnverified` for bulk operations * Run cleanup cycles * Monitor pool size and tx age #### 4. Memory Management ```ts setInterval(() => { txPool.cleanup() }, 5 * 60 * 1000) // Every 5 minutes const poolSize = txPool.txsInPool if (poolSize > MAX_POOL_SIZE * 0.8) { console.warn('Pool approaching capacity') } ``` ### API Reference ```ts class TxPool { constructor(options: { vm: Vm }) async add(tx: Transaction): Promise async addUnverified(tx: Transaction): Promise async getBySenderAddress(address: Address): Promise getByHash(hashes: Uint8Array[]): Transaction[] removeByHash(hash: string): void removeNewBlockTxs(blocks: Block[]): void start(): boolean stop(): boolean cleanup(): void close(): void } ``` ### Related Topics * [JSON-RPC Support](../api/json-rpc) * [VM & Submodules](../api/vm-and-submodules) * [Receipts & Logs](./receipts-and-logs)