@ethereumjs/evm v10
| TypeScript implementation of the Ethereum EVM. |
|---|
Runnable examples live in examples/ (including precompiles/ and opcodes/ subfolders).
- All hardforks up to Osaka (Amsterdam in development)
- Tree-shakeable API
- Controlled dependency set (7 external +
@Noblecrypto) - Flexible EIP on/off engine
- Custom opcodes and precompiles
- EIP-7708 Transfer logs and EIP-8037 state gas (Amsterdam, experimental)
- EIP-8024 stack opcodes and EIP-7843
SLOTNUM(Amsterdam, experimental) - Built-in profiler
- User-friendly colored debugging
- 422KB bundle size (110KB gzipped)
- WASM-free default + Fully browser ready
Table of Contents
- Installation
- Getting Started
- Contract Calls (
runCall) - Amsterdam (experimental)
- EIP Activation
- Customizing the EVM
- Observability
- Precompiles
- Browser
- API
- Architecture
- Supported Hardforks
- Supported EIPs
- Development
- EthereumJS
- License
Installation
To obtain the latest version, simply require the project using npm:
npm install @ethereumjs/evm
Note: Starting with the Dencun hardfork, the EIP-4844 point-evaluation precompile (0x0a) requires a separate KZG library install — see KZG Setup.
Getting Started
Use @ethereumjs/evm when you need bytecode or message execution without full transaction/block processing. For signed transactions, receipts, and block assembly, use @ethereumjs/vm.
| API | Use when |
|---|---|
runCode() |
Executing raw bytecode (tests, sandboxes, utilities) |
runCall() |
Full message path: checkpoints, value transfer, nonce updates |
createEVM() returns a standalone instance with a default SimpleStateManager and mock blockchain:
// ./examples/runBytecode.ts
import { createEVM } from '@ethereumjs/evm'
import { hexToBytes } from '@ethereumjs/util'
const main = async () => {
const evm = await createEVM()
const res = await evm.runCode({ code: hexToBytes('0x6001') }) // PUSH1 01 -- simple bytecode to push 1 onto the stack
console.log(res.executionGasUsed) // 3n
}
void main()
Pin a hardfork via Common when you need rules beyond the default (prague):
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createEVM } from '@ethereumjs/evm'
const evm = await createEVM({
common: new Common({ chain: Mainnet, hardfork: Hardfork.Prague }),
})
Contract Calls (runCall)
runCall() runs the full message path. Put contract code on the state manager, then call the address:
// ./examples/runCallWithState.ts
import { createEVM } from '@ethereumjs/evm'
import { Account, bytesToHex, createAddressFromString, hexToBytes } from '@ethereumjs/util'
const main = async () => {
const evm = await createEVM()
const caller = createAddressFromString('0x00000000000000000000000000000000000000ee')
const contract = createAddressFromString('0x00000000000000000000000000000000000000c0')
// PUSH1 03 PUSH1 05 ADD — store 8 at memory[0], RETURN 32 bytes
const code = hexToBytes('0x600360050160005260206000F3')
await evm.stateManager.putCode(contract, code)
await evm.stateManager.putAccount(caller, new Account(0n, 1_000_000_000_000n))
const result = await evm.runCall({
caller,
to: contract,
gasLimit: 100_000n,
})
console.log(`Return value: ${bytesToHex(result.execResult.returnValue)}`)
console.log(`Gas used: ${result.execResult.executionGasUsed}`)
}
void main()
State and blockchain. Pass a @ethereumjs/statemanager instance for persistent accounts and storage; an optional @ethereumjs/blockchain provides BLOCKHASH access. See withBlockchain.ts for wiring with MerkleStateManager. WASM crypto backends can replace the default JavaScript implementations — see @ethereumjs/common for customCrypto setup.
For async event listeners on runCall, see Events.
Amsterdam (experimental)
Amsterdam is the current development hardfork. Behaviour is unstable — expect further 10.1.x releases as the spec evolves. Release spec tracking and the full EIP bundle live in the canonical Amsterdam overview in @ethereumjs/vm.
Activation: new Common({ chain: Mainnet, hardfork: Hardfork.Amsterdam }).
| EIP | EVM-layer summary | Detail |
|---|---|---|
| 8024 | DUPN, SWAPN, EXCHANGE stack opcodes |
below |
| 7843 | SLOTNUM opcode + slotNumber header field |
below |
| 7954 | Raised max contract / initcode size | below |
| 7708 | Synthetic Transfer logs on value-bearing calls |
below |
| 8037 / 8038 | Two-dimensional state gas reservoir | below |
| 7928 | Block Level Access Lists | @ethereumjs/vm — accumulates on evm.blockLevelAccessList |
| 8246 | SELFDESTRUCT no longer burns ETH | Journal / SELFDESTRUCT behaviour |
| 8282 | Builder execution requests | @ethereumjs/vm EIP-8282 |
Tx-level Amsterdam rules (intrinsic gas, calldata floor, access-list pricing): @ethereumjs/tx Amsterdam Validation.
EIP-8024 stack opcodes (Amsterdam)
EIP-8024 adds three backward-compatible stack manipulation opcodes, each with a single-byte immediate operand:
| Opcode | Byte | Effect |
|---|---|---|
DUPN |
0xe6 |
Duplicate the stack item at depth n (immediate encodes n) |
SWAPN |
0xe7 |
Swap the top item with the item at depth n |
EXCHANGE |
0xe8 |
Exchange items at depths x and y (pair immediate) |
The opcodes are active on Hardfork.Amsterdam and validated at decode time (invalid immediates trap). Gas costs: dupnGas, swapnGas, exchangeGas (default 3 each). They are supported in legacy bytecode and in EOF containers.
Runnable walkthrough: examples/opcodes/eip8024StackOpcodes.ts. See also CLZ (EIP-7939) (Osaka) and SLOTNUM (EIP-7843).
EIP-7843 SLOTNUM opcode (Amsterdam)
EIP-7843 adds SLOTNUM (0x4b), which pushes the executing block's consensus slotNumber onto the stack. Pass a block header that sets the field. A stand-alone runCode() without a block uses a mock header with slotNumber: 0n:
// ./examples/opcodes/eip7843SlotnumOpcode.ts
import { createBlock } from '@ethereumjs/block'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createEVM } from '@ethereumjs/evm'
const SLOTNUM = 0x4b
const STOP = 0x00
const main = async () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Amsterdam })
const evm = await createEVM({ common })
const slotNumber = 42n
const block = createBlock(
{ header: { slotNumber, gasLimit: 30_000_000n } },
{ common, skipConsensusFormatValidation: true },
)
const res = await evm.runCode({
code: Uint8Array.from([SLOTNUM, STOP]),
block,
gasLimit: 100_000n,
})
const [top] = res.runState!.stack.peek(1)
console.log(`SLOTNUM read consensus slot ${top} (header.slotNumber=${slotNumber})`)
console.log(`Gas used: ${res.executionGasUsed}`)
}
void main()
Header construction: @ethereumjs/block slot number.
EIP-7954 contract and initcode size limits (Amsterdam)
EIP-7954 raises the EVM size limits when active on Hardfork.Amsterdam:
| Parameter | Pre-7954 | Post-7954 |
|---|---|---|
maxCodeSize |
24 KiB (24576) | 64 KiB (65536) |
maxInitCodeSize |
48 KiB (49152) | 128 KiB (131072) |
These are Common parameters — after createEVM() merges paramsEVM, read them with evm.common.param('maxCodeSize') and evm.common.param('maxInitCodeSize'). No separate API beyond selecting the Amsterdam hardfork. @ethereumjs/tx rejects initcode above maxInitCodeSize at construction time; the EVM enforces the same limit on CREATE / CREATE2 and caps deployed runtime code at maxCodeSize.
Runnable walkthrough: examples/eip7954MaxCodeSize.ts — compares Prague vs Amsterdam limits and deploys a 24577-byte contract (one byte above the legacy cap). Prague fails with code size to deposit exceeds maximum code size; Amsterdam succeeds (large deploys need generous gasLimit under EIP-8037 state-gas). For the transaction path use @ethereumjs/vm runTx().
EIP-7708 ETH transfer logs (Amsterdam)
EIP-7708 emits a system-address Transfer(address,address,uint256) log on value-bearing CALL/CREATE. runCall() returns it on execResult.logs even when the recipient has no code. Use decodeEIP7708TransferLog() to pick Transfer logs out of mixed opcode logs.
Burn logs are created in @ethereumjs/vm runTx() finalization, not by a stand-alone runCall(). For receipts see the runTxTransferLogs example.
// ./examples/eip7708TransferLog.ts
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createEVM, decodeEIP7708TransferLog } from '@ethereumjs/evm'
import { bytesToHex, createAccount, createAddressFromString } from '@ethereumjs/util'
import type { Log } from '@ethereumjs/evm'
/** Pretty-print a Log tuple for console output (not an RPC formatter). */
function formatLog(log: Log) {
const [address, topics, data] = log
return {
address: bytesToHex(address),
topics: topics.map((topic) => bytesToHex(topic)),
data: bytesToHex(data),
}
}
const main = async () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Amsterdam })
const evm = await createEVM({ common })
const caller = createAddressFromString('0x00000000000000000000000000000000000000ee')
const recipient = createAddressFromString('0x00000000000000000000000000000000000000aa')
await evm.stateManager.putAccount(caller, createAccount({ nonce: 0n, balance: BigInt(1e18) }))
// Value-bearing CALL with no bytecode still emits a system-address Transfer log.
const result = await evm.runCall({
caller,
to: recipient,
value: 1n,
gasLimit: 300_000n,
})
const logs = result.execResult.logs ?? []
console.log(`runCall emitted ${logs.length} log(s)`)
for (const [index, log] of logs.entries()) {
const formatted = formatLog(log)
console.log(` log[${index}] emitter=${formatted.address}`)
console.log(` topics=${formatted.topics.join(', ')}`)
console.log(` data=${formatted.data}`)
const transfer = decodeEIP7708TransferLog(log)
if (transfer !== undefined) {
console.log(
` → EIP-7708 Transfer from ${transfer.from} to ${transfer.to} value=${transfer.value} wei`,
)
}
}
}
void main()
EIP-8037 state-creation gas (Amsterdam)
EIP-8037 splits gas into regular and state dimensions. State-touching opcodes draw from evm.stateGasReservoir first; overflow spills into gas_left. runTx() sizes the reservoir from the tx budget. A stand-alone runCall() starts at 0 unless you set it.
Inner CREATE / CREATE2 charges new-account state gas unless the target already has nonce or code. A create collision burns the 63/64 stipend without a child frame.
Helpers (used by @ethereumjs/vm, available for custom runners):
| Helper | Role |
|---|---|
computeIntrinsicGasDimensions8037() |
Intrinsic gas is regular-only under current v7 rules (intrinsicState is 0) |
txExceedsAvailableBlockGas8037() |
Per-tx block inclusion: regular bound is min(TX_MAX, tx.gas), state bound is full tx.gas |
activeCostPerStateByte() |
costPerStateByte from Common |
EIP-8038 (same fork) raises state-access costs and charges SSTORE access before the implicit storage read (max(access cost, stipend + 1)).
Transaction results, header gasUsed = max(regular, state), and wallet gasLimit guidance: @ethereumjs/vm EIP-8037.
EIP Activation
Activate individual EIPs on top of a hardfork via Common eips:
// ./examples/activateEIP7702.ts
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createEVM } from '@ethereumjs/evm'
const main = async () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Cancun, eips: [7702] })
const evm = await createEVM({ common })
console.log(
`EIP 7702 is active in isolation on top of the Cancun HF - ${evm.common.isActivatedEIP(7702)}`,
)
}
void main()
See Supported EIPs for the full index. Opcode-focused samples: examples/opcodes/ (EIP-8024, EIP-7843 SLOTNUM, EIP-7939 CLZ).
Customizing the EVM
The EVM is customized through createEVM / EVMOpts:
- Custom opcodes —
customOpcodes: add, override, or remove opcodes by number (handler + gas function). - Custom precompiles —
customPrecompiles: add or override a precompile at an address. - Custom state manager —
stateManager: anyStateManagerInterface. Default isSimpleStateManager. - Custom
Common— hardfork / EIP gating and parameter resolution. - Custom parameters —
params: overrideparamsEVMvalues (for example a gas cost) without forking the package. - Custom crypto backends —
bls/bn254: native BLS12-381 / BN254 for the relevant precompiles.
Custom Opcodes
Add, override, or remove opcodes via customOpcodes on createEVM():
// ./examples/customOpcode.ts
import { createEVM } from '@ethereumjs/evm'
import { hexToBytes } from '@ethereumjs/util'
const main = async () => {
const evm = await createEVM({
customOpcodes: [
{
opcode: 0x21,
opcodeName: 'PUSH_ONE',
baseFee: 3,
gasFunction(_runState, gas) {
return gas
},
logicFunction(runState) {
runState.stack.push(1n)
},
},
],
})
const res = await evm.runCode({
code: hexToBytes('0x21'),
gasLimit: 100_000n,
})
const [top] = res.runState!.stack.peek(1)
console.log(`Stack top after custom opcode: ${top}`)
console.log(`Gas used: ${res.executionGasUsed}`)
}
void main()
Pass { opcode: 0x01 } (no handler) to delete a built-in opcode for that EVM instance.
Custom Precompiles
Register custom precompiles at arbitrary addresses — add new ones, override built-ins, or delete by address only:
// ./examples/precompiles/customPrecompile.ts
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createEVM } from '@ethereumjs/evm'
import {
bigIntToBytes,
bytesToBigInt,
bytesToHex,
createAddressFromString,
setLengthLeft,
} from '@ethereumjs/util'
import type { ExecResult, PrecompileInput } from '@ethereumjs/evm'
// Custom precompile that adds two 32-byte big-endian unsigned integers (mod 2^256).
const ADDITION_GAS = 15n
function additionPrecompile(input: PrecompileInput): ExecResult {
const a = bytesToBigInt(input.data.subarray(0, 32))
const b = bytesToBigInt(input.data.subarray(32, 64))
const sum = (a + b) % 2n ** 256n
return {
executionGasUsed: ADDITION_GAS,
returnValue: setLengthLeft(bigIntToBytes(sum), 32),
}
}
const main = async () => {
const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague })
const ADDRESS = '0x000000000000000000000000000000000000ff01'
// Register the custom precompile with a hex string address
const evm = await createEVM({
common,
customPrecompiles: [{ address: ADDRESS, function: additionPrecompile }],
})
// Verify it is registered
const fn = evm.getPrecompile(ADDRESS)
console.log(`Precompile registered at ${ADDRESS}: ${fn !== undefined}`)
// Build call data: two 32-byte values (7 + 35)
const a = setLengthLeft(bigIntToBytes(7n), 32)
const b = setLengthLeft(bigIntToBytes(35n), 32)
const callData = new Uint8Array(64)
callData.set(a, 0)
callData.set(b, 32)
// Execute via runCall
const result = await evm.runCall({
to: createAddressFromString(ADDRESS),
gasLimit: BigInt(30000),
data: callData,
})
console.log('--------------------------------')
console.log('Custom Addition Precompile')
console.log(`Input : 7 + 35`)
console.log(
`Result : ${bytesToBigInt(result.execResult.returnValue)} (${bytesToHex(result.execResult.returnValue)})`,
)
console.log(`Gas used : ${result.execResult.executionGasUsed}`)
console.log('--------------------------------')
}
void main()
Use evm.getPrecompile(address) to retrieve built-in or custom precompiles. Override by registering at the same address; delete with { address: '0x...' } only.
More precompile demos: examples/precompiles/ (MODEXP, BLS12-381, P256 verify, …).
Opcode table
Inspect the opcode table for a hardfork with getOpcodesForHF() — see examples/decodeOpcodes.ts for a minimal disassembly walkthrough.
Observability
Events
The EVM emits events via EventEmitter3. Subscribe to beforeMessage, afterMessage, step, and newContract. Async listeners receive a resolve callback that must be called when finished:
// ./examples/eventListener.ts
import { createEVM } from '@ethereumjs/evm'
import { createAddressFromString, hexToBytes } from '@ethereumjs/util'
const main = async () => {
const evm = await createEVM()
evm.events.on('beforeMessage', (event) => {
console.log('synchronous listener to beforeMessage', event)
})
evm.events.on('afterMessage', (event, resolve) => {
console.log('asynchronous listener to afterMessage', event)
// we need to call resolve() to avoid the event listener hanging
resolve?.()
})
const res = await evm.runCall({
to: createAddressFromString('0x0000000000000000000000000000000000000000'),
value: 0n,
data: hexToBytes('0x6001'), // PUSH1 01 -- simple bytecode to push 1 onto the stack
})
console.log(res.execResult.executionGasUsed) // 0n
}
void main()
If an exception is thrown from an async handler, it bubbles into the EVM and may corrupt state — avoid that in production tracing.
For opcode-level step tracing with state and blockchain wired in, see withBlockchain.ts.
Event Logs
The EVM records contract events as logs — a compact tuple reused across @ethereumjs/evm, @ethereumjs/vm, and (with field renaming) JSON-RPC:
type Log = [address: Uint8Array, topics: Uint8Array[], data: Uint8Array]
// emitter indexed fields unindexed payload
Both runCode() and runCall() return an ExecResult with an optional logs array. Nested calls append logs in execution order; a reverted top-level execution clears them. For transaction receipts and block blooms, use @ethereumjs/vm.
Bytecode LOG* emission: examples/emitLogs.ts. Native ETH Transfer logs on Amsterdam: EIP-7708.
Debug logging
Hierarchically structured debug loggers use the debug library. Activate on the CLI with DEBUG=ethjs,[Logger Selection]:

