Skip to main content

ERC-4337 Relayer

The ZeroQuant Relayer is a Rust-based ERC-4337 bundler that collects UserOperations, validates them, and submits bundles to the blockchain.

Overview

The relayer provides:

  • UserOperation Validation - Validates signatures, nonces, and gas parameters
  • Bundle Creation - Groups multiple UserOps for efficient execution
  • Transaction Submission - Signs and submits bundles with EIP-1559 transactions
  • Receipt Polling - Waits for transaction confirmation with configurable timeout

Architecture

┌─────────────────────────────────────────────────────────────┐
│ API Layer (Axum) │
│ POST /rpc - JSON-RPC endpoint for UserOperations │
│ GET /health - Health check │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ Validator │
│ - Signature verification │
│ - Nonce validation │
│ - Gas parameter checks │
│ - Session/permission verification │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ Mempool │
│ - Pending UserOperations │
│ - Priority ordering │
│ - Deduplication │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ Bundler │
│ - Bundle creation │
│ - Gas estimation aggregation │
│ - handleOps calldata encoding │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ Submitter │
│ - EIP-1559 transaction building │
│ - Private key signing │
│ - Nonce management │
│ - Receipt polling │
└─────────────────────────────────────────────────────────────┘

Configuration

Environment Variables

VariableDescriptionDefault
RPC_URLEthereum RPC endpointRequired
PRIVATE_KEYBundler wallet private keyRequired
ENTRY_POINT_ADDRESSERC-4337 EntryPoint contract0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
CHAIN_IDTarget chain ID1
PORTAPI server port3001
RUST_LOGLog levelinfo

Example .env

RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
PRIVATE_KEY=0x...your_bundler_private_key...
ENTRY_POINT_ADDRESS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
CHAIN_ID=11155111
PORT=3001
RUST_LOG=info

API Reference

JSON-RPC Endpoint

POST /rpc

All operations use JSON-RPC 2.0 format.

eth_sendUserOperation

Submit a UserOperation for inclusion in a bundle.

{
"jsonrpc": "2.0",
"method": "eth_sendUserOperation",
"params": [
{
"sender": "0x...",
"nonce": "0x1",
"initCode": "0x",
"callData": "0x...",
"callGasLimit": "0x5208",
"verificationGasLimit": "0x5208",
"preVerificationGas": "0x5208",
"maxFeePerGas": "0x3b9aca00",
"maxPriorityFeePerGas": "0x3b9aca00",
"paymasterAndData": "0x",
"signature": "0x..."
},
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
],
"id": 1
}

Response:

{
"jsonrpc": "2.0",
"result": "0x...userOpHash...",
"id": 1
}

eth_getUserOperationReceipt

Get the receipt for a submitted UserOperation.

{
"jsonrpc": "2.0",
"method": "eth_getUserOperationReceipt",
"params": ["0x...userOpHash..."],
"id": 1
}

Response:

{
"jsonrpc": "2.0",
"result": {
"userOpHash": "0x...",
"sender": "0x...",
"nonce": "0x1",
"actualGasCost": "0x...",
"actualGasUsed": "0x...",
"success": true,
"receipt": {
"transactionHash": "0x...",
"blockNumber": "0x...",
"blockHash": "0x..."
}
},
"id": 1
}

eth_supportedEntryPoints

Get supported EntryPoint addresses.

{
"jsonrpc": "2.0",
"method": "eth_supportedEntryPoints",
"params": [],
"id": 1
}

Health Check

GET /health

Returns 200 OK when the relayer is running.

Running the Relayer

Local Development

cd packages/relayer

# Set up environment
cp .env.example .env
# Edit .env with your configuration

# Build and run
cargo run

Docker

# Build image
docker build -t zeroquant-relayer -f packages/relayer/Dockerfile .

# Run container
docker run -p 3001:3001 \
-e RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY \
-e PRIVATE_KEY=0x... \
-e CHAIN_ID=11155111 \
zeroquant-relayer

Docker Compose

docker compose up relayer

Bundle Submission Flow

  1. UserOp Received - API receives UserOperation via JSON-RPC
  2. Validation - Validator checks signature, nonce, gas, permissions
  3. Mempool - Valid UserOp added to pending pool
  4. Bundle Creation - Bundler groups UserOps (configurable size)
  5. Gas Estimation - Estimate gas via eth_estimateGas
  6. Transaction Building - Create EIP-1559 transaction with handleOps calldata
  7. Signing - Sign transaction with bundler wallet
  8. Submission - Send via eth_sendRawTransaction
  9. Confirmation - Poll for receipt with configurable timeout

Transaction Details

EIP-1559 Transactions

The submitter builds EIP-1559 transactions:

struct Eip1559Transaction {
chain_id: u64,
nonce: u64,
max_priority_fee_per_gas: U256,
max_fee_per_gas: U256,
gas_limit: u64,
to: Address,
value: U256,
data: Bytes,
access_list: Vec<(Address, Vec<B256>)>,
}

Gas Estimation

  • Base fee fetched via eth_gasPrice
  • Priority fee via eth_maxPriorityFeePerGas (or 1 gwei default)
  • Gas limit via eth_estimateGas with 20% buffer

Nonce Management

  • Atomic nonce counter for sequential transactions
  • Fetches on-chain nonce via eth_getTransactionCount
  • Handles nonce gaps gracefully

Error Handling

ErrorDescriptionResolution
InvalidSignatureUserOp signature verification failedCheck signer address and signature
InvalidNonceNonce doesn't match expectedFetch current nonce from sender
InsufficientGasGas parameters too lowIncrease gas limits
SimulationFailedUserOp would revertCheck callData and target contract
ProviderErrorRPC call failedCheck RPC URL and network

Monitoring

Logs

The relayer uses tracing for structured logging:

# Info level (default)
RUST_LOG=info cargo run

# Debug level for troubleshooting
RUST_LOG=debug cargo run

# Trace level for maximum detail
RUST_LOG=trace cargo run

Metrics (Planned)

Future versions will expose Prometheus metrics:

  • relayer_userops_received_total
  • relayer_bundles_submitted_total
  • relayer_bundle_size_histogram
  • relayer_submission_latency_seconds

Security Considerations

  1. Private Key Security - Store bundler key securely (HSM, secrets manager)
  2. Gas Price Limits - Configure max gas price to prevent overpaying
  3. Rate Limiting - Implement rate limiting on API endpoint
  4. Validation - All UserOps validated before mempool inclusion
  5. Nonce Protection - Atomic nonce management prevents double-submission

Next Steps