Skip to main content

Run a Bundler

Nethereum includes a full ERC-4337 bundler — the off-chain service that collects UserOperations, validates them, and submits bundles to the EntryPoint contract. You can run it in-process for testing, as part of your application for app-specific chains, or as a standalone JSON-RPC server for production use.

Prerequisites

Install the bundler package and, if you want a JSON-RPC endpoint, the RPC server package:

dotnet add package Nethereum.AccountAbstraction.Bundler
dotnet add package Nethereum.AccountAbstraction.Bundler.RpcServer

You will also need:

  • A running Ethereum node (local devchain or public RPC)
  • A funded EOA to act as the bundler's beneficiary (receives bundler fees)
  • A deployed EntryPoint contract (or use the canonical addresses from EntryPointAddresses)

Quick Start: In-Process Bundler

The simplest setup creates a BundlerService with a config and a web3 connection. The bundler validates incoming UserOperations, stores them in an in-memory mempool, and submits bundles to the EntryPoint on a timer.

using Nethereum.AccountAbstraction.Bundler;
using Nethereum.Web3;
using Nethereum.Web3.Accounts;

var privateKey = "0xYOUR_BUNDLER_PRIVATE_KEY";
var web3 = new Web3(new Account(privateKey), "http://localhost:8545");

var config = BundlerConfig.CreateAppChainConfig(
entryPoint: EntryPointAddresses.Latest,
beneficiary: "0xYourBeneficiaryAddress");

var bundlerService = new BundlerService(web3, config);

The bundler is now running. It will automatically bundle UserOperations every AutoBundleIntervalMs milliseconds. You can also submit a UserOperation directly and trigger bundling on demand:

// Submit a UserOperation
string userOpHash = await bundlerService.SendUserOperationAsync(packedUserOp, EntryPointAddresses.Latest);

// Trigger immediate bundling
BundleResult result = await bundlerService.ExecuteBundleAsync();

// Check operation status
var receipt = await bundlerService.GetUserOperationReceiptAsync(userOpHash);

When you are finished, dispose the service to stop the auto-bundle timer:

bundlerService.Dispose();

Configuration Presets

BundlerConfig provides three presets that cover common scenarios. Each returns a fully configured object you can further customise.

PresetUse CaseStrictValidationERC-7562AutoBundle IntervalMinPriorityFee
CreateAppChainConfigPrivate / app-specific chainsfalsefalse1,000 ms0
CreateStandardConfigPublic testnets, stagingtruetrue10,000 ms1 Gwei
CreateProductionConfigPublic mainnettruetrue10,000 ms1 Gwei + staking

Create each preset with an entry point and beneficiary address:

// App chain — relaxed validation, fast bundling
var appChainConfig = BundlerConfig.CreateAppChainConfig(
entryPoint: EntryPointAddresses.Latest,
beneficiary: "0xBeneficiary");

// Standard — strict validation, ERC-7562 opcode checks
var standardConfig = BundlerConfig.CreateStandardConfig(
entryPoint: EntryPointAddresses.Latest,
beneficiary: "0xBeneficiary");

// Production — full staking and reputation enforcement
var productionConfig = BundlerConfig.CreateProductionConfig(
entryPoint: EntryPointAddresses.Latest,
beneficiary: "0xBeneficiary");

All three return a BundlerConfig you can modify before passing to BundlerService.

Configuration Reference

Every property on BundlerConfig and its default value:

PropertyTypeDefaultDescription
SupportedEntryPointsstring[](required)EntryPoint contract addresses the bundler will accept
BeneficiaryAddressstring(required)EOA that receives bundler fees
MaxBundleSizeint10Maximum UserOperations per bundle
MaxMempoolSizeint1000Maximum UserOperations in the mempool
MinPriorityFeePerGasBigInteger0Minimum priority fee to accept a UserOperation
MaxBundleGasBigInteger15,000,000Gas limit for the entire bundle transaction
AutoBundleIntervalMsint10,000Milliseconds between automatic bundle submissions (0 = disabled)
StrictValidationbooltrueEnforce ERC-4337 validation rules
SimulateValidationbooltrueSimulate validation before accepting a UserOperation
VerificationGasOverheadBigInteger10,000Extra gas added to verification gas estimates
CallGasOverheadBigInteger21,000Extra gas added to call gas estimates
UnsafeModeboolfalseSkip all validations — testing only
EnableERC7562ValidationboolfalseEnable trace-based opcode validation per ERC-7562
MinStakeBigInteger1 ETHMinimum stake for throttled/staked entities
MinUnstakeDelaySecuint86,400Minimum unstake delay (24 hours)
WhitelistedAddressesHashSet<string>emptyAddresses exempt from reputation checks
BlacklistedAddressesHashSet<string>emptyAddresses permanently rejected
ChainIdBigInteger?auto-detectedOverride chain ID (auto-detected from node if null)
EnableBlsAggregationboolfalseEnable BLS signature aggregation

Run as JSON-RPC Server

To expose the bundler as a standard ERC-4337 JSON-RPC endpoint, add the RPC server handlers. This makes the bundler accessible to any ERC-4337 compatible client or wallet.

using Nethereum.AccountAbstraction.Bundler;
using Nethereum.AccountAbstraction.Bundler.RpcServer;
using Nethereum.Web3;
using Nethereum.Web3.Accounts;

var privateKey = "0xYOUR_BUNDLER_PRIVATE_KEY";
var web3 = new Web3(new Account(privateKey), "http://localhost:8545");

