When we shipped HyperPNL's gasless transaction layer, the part nobody talks about — the bundler — turned out to be the actual hard problem. Smart contracts are just specs; bundlers are where reality lives.
What a bundler actually does
In ERC-4337, a UserOperation is a struct that looks like a transaction but isn't one. It needs to be:
- Validated off-chain against the entry point's view of state.
- Bundled with other UserOps from the public mempool.
- Submitted on-chain inside an
EntryPoint.handleOps()call.
The mistake I made early was thinking of the bundler as a thin wrapper. It isn't — it's a full mempool, a fee oracle, and a state simulator stitched together.
The gas-estimation trap
Calling eth_estimateGas against handleOps returns numbers that are almost always wrong because validation runs before execution and reverts get bubbled. We replaced naive estimateGas with our own simulator that:
- Calls
simulateValidationfirst to boundverificationGasLimit. - Replays
callGasLimitagainst an in-memory fork at the post-validation state. - Adds a safety margin proportional to the calldata gas, not a flat 20%.
That single change cut UserOp revert rates from ~6% to under 0.4% in production.
Nonce management
EOA nonces are simple. Smart-account nonces are 192-bit keyed sequences — key << 64 | seq. Lots of bundlers (including ours, initially) treat them as a single counter and break when an account uses parallel keys for batched txs.