This is experimental software and is provided "as is" without warranty of any kind. Use at your own risk. The software may contain bugs, security vulnerabilities, or other issues that could result in loss of funds or data.
A sequencer built in Zig for L2 rollups that accepts transactions, orders them, forms batches, and posts them to L1.
The Native Sequencer is a high-performance transaction sequencer designed for Layer 2 rollup solutions. It provides a complete pipeline for accepting L2 transactions from users, validating and ordering them, forming batches, and submitting them to the L1 blockchain.
- Predictable, low overhead runtime with no garbage collection - ideal for latency-sensitive I/O and high throughput
- Excellent C interop - reuse battle-tested C libraries (LMDB, libsecp256k1, etc.)
- Strong control over memory layout - enables zero-copy network stacks and deterministic serialization
- Modern tooling - easy cross-compilation for Linux amd64/arm64 containers
- Built with Zig 0.14.1 for stability and performance
- op-node Style Architecture: Delegates execution to L2 geth via Engine API
- L1 Derivation: Derives safe blocks from L1 batches
- Witness Generation: Generates witness data for stateless execution on L1
- ExecuteTx Support: Uses ExecuteTx transactions (type 0x05) for L1 submission
- State Queries: Queries L2 geth for state instead of maintaining local state
- MEV-Aware Ordering: Transaction ordering with MEV support
- Metrics & Observability: Prometheus-style metrics endpoint
The native-sequencer follows an op-node style architecture, delegating execution to L2 geth while handling consensus, transaction ordering, and L1 derivation. It uses ExecuteTx transactions for stateless execution on L1.
┌──────────────────┐
│ native-sequencer │ (Consensus Layer)
└────────┬─────────┘
│ 1. Request block building
│ engine_forkchoiceUpdatedV3(payload_attrs)
▼
┌──────────────────┐
│ L2 geth │ (Execution Layer)
│ │ 2. Build block
│ │ 3. Execute transactions
│ │ 4. Return payload
└────────┬─────────┘
│ 5. Generate Witness data
│ (State trie nodes, contract code, block headers)
▼
┌──────────────────┐
│ native-sequencer │
│ │ 6. Build witness from execution
│ │ 7. Update fork choice
│ │ 8. Submit ExecuteTx transaction to L1
└────────┬─────────┘
│
▼
┌──────────────────┐
│ L1 │ (Stateless execution via EXECUTE precompile)
│ │ (Witness provides state for execution)
└──────────────────┘
transaction.zig: Transaction data structures, RLP encoding/decoding, signature recoverytransaction_execute.zig: ExecuteTx transaction type (0x05) implementation for stateless executionblock.zig: Block data structures and serializationbatch.zig: Batch data structures for grouping blockswitness.zig: Witness data structures (state trie nodes, contract code, block headers)witness_builder.zig: Witness generation from execution tracesrlp.zig: RLP (Recursive Length Prefix) encoding/decoding for Ethereum datatypes.zig: Common type definitions (Hash, Address, etc.)signature.zig: ECDSA signature verification and recovery
server.zig: JSON-RPC HTTP server for transaction submission and queriesjsonrpc.zig: JSON-RPC protocol implementationhttp.zig: HTTP request/response handling
ingress.zig: Transaction ingress handler - accepts and validates transactionstransaction.zig: Transaction validator - validates signatures, nonces, balances using L2 geth state
mempool.zig: Priority queue of pending transactions with gas price orderingwal.zig: Write-ahead log for mempool persistence
sequencer.zig: Main sequencer logic - requests payloads from L2 geth, manages block stateblock_state.zig: Tracks safe/unsafe/finalized/head blocks (op-node style)execution.zig: Local execution engine for witness generation (not used in main block building path)mev.zig: MEV-aware transaction orderingreorg_handler.zig: Chain reorganization detection and handling
client.zig: L1 JSON-RPC client for batch submission and block queriesderivation.zig: L1 derivation pipeline - derives L2 blocks from L1 batches (op-node style)batch_parser.zig: Parses L2 batch data from L1 transaction calldataexecute_tx_builder.zig: Builds ExecuteTx transactions with witness data for L1 submission
engine_api_client.zig: Engine API client for requesting payloads from L2 geth with full field parsing (extraData, baseFeePerGas, withdrawals, blobGasUsed, excessBlobGas) and JWT authenticationpayload_attrs.zig: Payload attributes builder forengine_forkchoiceUpdatedV3state_provider.zig: State provider for querying L2 geth state (nonces, balances, code)
builder.zig: Groups blocks into batches for L1 submission with size/gas limits
manager.zig: State manager for tracking nonces, balances, receipts (used for witness generation)state_root.zig: State root computation utilities
lmdb.zig: LMDB database bindings for persistent state storagewitness_storage.zig: Witness data storage and retrieval
config.zig: Configuration management from environment variables
metrics.zig: Metrics collection (transaction counts, blocks created, batches submitted)server.zig: Metrics HTTP server for Prometheus-style metrics
hash.zig: Cryptographic hashing (Keccak256)secp256k1_wrapper.zig: ECDSA signature operations via libsecp256k1signature.zig: Signature verification and address recoveryjwt.zig: JWT token generation for Engine API authentication (HMAC-SHA256)
- op-node Style: Delegates execution to L2 geth via Engine API (same as op-node)
- L1 Derivation: Derives safe blocks from L1 batches (op-node style)
- Safe/Unsafe Blocks: Tracks safe (L1-derived) and unsafe (sequencer-proposed) blocks
- Witness Generation: Generates witness data for stateless execution on L1
- ExecuteTx Submission: Uses ExecuteTx transactions (type 0x05) for L1 submission
- State Queries: Queries L2 geth for state (nonces, balances) instead of maintaining local state
- Zig 0.14.1 (Install Zig)
- C compiler (for vendored C dependencies)
- LMDB (Lightning Memory-Mapped Database) - for persistence
- macOS:
brew install lmdb - Linux:
sudo apt-get install liblmdb-dev(Debian/Ubuntu) orsudo yum install lmdb-devel(RHEL/CentOS) - Or build from source: https://github.com/LMDB/lmdb
- macOS:
# Build the sequencer executable (native architecture)
zig build
# Build for specific target
zig build -Dtarget=x86_64-linux-gnu.2.38 # Linux x86_64
zig build -Dtarget=aarch64-macos # macOS ARM64
zig build -Dtarget=x86_64-windows # Windows x86_64
# Build and run
zig build run
# Run tests
zig build test
# Run linter (format check + AST checks)
zig build lint
# Format code automatically
zig build fmt
# Run lint-fix (alias for fmt)
zig build lint-fixThe build output will be in zig-out/bin/sequencer (or sequencer.exe on Windows).
The sequencer supports cross-compilation to multiple platforms:
- macOS: Native ARM64 builds supported. x86_64 cross-compilation from ARM64 may require architecture-matched libraries.
- Windows: Cross-compilation supported (LMDB disabled, uses in-memory state).
- Linux: Cross-compilation requires proper setup with cross-compilation toolchains and libraries.
Note: LMDB linking is automatically handled based on the target platform. Windows builds use in-memory state instead of LMDB persistence.
# Build Docker image
docker build -t native-sequencer .
# Run with Docker
docker run -d \
--name sequencer \
-p 6197:6197 \
-p 9090:9090 \
-v sequencer-data:/app/data \
-e L1_RPC_URL=http://host.docker.internal:8545 \
-e SEQUENCER_KEY=<your-private-key> \
native-sequencer
# View logs
docker logs -f sequencer
# Stop the sequencer
docker stop sequencer
docker rm sequencerContainer won't start:
docker logs sequencerPort already in use: Change the port mapping:
docker run -p 16197:6197 -p 19090:9090 ...L1 connection issues:
- On Mac/Windows, use
host.docker.internalto access the host:-e L1_RPC_URL=http://host.docker.internal:8545
- On Linux, you may need to use
--network host:docker run --network host ...
Permission issues: Ensure the data directory has correct permissions:
sudo chown -R 1000:1000 /path/to/data# Run with default configuration
zig build run
# Or run the built executable directly
./zig-out/bin/sequencerConfigure the sequencer using environment variables. Key variables:
- API:
API_HOST,API_PORT(default:0.0.0.0:6197) - L1:
L1_RPC_URL,L1_CHAIN_ID(default:61971),SEQUENCER_KEY - L2:
L2_RPC_URL(default:http://localhost:18545),L2_ENGINE_API_PORT(default:18551),L2_CHAIN_ID(default:61972),L2_JWT_SECRET(hex-encoded 32-byte secret for Engine API authentication) - Mempool:
MEMPOOL_MAX_SIZE,MEMPOOL_WAL_PATH - Batch:
BATCH_SIZE_LIMIT,BATCH_INTERVAL_MS,BLOCK_GAS_LIMIT - State:
STATE_DB_PATH - Metrics:
METRICS_PORT(default:9090) - Controls:
EMERGENCY_HALT,RATE_LIMIT_PER_SECOND
All variables have defaults. See src/config/config.zig for complete list.
The native-sequencer exposes a JSON-RPC API over HTTP. All endpoints accept POST requests to / with JSON-RPC 2.0 formatted requests.
http://<API_HOST>:<API_PORT>/
Default: http://0.0.0.0:6197/
Submit a raw transaction to the sequencer. Supports both legacy transactions and ExecuteTx transactions (type 0x05).
Request:
{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": ["0x<raw_transaction_hex>"],
"id": 1
}Parameters:
params[0](string, required): Hex-encoded raw transaction bytes (with or without0xprefix)
Response (Success):
{
"jsonrpc": "2.0",
"result": "0x<transaction_hash>",
"id": 1
}Response (Error):
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid transaction encoding"
},
"id": 1
}Transaction Types Supported:
-
Legacy Transactions (Standard Ethereum transactions):
- Validated for signature, nonce, balance, and gas price
- Added to mempool if valid
- Sequenced into blocks by the sequencer
- Returns transaction hash
-
ExecuteTx Transactions (Type 0x05):
- Stateless transactions designed for L1 execution
- Minimally validated (signature check for deduplication)
- Automatically forwarded to L1 geth via
eth_sendRawTransaction - Not stored in sequencer's mempool
- Returns transaction hash (from L1 if forwarded, or computed locally)
Error Codes:
-32602(InvalidParams): Missing or invalid transaction data-32000(ServerError): Transaction validation failed, processing failed, or forwarding failed
Example (Legacy Transaction):
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": ["0xf86c808502540be400825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc9e3c0a9f6eccdf15726f5f"],
"id": 1
}'Example (ExecuteTx Transaction):
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": ["0x05<execute_tx_rlp_encoded>"],
"id": 1
}'Submit a raw transaction with conditional inclusion criteria (EIP-7796). The transaction will only be included in a block if the specified conditions are met.
Request:
{
"jsonrpc": "2.0",
"method": "eth_sendRawTransactionConditional",
"params": [
"0x<raw_transaction_hex>",
{
"blockNumberMin": "0x42",
"blockNumberMax": "0x100",
"timestampMin": "0x1234567890",
"timestampMax": "0x123456789a"
}
],
"id": 1
}Parameters:
params[0](string, required): Hex-encoded raw transaction bytes (with or without0xprefix)params[1](object, required): Conditional options object with the following optional fields:blockNumberMin(string, optional): Minimum block number (hex string, e.g.,"0x42")blockNumberMax(string, optional): Maximum block number (hex string, e.g.,"0x100")timestampMin(string or integer, optional): Minimum block timestamp (hex string or integer)timestampMax(string or integer, optional): Maximum block timestamp (hex string or integer)
Response (Success):
{
"jsonrpc": "2.0",
"result": "0x<transaction_hash>",
"id": 1
}Response (Error):
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Failed to parse conditional options"
},
"id": 1
}Conditional Inclusion:
- The transaction is added to the mempool but will only be included in a block when all specified conditions are satisfied
- Conditions are checked against the current block number and timestamp when building blocks
- If conditions are not met, the transaction remains in the mempool until conditions are satisfied or it expires
- ExecuteTx transactions (type 0x05) do not support conditional submission
Error Codes:
-32602(InvalidParams): Missing or invalid transaction data or options-32000(ServerError): Transaction validation failed, processing failed, or insertion failed
Example:
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendRawTransactionConditional",
"params": [
"0xf86c808502540be400825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc9e3c0a9f6eccdf15726f5f",
{
"blockNumberMin": "0x100",
"blockNumberMax": "0x200"
}
],
"id": 1
}'Get transaction receipt by transaction hash.
Request:
{
"jsonrpc": "2.0",
"method": "eth_getTransactionReceipt",
"params": ["0x<transaction_hash>"],
"id": 1
}Parameters:
params[0](string, required): Transaction hash (hex-encoded, with or without0xprefix)
Response (Success):
{
"jsonrpc": "2.0",
"result": null,
"id": 1
}Note: Currently returns null as receipt storage is not yet implemented. Future versions will return full receipt data including block number, gas used, logs, etc.
Response (Error):
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Missing params"
},
"id": 1
}Example:
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_getTransactionReceipt",
"params": ["0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"],
"id": 1
}'Get the current block number.
Request:
{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}Parameters: None (empty array)
Response (Success):
{
"jsonrpc": "2.0",
"result": "0x0",
"id": 1
}Note: Currently returns 0x0 as block number tracking is not yet fully implemented. Future versions will return the actual current block number.
Example:
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'Generate witness data for a single transaction. This is a debug/testing endpoint for witness generation.
Request:
{
"jsonrpc": "2.0",
"method": "debug_generateWitness",
"params": ["0x<raw_transaction_hex>"],
"id": 1
}Parameters:
params[0](string, required): Hex-encoded raw transaction bytes (with or without0xprefix)
Response (Success):
{
"jsonrpc": "2.0",
"result": {
"witness": "0x<rlp_encoded_witness>",
"witnessSize": 1234
},
"id": 1
}Response Fields:
witness(string): Hex-encoded RLP-encoded witness data containing state trie nodes, contract code, and block headerswitnessSize(integer): Size of the witness in bytes
Response (Error):
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "Sequencer not available for witness generation"
},
"id": 1
}Error Codes:
-32602(InvalidParams): Missing or invalid transaction data-32000(ServerError): Sequencer not available, transaction execution failed, or witness generation failed
Note: This endpoint executes the transaction locally to track state access. In production, witness generation happens during ExecuteTx building.
Example:
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "debug_generateWitness",
"params": ["0xf86c808502540be400825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc9e3c0a9f6eccdf15726f5f"],
"id": 1
}'Generate witness data for a block. This is a debug/testing endpoint for block witness generation.
Request:
{
"jsonrpc": "2.0",
"method": "debug_generateBlockWitness",
"params": ["latest"]
}or
{
"jsonrpc": "2.0",
"method": "debug_generateBlockWitness",
"params": ["0x42"]
}Parameters:
params[0](string or integer, required): Block number as hex string ("0x42"), decimal integer (42), or"latest"for the latest block
Response (Success):
{
"jsonrpc": "2.0",
"result": {
"witness": "0x<rlp_encoded_witness>",
"witnessSize": 5678,
"blockNumber": 42,
"transactionCount": 5
},
"id": 1
}Response Fields:
witness(string): Hex-encoded RLP-encoded witness data containing state trie nodes, contract code, and block headers for all transactions in the blockwitnessSize(integer): Size of the witness in bytesblockNumber(integer): Block number for which witness was generatedtransactionCount(integer): Number of transactions in the block
Response (Error):
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "Failed to build block"
},
"id": 1
}Error Codes:
-32602(InvalidParams): Missing or invalid block number-32000(ServerError): Sequencer not available or failed to build block
Note: This endpoint builds a new block from the mempool and generates witness for it. In production, witness generation happens during ExecuteTx batch building.
Example:
curl -X POST http://localhost:6197/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "debug_generateBlockWitness",
"params": ["latest"],
"id": 1
}'The sequencer uses standard JSON-RPC 2.0 error codes:
| Code | Name | Description |
|---|---|---|
-32700 |
ParseError | Invalid JSON was received |
-32600 |
InvalidRequest | The JSON sent is not a valid Request object |
-32601 |
MethodNotFound | The method does not exist |
-32602 |
InvalidParams | Invalid method parameters |
-32603 |
InternalError | Internal JSON-RPC error |
-32000 |
ServerError | Server error (validation failures, processing errors) |
200 OK: Request processed successfully (even if JSON-RPC returns an error)404 Not Found: Invalid HTTP method or path (must be POST/)
Access metrics at http://localhost:9090 (or configured port).
Available metrics:
- Transaction acceptance rate
- Blocks created
- Batches submitted
- L1 submission errors
- Mempool size
This is experimental software. Core features are implemented:
- ✅ op-node style architecture (L1 derivation, Engine API, safe/unsafe blocks)
- ✅ Transaction validation and mempool
- ✅ Batch formation and L1 submission via ExecuteTx
- ✅ LMDB persistence
- ✅ Witness generation for stateless execution
- ✅ Engine API full compatibility (parses all fields: extraData, baseFeePerGas, withdrawals, blobGasUsed, excessBlobGas)
- ✅ JWT authentication for Engine API
- ✅ Cross-platform builds (macOS ARM64, Windows x86_64, Linux x86_64)
- ⏳ L1 subscription monitoring (WebSocket support)
- ⏳ Comprehensive testing
The repository includes comprehensive linting checks to ensure code quality:
- Format Check: Validates code formatting using
zig fmt --check - AST Checks: Validates syntax and type correctness using
zig ast-checkfor key modules - Format Fix: Automatically formats code using
zig fmt
# Run all linting checks (format + AST)
# Exit code 1 if formatting issues are found
zig build lint
# Format code automatically (fixes formatting issues)
zig build fmt
# Run lint-fix (alias for fmt)
zig build lint-fixNote: If zig build lint fails, run zig build fmt to automatically fix formatting issues, then commit the changes.
GitHub Actions workflow (.github/workflows/ci.yml) runs linting, testing, and multi-platform builds (Linux, macOS, Windows) on push/PR.
The sequencer supports ExecuteTx transactions (type 0x05) for stateless execution on L1. ExecuteTx transactions are:
- Stateless: Designed for execution by L1 geth nodes
- Forwarded to L1: Automatically forwarded to L1 geth via
eth_sendRawTransaction - Minimally Validated: Only signature check for deduplication (full validation by L1 geth)
- Not Mempooled: Not stored in sequencer's mempool
ExecuteTx includes pre-state hash, witness data, withdrawals, and standard EIP-1559 fields. See src/core/transaction_execute.zig for implementation details.
The sequencer's Engine API client (src/l2/engine_api_client.zig) provides full compatibility with geth's Engine API:
- Full Field Parsing: Parses all fields returned by geth, including:
extraData: Block extra databaseFeePerGas: Base fee per gas (EIP-1559)withdrawals: Validator withdrawals array (Shanghai/Cancun upgrade)blobGasUsed: Blob gas used (Cancun upgrade)excessBlobGas: Excess blob gas (Cancun upgrade)
- JWT Authentication: Supports JWT token authentication for Engine API endpoints (configured via
L2_JWT_SECRET) - Backward Compatibility: Handles optional fields gracefully (null values for pre-upgrade blocks)
- Memory Management: Proper cleanup of all parsed data structures
The sequencer supports JWT authentication for Engine API communication with L2 geth. Configure the JWT secret using the L2_JWT_SECRET environment variable:
# Generate a random 32-byte hex secret
openssl rand -hex 32
# Set in environment
export L2_JWT_SECRET=0x<generated_secret>
# Or pass directly to the sequencer
L2_JWT_SECRET=0x<generated_secret> ./zig-out/bin/sequencerImportant: The same JWT secret must be configured in both the sequencer and L2 geth. See your L2 geth documentation for JWT configuration.
Note: If L2_JWT_SECRET is not provided, the sequencer will attempt Engine API calls without authentication. This may fail if L2 geth requires JWT authentication.
LMDB: The sequencer uses LMDB for persistence. Make sure LMDB is installed on your system (see Prerequisites section above).
zig build -Dtarget=x86_64-linux-gnu.2.38CI Compatibility: GitHub Actions ubuntu-latest runners use Ubuntu 22.04 (glibc 2.35), which is insufficient. The CI workflow specifies glibc 2.38 in the build target to ensure compatibility. For local builds on older Linux distributions, you may need to:
- Use a newer Linux distribution (Ubuntu 24.04+ or equivalent)
- Build in a container with glibc 2.38+
- Use the Docker build which includes the correct glibc version
See LICENSE file.