var config = BundlerConfig.CreateStandardConfig(
entryPoint: EntryPointAddresses.Latest,
beneficiary: "0xBeneficiary");

var bundlerService = new BundlerService(web3, config);

// Create an RPC server and register all bundler handlers
var rpcServer = new RpcServer();
rpcServer.AddBundlerHandlers(bundlerService);

You can also skip creating the BundlerService yourself and let the extension method create it from the config:

var rpcServer = new RpcServer();
rpcServer.AddBundlerHandlers(web3, config);

The AddBundlerHandlers extension registers all standard and debug RPC handlers listed in the next sections.

ERC-4337 RPC Methods

These are the standard methods that any ERC-4337 client expects from a bundler:

RPC MethodHandlerDescription
eth_sendUserOperationBundlerEthSendUserOperationHandlerSubmit a UserOperation to the mempool
eth_estimateUserOperationGasBundlerEthEstimateUserOperationGasHandlerEstimate gas for a UserOperation
eth_getUserOperationByHashBundlerEthGetUserOperationByHashHandlerLook up a UserOperation by its hash
eth_getUserOperationReceiptBundlerEthGetUserOperationReceiptHandlerGet the receipt for a mined UserOperation
eth_supportedEntryPointsBundlerEthSupportedEntryPointsHandlerList EntryPoint addresses the bundler supports
eth_chainIdBundlerEthChainIdHandlerReturn the chain ID

Debug Methods

Debug methods are useful during development and testing. They give you direct control over bundling and visibility into internal state.

RPC MethodHandlerDescription
debug_bundler_sendBundleNowBundlerDebugSendBundleNowHandlerForce immediate bundle submission
debug_bundler_dumpMempoolBundlerDebugDumpMempoolHandlerReturn all UserOperations in the mempool
debug_bundler_dumpReputationBundlerDebugDumpReputationHandlerReturn reputation scores for all tracked entities
debug_bundler_setReputationBundlerDebugSetReputationHandlerManually set reputation for an address

Validation Modes

The bundler supports several validation modes that you can combine depending on your security requirements.

StrictValidation (default: true) enforces the full ERC-4337 validation rules. When enabled, the bundler checks that UserOperations follow the specification — correct nonce handling, gas limits within bounds, and proper signature format. Disable this for private chains where you control all accounts.

SimulateValidation (default: true) runs the UserOperation through the EntryPoint's simulateValidation before accepting it into the mempool. This catches operations that would revert on-chain, saving gas. Disable only if you trust all submitters.

EnableERC7562Validation (default: false) adds trace-based opcode validation per the ERC-7562 specification. The bundler traces the validation phase and rejects operations that use forbidden opcodes (like ORIGIN, GASPRICE, or storage access outside allowed rules). Enable this for public bundlers where untrusted accounts submit operations.

UnsafeMode (default: false) disables all validation. Every UserOperation is accepted into the mempool without checks. Use this only for local development and testing — never in production.

// Development: skip everything for speed
var devConfig = BundlerConfig.CreateAppChainConfig(entryPoint, beneficiary);
devConfig.UnsafeMode = true;

// Public testnet: full validation with ERC-7562 opcode checks
var testnetConfig = BundlerConfig.CreateStandardConfig(entryPoint, beneficiary);
testnetConfig.EnableERC7562Validation = true;
testnetConfig.SimulateValidation = true;

Reputation and Staking

ERC-4337 includes a reputation system that protects the bundler from denial-of-service attacks. Entities (factories, paymasters, aggregators) that cause UserOperations to fail on-chain have their reputation reduced. Entities with low reputation are throttled or banned.

The bundler tracks reputation automatically. You can control it with these config properties:

  • MinStake — Entities that stake at least this amount (default: 1 ETH) get higher rate limits and can bypass some throttling.
  • MinUnstakeDelaySec — The minimum unstake delay (default: 86,400 seconds / 24 hours) required for a stake to count.
  • WhitelistedAddresses — Addresses in this set are exempt from all reputation checks. Use for your own trusted paymasters and factories.
  • BlacklistedAddresses — Addresses in this set are permanently rejected. No UserOperation referencing them will be accepted.
var config = BundlerConfig.CreateProductionConfig(entryPoint, beneficiary);

// Trust your own paymaster
config.WhitelistedAddresses.Add("0xYourPaymasterAddress");

// Block a known bad factory
config.BlacklistedAddresses.Add("0xMaliciousFactoryAddress");

Common Mistakes

No beneficiary addressBeneficiaryAddress is required. Without it the bundler cannot submit bundles because it has no address to receive fees.

Wrong EntryPoint address — The bundler only accepts UserOperations targeting an address in SupportedEntryPoints. If you pass a v0.6 EntryPoint but your accounts use v0.7, every operation is rejected. Use EntryPointAddresses.Latest for the current version, or explicitly set the version your accounts target.

AutoBundleIntervalMs set to 0 without manual bundling — Setting the interval to 0 disables the auto-bundle timer. UserOperations accumulate in the mempool but are never submitted. Either keep a positive interval or call ExecuteBundleAsync() yourself:

config.AutoBundleIntervalMs = 0; // Timer disabled

// You must trigger bundling manually
var result = await bundlerService.ExecuteBundleAsync();

UnsafeMode in productionUnsafeMode = true skips all validation. Malicious or malformed UserOperations will be bundled and waste gas. Only use this for local development.

Next Steps