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​