An Ethereum Virtual Machine (EVM) implementation in TypeScript using the Effect library, with a focus on debuggability.
This implementation is designed for building developer tooling that requires deep introspection into EVM execution — such as debuggers, tracers, profilers, and testing frameworks. The Effect-TS foundation provides structured concurrency, typed errors, and built-in tracing capabilities that make it straightforward to observe and analyze every step of execution.
Note: This project is not yet published to npm. The API is still in flux and will likely go through significant changes before the first stable release.
This implementation is extensively tested against the official Ethereum Execution Specs state tests and blockchain tests:
➜ evm-effect git:(main) ✗ bun test packages/evm/test/fixtures.test.ts
...
91392 pass
0 fail
2851964 expect() calls
Ran 91392 tests across 1 file. [11202.85s]All released Ethereum forks are supported:
| Fork | Status |
|---|---|
| Frontier | ✅ |
| Homestead | ✅ |
| Tangerine Whistle | ✅ |
| Spurious Dragon | ✅ |
| Byzantium | ✅ |
| Constantinople | ✅ |
| Petersburg | ✅ |
| Istanbul | ✅ |
| Muir Glacier | ✅ |
| Berlin | ✅ |
| London | ✅ |
| Paris (The Merge) | ✅ |
| Shanghai | ✅ |
| Cancun | ✅ |
| Prague | ✅ |
| Osaka (Unreleased) | 🚧 In Progress |
This monorepo contains the following packages:
| Package | Description |
|---|---|
@evm-effect/evm |
Core EVM implementation with interpreter, block/transaction processing, state management, precompiles, and EIP-3155 tracing |
@evm-effect/ethereum-types |
Core Ethereum types (Address, Bytes, U256, etc.) |
@evm-effect/rlp |
RLP encoding and decoding |
@evm-effect/crypto |
Cryptographic primitives (keccak256, sha256, transaction signing) |
@evm-effect/solc |
Solidity compiler wrapper with typed schemas |
@evm-effect/shared |
Shared utilities (HashMap, HashSet) |
@evm-effect/examples |
Usage examples (contract deployment, transactions) |
import { Console, Effect } from "effect";
import { getRandomPrivateKey } from "@evm-effect/crypto/getRandomPrivateKey";
import { getAddressFromPrivateKey, signTransaction } from "@evm-effect/crypto/transactions";
import { Address, Bytes, Bytes32, U64, U256, Uint } from "@evm-effect/ethereum-types";
import {
Account, applyBody, BlockChain, BlockEnvironment,
Fork, LegacyTransaction, State
} from "@evm-effect/evm";
const program = Effect.gen(function* () {
// Create empty blockchain
const chainId = U64.constant(1n);
const blockchain = BlockChain.empty(chainId);
// Generate accounts
const alicePrivateKey = getRandomPrivateKey();
const alice = getAddressFromPrivateKey(alicePrivateKey);
const bob = new Address("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
// Fund Alice with 10 ETH
yield* State.setAccount(blockchain.state, alice, Account.make({
nonce: Uint.constant(0n),
balance: U256.constant(10n * 10n ** 18n),
code: Bytes.empty(),
}));
// Create transaction: Alice sends 1 ETH to Bob
const unsignedTx = LegacyTransaction.make({
nonce: U256.constant(0n),
gasPrice: Uint.constant(10n * 10n ** 9n), // 10 gwei
gas: Uint.constant(21_000n),
to: bob,
value: U256.constant(1n * 10n ** 18n), // 1 ETH
data: Bytes.empty(),
});
// Sign transaction
const signature = signTransaction({ transaction: unsignedTx, privateKey: alicePrivateKey });
const signedTx: LegacyTransaction = { ...unsignedTx, ...signature };
// Create block environment
const blockEnv = BlockEnvironment.make({
chainId,
state: blockchain.state,
blockGasLimit: Uint.constant(30_000_000n),
blockHashes: [],
coinbase: new Address("0xcccccccccccccccccccccccccccccccccccccccc"),
number: Uint.constant(1n),
baseFeePerGas: Uint.constant(10n * 10n ** 9n),
time: U256.constant(BigInt(Math.floor(Date.now() / 1000))),
prevRandao: Bytes32.zero(),
difficulty: Uint.constant(0n),
excessBlobGas: U64.constant(0n),
parentBeaconBlockRoot: Bytes32.zero(),
});
// Execute block with transaction
const blockOutput = yield* applyBody(blockEnv, [signedTx], [], []);
yield* Console.log(`Gas used: ${blockOutput.blockGasUsed.value}`);
yield* Console.log(`Bob balance: ${State.getAccount(blockchain.state, bob).balance.value / 10n ** 18n} ETH`);
});
// Run with London fork
Effect.runPromise(program.pipe(Effect.provide(Fork.london())));
// Output:
// Gas used: 21000
// Bob balance: 1 ETHThis project is not yet published to npm. The API is still being refined and will likely undergo changes.
To use from source:
git clone https://github.com/your-username/evm-effect.git
cd evm-effect
bun install
bun run buildMIT