What happens to a UserOp within a Bundler?
Introduction
Following our previous post on UserOperation CallData, several readers asked, "Why do we need a bundler when I can just relay the UserOp directly to the entrypoint from an Externally Owned Account (EOA)?" To answer this, let's break down the checks a bundler performs upon receiving a UserOperation through its RPC URL.
Special thanks to the Ethereum Foundation and ERC4337 community for their support. JiffyScan and these articles wouldn't be possible without their help.
Overview
A bundler accepts signed calldata representing a UserOperation and submits it to the blockchain (triggering entrypoint.handleOps()
), possibly alongside other independent UserOperations. This process can be divided into two phases:
Pre-Bundling Phase: Validating a UserOperation before adding it to the pool of operations ready to create a bundle.
Bundling Phase: Submitting a selected subset of UserOperations to be added to the blockchain.
Note: We’ve used Eth-infinitism’s bundler as a reference for this post.
Pre-Bundling Phase
When a UserOperation is received by a bundler’s RPC endpoint, several checks are performed before it can be added to the set of valid UserOperations that are ready to be submitted to the blockchain. These checks are conducted in the following stages:
Validate Input Parameters: Ensure all required fields in the UserOperation are valid.
Simulate UserOperation and Validate the Trace: Check the potential impact of the operation on the blockchain’s state and confirm it meets the necessary restrictions.
Perform Mempool Level Checks: Verify the operation's readiness for inclusion in a bundle.
Pre-Bundling Phase
When a UserOperation is received by the bundler's RPC endpoint, it undergoes several checks before being added to the set of valid UserOperations ready for submission to the blockchain. These checks occur in the following stages:
1. Validate Input Parameters
The bundler verifies the presence and validity of all the fields of a UserOperation at an individual level. Key checks include:
Ensuring the
entrypoint
parameter in the RPC call is not null and is supported by the bundler.Verifying the UserOperation is not null and that all fields have valid hexadecimal values, such as:
Sender
nonce
initCode
callData
paymasterAndData
signature
(if required)
Checking that all gas parameters are present and have valid hexadecimal values, including:
preVerificationGas
verificationGasLimit
callGasLimit
maxFeePerGas
maxPriorityFeePerGas
Ensuring the
paymasterAndData
field is either0x
or has a length of at least 42 bytes (to store at least an address).Ensuring the
initCode
field is either0x
or has a length of at least 42 bytes.Confirming that
preVerificationGas
meets the minimum required by the bundler to process the request.
You can refer to the code for validating input parameters [here](insert link). It's worth noting that steps 1-3 are redundantly performed in the v1.6 of Eth-infinitism's bundler implementation.
2. Simulate UserOperation and Validate the Trace
Next, the bundler checks the potential impact of the operation on the blockchain's state and ensures it adheres to the standard's restrictions.
This is done using debug_traceCall
provided by certain node providers, with geth
being the most prominent. It allows you to simulate a transaction on the latest blockchain state and gather information about the addresses involved, opcodes used, memory accessed, value transferred, etc., according to your needs.
Note: A tracer is a JavaScript program passed in the
debug_traceCall
API call, specifying the information you want to collect. You can see the script used in the current implementation [here](insert link).
Steps in this stage include:
Simulating the UserOperation and generating the validation result and trace output by invoking the
entrypoint
'ssimulateValidation()
method.Ensuring the return value is not
REVERT
as it would be invalid.Performing additional checks to determine if the returned data is a genuine result or an error and handling it accordingly.
The bundler then parses the trace results to ensure no security breaches, ensuring:
There is at least one call from the
entrypoint
.No illegal calls are made to the
entrypoint
from external contracts.Calls without specifying a value to external contracts are not made.
No banned opcodes are used.
Only the factory address makes a
CREATE2
call.No entity other than the factory uses the
CREATE2
opcode.Unstaked entities do not access forbidden storage slots of external contracts/accounts.
All referenced contracts have code deployed unless it is the sender of the UserOperation.
Finally, the validity of additional meta-parameters is checked, ensuring:
Both the UserOperation and Paymaster signatures are valid.
validAfter
is in the past.validUntil
is either null or in the future.validUntil
is sufficiently far in the future to not expire while being added to the chain.
These checks complete the bulk of the validations needed to accept a UserOperation.
3. Mempool Level Checks
After initial validation, the bundler adds the UserOperation to the valid pool for potential submission to the blockchain during the next round.
The checks at this stage depend on whether a previous UserOperation with the same nonce and sender already exists:
If a pending UserOperation from the same sender with the same nonce value is found, the bundler checks if the new operation has sufficient incentive to replace the previous one. In the reference implementation, the old UserOperation is replaced if the
maxPriorityFeePerGas
andmaxFeePerGas
values are 1.1 times higher in the new UserOperation.If no existing UserOperation with the same sender and nonce value is found, the reputation status of the account, paymaster, factory, and aggregator is checked first. The entities should not be banned, throttled, or exceed the maximum allowed UserOperations from an unstaked entity.
The sender address should not be the factory or paymaster for another UserOperation in the Mempool.
The factory or paymaster addresses should not be the sender for another UserOperation in the Mempool.
Bundling UserOperations in Mempool
Depending on the bundler, certain conditions will trigger the bundler to package the pending UserOps from the pool in a bundle and add them to the chain. The trigger condition could be as simple as submitting every UserOp to the chain the moment it is added to the mempool to wait for a certain period or cumulative gasFee
reward from UserOps.
Due to the delay, UserOps could become invalid, thus being dropped or never being added to a bundle.
In this section, we break down what happens when a bundler decides to create a new bundle from the pool of pending UserOps.
Checks while Creating a Bundle
The process to select UserOperations into the next bundle is as follows:
Sort the pending UserOps in decreasing order of incentive. In the reference implementation, UserOperations are sorted based on the
maxPriorityFeePerGas
value.Each UserOperation in the sorted list is iterated over. While iterating, the following checks are made:
If the
paymaster
orfactory
is banned, the UserOperation is dropped from the mempoolIf the
paymaster
orfactory
has been throttled to limit the number of UserOperations from them, the UserOperation is skipped for the current bundleIf there’s already a UserOperation from the same
sender
added to the bundle in the previous iterations, the UserOperation is skipped for the current bundle.The UserOperation is again validated by simulating it. If the simulation fails now, the UserOperation is dropped from the pool
If the UserOperation accesses the storage of a
sender
from the UserOperations already included from previous iterations, it is skippedIf the cumulative gas from all the UserOps accepted so far is less than the maximum Gas the bundle can have, skips the current UserOperation and stops iterating over the remaining ones.
Skips the UserOperation if the
paymaster
balance is not sufficient to sponsor all the UserOperations if the current is also added to the bundle
The UserOps selected after the iteration process ends are added to a bundle and sent as a transaction to the entrypoint to be added to the chain and later removed from the mempool.
Next Steps
Try running a bundler yourself. You can try the Eth-Infinitism Bundler to get started.
Remember you can use the JiffyLabs interface to share your UserOps with your friends and community.
Also, if you need real-time data on 4337, do check out our leading API.
We will be releasing more deep dives, walkthroughs, and quickstart tutorials for 4337 regularly in the coming weeks. Follow us on Twitter to stay updated.
Subscribe to my newsletter
Read articles from JiffyLabs directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by