| Logger | Description |
|---|---|
evm:evm |
EVM control flow, CALL or CREATE message execution |
evm:gas |
EVM gas logger |
evm:precompiles |
EVM precompiles logger |
evm:journal |
EVM journal logger |
evm:ops |
Opcode traces |
evm:ops:[Lower-case opcode name] |
Traces on a specific opcode |
Examples:
DEBUG=ethjs,evm tsx test.ts
DEBUG=ethjs,evm:*,evm:*:* tsx test.ts
DEBUG=ethjs,evm,evm:ops:sstore,evm:*:gas tsx test.ts
ethjs must be included in the DEBUG environment variables to enable any logs.
Profiling the EVM
Built-in profiling detects performance bottlenecks. The profiler is most useful when run through the EthereumJS client for realistic tx and state sizes.
Sync the client on mainnet or a larger testnet to the desired block, then run without sync using --executeBlocks and --vmProfileBlocks (or --vmProfileTxs):
npm run client:start -- --sync=none --vmProfileBlocks --executeBlocks=962720

The total (ms) column shows where time is spent relative to call count. Optimize for Mgas/s (gas processed per second). With a 30 Mio gas limit and 12 sec slot time, a rough minimum is 30M / 12 sec ≈ 2.5 Mgas/s.
Note: profiler results for some opcodes (notably SSTORE) are distorted because checkpoint commit cost is not attributed to the opcode.
Precompiles
This library supports all EVM precompiles up to the Osaka hardfork. The examples/precompiles/ folder provides a runPrecompile() helper for direct runs.
BLS12_G1ADD example:
// ./examples/precompiles/bls12G1AddPrecompile.ts
import { runPrecompile } from './util.ts'
const main = async () => {
// BLS12_G1ADD precompile (address 0xb)
// Data taken from test/eips/precompiles/bls/add_G1_bls.json
// Input: G1 and G2 points (each 128 bytes = 256 hex characters)
const g1Point =
'0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1'
const g2Point =
'00000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21'
const data = `0x${g1Point}${g2Point}`
await runPrecompile('BLS12_G1ADD', '0xb', data)
}
void main()
EIP-2537 BLS Precompiles (Prague)
Starting with v10 the EVM supports the BLS precompiles introduced with EIP-2537. These run natively using @noble/curves.
An alternative WASM implementation (using bls-wasm) can be optionally used for performance:
import { EVM, MCLBLS } from '@ethereumjs/evm'
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Prague })
await mcl.init(mcl.BLS12_381)
const mclbls = new MCLBLS(mcl)
const evm = await createEVM({ common, bls })
EIP-7823/EIP-7883 MODEXP Precompile (Osaka)
The Osaka hardfork introduces behavioral changes with EIP-7823 and a gas cost increase for MODEXP with EIP-7883:
// ./examples/precompiles/modexpPrecompile.ts
import { Hardfork } from '@ethereumjs/common'
import { runPrecompile } from './util.ts'
const main = async () => {
// MODEXP precompile (address 0x05)
// Calculate: 2^3 mod 5 = 8 mod 5 = 3
//
// Input format:
// - First 32 bytes: base length (0x01 = 1 byte)
// - Next 32 bytes: exponent length (0x01 = 1 byte)
// - Next 32 bytes: modulus length (0x01 = 1 byte)
// - Next 1 byte: base value (0x02 = 2)
// - Next 1 byte: exponent value (0x03 = 3)
// - Next 1 byte: modulus value (0x05 = 5)
const baseLen = '0000000000000000000000000000000000000000000000000000000000000001' // 1 byte
const expLen = '0000000000000000000000000000000000000000000000000000000000000001' // 1 byte
const modLen = '0000000000000000000000000000000000000000000000000000000000000001' // 1 byte
const base = '02' // 2
const exponent = '03' // 3
const modulus = '05' // 5
const data = `0x${baseLen}${expLen}${modLen}${base}${exponent}${modulus}`
await runPrecompile('MODEXP', '0x05', data)
await runPrecompile('MODEXP', '0x05', data, Hardfork.Cancun)
}
void main()
EIP-7951 Precompile for secp256r1 Curve Support (Osaka)
The Osaka hardfork introduces secp256r1 curve support with EIP-7951. See p256VerifyPrecompile.ts. Input values can be generated with Noble Curves v2.0.0 or later.
For custom precompile registration, see Custom Precompiles.
Browser
We provide hybrid ESM/CJS builds for all our libraries. With the v10 breaking release round from Spring 2025, all libraries are "pure-JS" by default and we have eliminated all hard-wired WASM code. Additionally we have substantially lowered the bundle sizes, reduced the number of dependencies, and cut out all usages of Node.js-specific primitives (like the Node.js event emitter).
It is easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see ./examples/browser.html.
API
Docs
For documentation on EVM instantiation, exposed API and emitted events see generated API docs.
Hybrid CJS/ESM Builds
With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (cjs folder) and ESM builds (esm folder), see package.json for the detailed setup.
If you use an ES6-style import in your code files, the ESM build will be used:
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]'
If you use Node.js specific require, the CJS build will be used:
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]')
Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide.
Architecture
VM/EVM Relation
This package contains the inner Ethereum Virtual Machine core functionality which was included in the @ethereumjs/vm package up to v5 and has been extracted along the v6 release. A customized EVM can be passed as an optional argument to the outer VM instance.
Monorepo context: ARCHITECTURE.md.
Layout
createEVM() / runCall() / runCode() sit on the EVM class. Bytecode runs in the interpreter; opcodes and precompiles are hardfork-gated tables.
| Area | Role |
|---|---|
evm.ts |
Message dispatch (runCall, runCode), checkpoints, precompile dispatch |
interpreter.ts |
Fetch-decode-execute loop, gas, step events |
opcodes/, precompiles/ |
Opcode and precompile handlers |
journal.ts |
Checkpoint / commit / revert onto StateManagerInterface |
eof/ |
EOF container parse and verify |
params.ts |
paramsEVM, merged into Common |
Per-frame stack/memory, the Message object, and EIP-7864 witnesses live next to those files. createEVM and public option types are in constructors.ts / types.ts.
Internal Structure
runCall — message execution (calls and creates): checkpoint state, set execution environment, handle value transfer and nonce updates, delegate to _executeCall / _executeCreate (both call runInterpreter), commit or revert, emit beforeMessage / afterMessage.
runCode — direct bytecode helper: minimal message context, calls runInterpreter without full message handling.
Interpreter — fetch-decode-execute loop: jump analysis, gas (static + dynamic), opcode handlers, step events. CALL / CREATE / DELEGATECALL re-enter runCall.
Journal — checkpointing and reversion; transient storage (EIP-1153) has its own checkpoint mechanism.
Supported Hardforks
The EthereumJS EVM implements all hardforks from Frontier (chainstart) through Osaka, plus Amsterdam (experimental, in development).
| Range | Hardforks |
|---|---|
| Early | chainstart through istanbul, muirGlacier (mainnet only) |
| Berlin → Merge | berlin, london, arrowGlacier (mainnet only), merge |
| Recent | shanghai, cancun, prague (default), osaka, amsterdam (experimental) |
Default: prague (Common.DEFAULT_HARDFORK). Activate a ruleset by passing hardfork on the Common instance to createEVM() or the outer @ethereumjs/vm.
Supported EIPs
Individual EIP activation is shown in EIP Activation. Amsterdam how-tos: Amsterdam (experimental). Release spec tracking: @ethereumjs/vm Amsterdam overview.
Hardfork labels indicate default activation on that fork. (Amsterdam, experimental) and (experimental) mark unstable specs.
Shanghai / Merge
- EIP-3651 - Warm COINBASE (Shanghai)
- EIP-3675 - Upgrade consensus to Proof-of-Stake
- EIP-3855 - PUSH0 opcode (Shanghai)
- EIP-3860 - Limit and meter initcode (Shanghai)
- EIP-4399 - Supplant DIFFICULTY opcode with PREVRANDAO (Merge)
- EIP-4895 - Beacon chain push withdrawals as operations (Shanghai)
Cancun
- EIP-1153 - Transient storage opcodes
- EIP-4788 - Beacon block root in the EVM
- EIP-4844 - Shard Blob Transactions (
BLOBHASH, point eval precompile0x0a) - EIP-5656 - MCOPY
- EIP-6780 - SELFDESTRUCT only in same transaction
- EIP-7516 - BLOBBASEFEE opcode
Prague
- EIP-2537 - BLS12-381 precompiles
- EIP-2935 - Serve historical block hashes in state
- EIP-6110 - Supply validator deposits on chain
- EIP-7002 - Execution layer triggerable exits
- EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
- EIP-7623 - Increase calldata cost
- EIP-7685 - General purpose execution layer requests
- EIP-7691 - Blob throughput increase
- EIP-7702 - Set EOA account code
Osaka
- EIP-7594 - PeerDAS blob transactions
- EIP-7823 - Set upper bounds for MODEXP
- EIP-7825 - Transaction gas limit cap
- EIP-7883 - ModExp gas cost increase
- EIP-7918 - Blob base fee bounded by execution cost
- EIP-7934 - RLP Execution Block Size Limit
- EIP-7939 - Count leading zeros (CLZ) opcode
- EIP-7951 - Precompile for secp256r1 curve support
Amsterdam (experimental)
- EIP-2780 - Reduce intrinsic transaction gas — @ethereumjs/tx
- EIP-7708 - ETH transfers emit a log — notes
- EIP-7778 - Block-level gas accounting without refunds — @ethereumjs/vm
- EIP-7843 - SLOTNUM opcode — notes
- EIP-7928 - Block Level Access Lists — @ethereumjs/vm
- EIP-7954 - Increase max contract and initcode size — notes
- EIP-7976 - Increase calldata floor cost — @ethereumjs/tx
- EIP-7981 - Access list data pricing — @ethereumjs/tx
- EIP-8024 - DUPN, SWAPN and EXCHANGE instructions — notes
- EIP-8037 - State creation gas cost increase — notes
- EIP-8038 - State access gas cost increase — notes
- EIP-8246 - SELFDESTRUCT no burn
- EIP-8282 - Builder execution requests — @ethereumjs/vm
Experimental / cross-layer
- EIP-7692 - EVM Object Format (EOF) v1
- EIP-7709 - Read BLOCKHASH from storage (Verkle)
- EIP-7864 - Ethereum state using a unified binary tree
Legacy / primarily tx or chain layer
- EIP-1559, EIP-2565, EIP-2718, EIP-2929, EIP-2930, EIP-3198, EIP-3529, EIP-3541, EIP-3554, EIP-3607, EIP-4345, EIP-5133
Development
See @ethereumjs/vm README.
EthereumJS
The EthereumJS GitHub organization and its repositories are managed by members of the former Ethereum Foundation JavaScript team and the broader Ethereum community. If you want to join for work or carry out improvements on the libraries see the developer docs for an overview of current standards and tools and review our code of conduct.