Type-first EVM architecture. Every execution primitive is a strongly-typed branded Uint8Array or interface.
These are low-level primitives for EVM operations, not a full EVM implementation. For complete EVM implementations, see evmts/guillotine-mini (lightweight) or evmts/guillotine (full-featured).
Core Types
Opcode
Branded number type for EVM instructions (0x00-0xFF).
import { Opcode } from "@tevm/voltaire" ;
// Opcode type is a branded number
type OpcodeType = number & { readonly __tag : "Opcode" };
// Create opcodes
const add : Opcode = Opcode . ADD ; // 0x01
const push1 : Opcode = Opcode . PUSH1 ; // 0x60
const sload : Opcode = Opcode . SLOAD ; // 0x54
// Type safety prevents passing arbitrary numbers
// ❌ const invalid: Opcode = 0x01; // Type error!
See Opcode documentation for complete reference.
Instruction
Opcode with program counter offset and optional immediate data.
import { Instruction , Opcode } from "@tevm/voltaire" ;
type Instruction = {
/** Program counter offset */
offset : number ;
/** The opcode */
opcode : OpcodeType ;
/** Immediate data for PUSH operations */
immediate ?: Uint8Array ;
};
// PUSH1 0x42 at offset 0
const push : Instruction = {
offset: 0 ,
opcode: Opcode . PUSH1 ,
immediate: new Uint8Array ([ 0x42 ]),
};
// ADD at offset 2
const add : Instruction = {
offset: 2 ,
opcode: Opcode . ADD ,
};
Instructions are returned by bytecode analysis:
import { Bytecode } from "@tevm/voltaire" ;
const code = Bytecode . from ( "0x6001600201" );
const analysis = code . analyze ();
// analysis.instructions: Instruction[]
// [
// { offset: 0, opcode: 0x60, immediate: Uint8Array([0x01]) }, // PUSH1 1
// { offset: 2, opcode: 0x60, immediate: Uint8Array([0x02]) }, // PUSH1 2
// { offset: 4, opcode: 0x01 } // ADD
// ]
Execution Types
Frame
EVM execution frame with stack, memory, and execution state.
import type { BrandedFrame } from "@tevm/voltaire/evm" ;
import type { Address } from "@tevm/voltaire" ;
type BrandedFrame = {
readonly __tag : "Frame" ;
// Stack (max 1024 items, 256-bit words)
stack : bigint [];
// Memory (sparse map, byte-addressable)
memory : Map < number , number >;
memorySize : number ; // Word-aligned size
// Execution state
pc : number ; // Program counter
gasRemaining : bigint ;
bytecode : Uint8Array ;
// Call context
caller : Address ;
address : Address ;
value : bigint ;
calldata : Uint8Array ;
output : Uint8Array ;
returnData : Uint8Array ;
// Flags
stopped : boolean ;
reverted : boolean ;
isStatic : boolean ;
// Block context (for block opcodes)
blockNumber ?: bigint ;
blockTimestamp ?: bigint ;
blockGasLimit ?: bigint ;
chainId ?: bigint ;
blockBaseFee ?: bigint ;
blobBaseFee ?: bigint ;
// Logs (LOG0-LOG4 opcodes)
logs ?: Array <{
address : Address ;
topics : bigint [];
data : Uint8Array ;
}>;
};
Frame represents the complete execution state at any point in EVM execution.
Host
Interface for external state access (accounts, storage, code) and nested execution.
import type { BrandedHost , CallParams , CallResult , CreateParams , CreateResult } from "@tevm/voltaire/evm" ;
import type { Address } from "@tevm/voltaire" ;
type BrandedHost = {
readonly __tag : "Host" ;
// Account balance
getBalance : ( address : Address ) => bigint ;
setBalance : ( address : Address , balance : bigint ) => void ;
// Contract code
getCode : ( address : Address ) => Uint8Array ;
setCode : ( address : Address , code : Uint8Array ) => void ;
// Persistent storage
getStorage : ( address : Address , slot : bigint ) => bigint ;
setStorage : ( address : Address , slot : bigint , value : bigint ) => void ;
// Account nonce
getNonce : ( address : Address ) => bigint ;
setNonce : ( address : Address , nonce : bigint ) => void ;
// Transient storage (EIP-1153, transaction-scoped)
getTransientStorage : ( address : Address , slot : bigint ) => bigint ;
setTransientStorage : ( address : Address , slot : bigint , value : bigint ) => void ;
// Nested execution (optional - for CALL/CREATE opcodes)
call ?: ( params : CallParams ) => CallResult ;
create ?: ( params : CreateParams ) => CreateResult ;
};
The call and create methods are optional. When not provided, system opcodes (CALL, CREATE, etc.) return a NotImplemented error. For full EVM execution with nested calls, use:
guillotine : Production EVM with async state, tracing, full EIP support
guillotine-mini : Lightweight synchronous EVM for testing
Host provides pluggable state backend - implement for custom chains or test environments.
InstructionHandler
Function signature for opcode implementations.
import type { InstructionHandler , BrandedFrame , BrandedHost , EvmError } from "@tevm/voltaire/evm" ;
type InstructionHandler = (
frame : BrandedFrame ,
host : BrandedHost ,
) => EvmError | { type : "Success" };
Example handler implementation:
// ADD opcode (0x01): Pop two values, push sum
const addHandler : InstructionHandler = ( frame , host ) => {
// Check stack depth
if ( frame . stack . length < 2 ) {
return { type: "StackUnderflow" };
}
// Check gas
if ( frame . gasRemaining < 3 n ) {
return { type: "OutOfGas" };
}
// Execute
const b = frame . stack . pop () ! ;
const a = frame . stack . pop () ! ;
const result = ( a + b ) % 2 n ** 256 n ; // Mod 2^256 for overflow
frame . stack . push ( result );
frame . gasRemaining -= 3 n ;
frame . pc += 1 ;
return { type: "Success" };
};
All 166 EVM opcodes follow this pattern. See Instructions .
Call Types
CallParams
Parameters for cross-contract calls (CALL, STATICCALL, DELEGATECALL).
import type { CallParams , CallType , Address } from "@tevm/voltaire" ;
type CallType = "CALL" | "STATICCALL" | "DELEGATECALL" | "CALLCODE" ;
type CallParams = {
callType : CallType ;
target : Address ;
value : bigint ;
gasLimit : bigint ;
input : Uint8Array ;
caller : Address ;
isStatic : boolean ;
depth : number ;
};
Example usage:
// STATICCALL to view function
const params : CallParams = {
callType: "STATICCALL" ,
target: contractAddress ,
value: 0 n ,
gasLimit: 100000 n ,
input: encodedCalldata ,
caller: myAddress ,
isStatic: true ,
depth: 1 ,
};
CallResult
Result of call operation.
import type { CallResult , Address } from "@tevm/voltaire" ;
type CallResult = {
success : boolean ; // False if reverted
gasUsed : bigint ;
output : Uint8Array ; // Return data or revert reason
logs : Array <{
address : Address ;
topics : bigint [];
data : Uint8Array ;
}>;
gasRefund : bigint ;
};
Example:
// Successful call
const result : CallResult = {
success: true ,
gasUsed: 21000 n ,
output: returnData ,
logs: [],
gasRefund: 0 n ,
};
// Reverted call
const revertResult : CallResult = {
success: false ,
gasUsed: 50000 n ,
output: revertReason , // Revert message
logs: [],
gasRefund: 0 n ,
};
Creation Types
CreateParams
Parameters for contract deployment (CREATE, CREATE2).
import type { CreateParams , Address } from "@tevm/voltaire" ;
type CreateParams = {
caller : Address ;
value : bigint ;
initCode : Uint8Array ;
gasLimit : bigint ;
salt ?: bigint ; // For CREATE2
depth : number ;
};
Example:
// CREATE2 deployment with deterministic address
const params : CreateParams = {
caller: deployerAddress ,
value: 0 n ,
initCode: contractBytecode ,
gasLimit: 1000000 n ,
salt: 0x1234 n , // Determines address
depth: 1 ,
};
CreateResult
Result of contract creation.
import type { CreateResult , Address } from "@tevm/voltaire" ;
type CreateResult = {
success : boolean ;
address : Address | null ; // Null if failed
gasUsed : bigint ;
output : Uint8Array ; // Runtime code or revert reason
gasRefund : bigint ;
};
Example:
// Successful deployment
const result : CreateResult = {
success: true ,
address: newContractAddress ,
gasUsed: 200000 n ,
output: runtimeCode ,
gasRefund: 0 n ,
};
Error Types
EvmError
Execution errors that halt the EVM.
type EvmError =
| { type : "StackOverflow" }
| { type : "StackUnderflow" }
| { type : "OutOfGas" }
| { type : "OutOfBounds" }
| { type : "InvalidJump" }
| { type : "InvalidOpcode" }
| { type : "RevertExecuted" }
| { type : "CallDepthExceeded" }
| { type : "WriteProtection" }
| { type : "InsufficientBalance" }
| { type : "NotImplemented" ; message : string };
The NotImplemented error is returned by system opcodes (CALL, CREATE, etc.) when the host doesn’t provide call or create methods. This is intentional - these low-level primitives don’t include an execution engine. Use guillotine or guillotine-mini for full EVM execution.
Error handling:
const result = instructionHandler ( frame , host );
if ( result . type !== "Success" ) {
// Handle error
switch ( result . type ) {
case "StackUnderflow" :
console . error ( "Stack underflow - not enough items" );
break ;
case "OutOfGas" :
console . error ( "Insufficient gas" );
break ;
case "RevertExecuted" :
console . error ( "Execution reverted" );
break ;
case "NotImplemented" :
console . error ( "Feature not implemented:" , result . message );
break ;
// ... handle other errors
}
}
Info
Opcode metadata (gas cost, stack effect).
import { Opcode } from "@tevm/voltaire" ;
type Info = {
gasCost : number ; // Base gas cost (may be dynamic)
stackInputs : number ; // Items consumed from stack
stackOutputs : number ; // Items pushed to stack
name : string ; // Opcode name
};
// Get opcode info
const info = Opcode . info ( Opcode . ADD );
// {
// gasCost: 3,
// stackInputs: 2,
// stackOutputs: 1,
// name: "ADD"
// }
Type Safety Benefits
Prevents Type Confusion
import { Opcode , Address , Bytecode } from "@tevm/voltaire" ;
// ❌ Cannot pass wrong type
const opcode : Opcode = 0x01 ; // Type error!
const addr : Address = "0x123..." ; // Type error!
// ✅ Must use constructors
const opcode = Opcode . ADD ; // Correct
const addr = Address ( "0x123..." ); // Correct
Compile-Time Validation
// ❌ Type mismatch caught at compile time
function executeOpcode ( op : Opcode ) { /*...*/ }
executeOpcode ( 0x60 ); // Type error!
// ✅ Type-safe
executeOpcode ( Opcode . PUSH1 ); // Correct
IDE Autocomplete
TypeScript provides full IntelliSense:
import { Opcode } from "@tevm/voltaire" ;
const op = Opcode .
// ^ Shows all opcode names with documentation
Zero Runtime Overhead
Branded types are compile-time only:
// TypeScript
const op : OpcodeType = Opcode . ADD ;
// Compiles to JavaScript
const op = 0x01 ; // No wrapper, just the number
Architecture
Execution Flow
import type { BrandedFrame , BrandedHost , InstructionHandler } from "@tevm/voltaire/evm" ;
// 1. Initialize frame
const frame : BrandedFrame = {
stack: [],
memory: new Map (),
memorySize: 0 ,
pc: 0 ,
gasRemaining: 1000000 n ,
bytecode: code ,
// ... other fields
};
// 2. Initialize host
const host : BrandedHost = {
getBalance : ( addr ) => balances . get ( addr ) || 0 n ,
setBalance : ( addr , bal ) => balances . set ( addr , bal ),
// ... other methods
};
// 3. Execute instructions
while ( ! frame . stopped && ! frame . reverted ) {
const opcode = frame . bytecode [ frame . pc ];
const handler = getHandler ( opcode );
const result = handler ( frame , host );
if ( result . type !== "Success" ) {
// Handle error
break ;
}
}
Pluggable Backend
Host interface allows custom state implementations:
// In-memory state for testing
class MemoryHost implements BrandedHost {
private balances = new Map < Address , bigint >();
private storage = new Map < string , bigint >();
getBalance ( addr : Address ) { return this . balances . get ( addr ) || 0 n ; }
setBalance ( addr : Address , bal : bigint ) { this . balances . set ( addr , bal ); }
// ... implement other methods
}
// Database-backed state for production
class DatabaseHost implements BrandedHost {
async getBalance ( addr : Address ) { return await db . getBalance ( addr ); }
// ... implement with DB queries
}
Opcode Opcode type and constants
Bytecode Bytecode analysis and instruction parsing
Instructions All 166 opcode handlers
Address Address type for account references
References