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
| Variable | Description | Default |
|---|---|---|
RPC_URL | Ethereum RPC endpoint | Required |
PRIVATE_KEY | Bundler wallet private key | Required |
ENTRY_POINT_ADDRESS | ERC-4337 EntryPoint contract | 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 |
CHAIN_ID | Target chain ID | 1 |
PORT | API server port | 3001 |
RUST_LOG | Log level | info |
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
- UserOp Received - API receives UserOperation via JSON-RPC
- Validation - Validator checks signature, nonce, gas, permissions
- Mempool - Valid UserOp added to pending pool
- Bundle Creation - Bundler groups UserOps (configurable size)
- Gas Estimation - Estimate gas via
eth_estimateGas - Transaction Building - Create EIP-1559 transaction with handleOps calldata
- Signing - Sign transaction with bundler wallet
- Submission - Send via
eth_sendRawTransaction - 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_estimateGaswith 20% buffer
Nonce Management
- Atomic nonce counter for sequential transactions
- Fetches on-chain nonce via
eth_getTransactionCount - Handles nonce gaps gracefully
Error Handling
| Error | Description | Resolution |
|---|---|---|
InvalidSignature | UserOp signature verification failed | Check signer address and signature |
InvalidNonce | Nonce doesn't match expected | Fetch current nonce from sender |
InsufficientGas | Gas parameters too low | Increase gas limits |
SimulationFailed | UserOp would revert | Check callData and target contract |
ProviderError | RPC call failed | Check 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_totalrelayer_bundles_submitted_totalrelayer_bundle_size_histogramrelayer_submission_latency_seconds
Security Considerations
- Private Key Security - Store bundler key securely (HSM, secrets manager)
- Gas Price Limits - Configure max gas price to prevent overpaying
- Rate Limiting - Implement rate limiting on API endpoint
- Validation - All UserOps validated before mempool inclusion
- Nonce Protection - Atomic nonce management prevents double-submission
Next Steps
- SDK Client - Submit UserOps from the SDK
- Transactions - Build transactions for vaults
- API Overview - Full API reference