ERC-4337: Account Abstraction Using Alt Mempool
ERC-4337 is an account abstraction proposal which completely avoids the need for consensus-layer protocol changes.
It means it is does not requires hard or even a soft fork.
Instead of adding new protocol features and changing the bottom-layer transaction type, this proposal instead introduces a higher-layer pseudo-transaction object called a UserOperation.
- Users send
UserOperationobjects into a separate mempool. - A special class of actor called
bundlerspackage up a set of these objects into a transaction making ahandleOpscall to a special contract, - and that transaction then gets included in a block.
An account abstraction proposal which completely avoids consensus-layer protocol changes, instead relying on higher-layer infrastructure.
Schema from infinitism - ERC 4337: account abstraction without Ethereum protocol changes
Warning: this article is still in draft state and its content is still mainly taken direcly from the ERC with a few edits of my own. Its content should become more personal later.

[TOC]
Specification
EIP Required
EIP-712: typed structured data hashing and signing
A procedure for hashing and signing of typed structured data as opposed to just bytestrings.
EIP-7562: Account Abstraction Validation Scope Rules
A set of limitations on validation EVM code to protect Account Abstraction nodes from denial-of-service attacks through unpaid computation.
This EIP describes the rules Account Abstraction protocols should follow, during the validation phase of Account Abstraction transactions, such as ERC-4337 UserOperation or RIP-7560 (Native Account Abstraction), which are enforced off-chain by a block builder or a standalone bundler, and the rationale behind each one of them.
Definitions
| Term | Type | Description |
|---|---|---|
| UserOperation | struct | A structure similar to a transaction, but with additional fields. Contains sender’s signature and more. |
| Sender | Smart Contract | The Smart Contract Account sending the UserOperation. |
| EntryPoint | Smart Contract | A contract that executes bundles of UserOperations. Bundlers must whitelist supported EntryPoints. |
| Bundler | node | A node that handles UserOperations, creates valid transactions, and adds them to blocks. Can work with block-building infrastructure. |
| Paymaster | Smart Contract | A contract that agrees to pay for the transaction costs, instead of the sender. |
| Factory | Smart Contract | A contract that deploys a new sender contract when necessary. |
| Aggregator | Smart Contract | An “authorizer contract” that allows multiple UserOperations to share a single validation. |
| Canonical UserOperation Mempool | Decentralized network | A decentralized network where valid UserOperations conforming to ERC-7562 are exchanged. |
| Alternative UserOperation Mempool | Decentralized network | A P2P network with its own rules for validating UserOperations, different from ERC-7562. |
| Deposit | Currency | Ether (or L2 native currency) transferred by Sender or Paymaster to EntryPoint to cover future gas costs. |
UserOperation
To avoid Ethereum consensus changes, we do not attempt to create new transaction types for account-abstracted transactions. Instead, users package up the action they want their Smart Contract Account to take in a struct named UserOperation
Users send UserOperation objects to a dedicated UserOperation mempool.
| Field | Type | Description |
|---|---|---|
sender |
address |
The Account making the UserOperation |
nonce |
uint256 |
Anti-replay parameter (see “Semi-abstracted Nonce Support” ) |
factory |
address |
Account Factory for new Accounts OR 0x7702 flag for EIP-7702 Accounts, otherwise address(0) |
factoryData |
bytes |
data for the Account Factory if factory is provided OR EIP-7702 initialization data, or empty array |
callData |
bytes |
The data to pass to the sender during the main execution call |
callGasLimit |
uint256 |
The amount of gas to allocate the main execution call |
verificationGasLimit |
uint256 |
The amount of gas to allocate for the verification step |
preVerificationGas |
uint256 |
Extra gas to pay the bundler |
maxFeePerGas |
uint256 |
Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) |
maxPriorityFeePerGas |
uint256 |
Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) |
paymaster |
address |
Address of paymaster contract, (or empty, if the sender pays for gas by itself) |
paymasterVerificationGasLimit |
uint256 |
The amount of gas to allocate for the paymaster validation code (only if paymaster exists) |
paymasterPostOpGasLimit |
uint256 |
The amount of gas to allocate for the paymaster post-operation code (only if paymaster exists) |
paymasterData |
bytes |
Data for paymaster (only if paymaster exists) |
signature |
bytes |
Data passed into the sender to verify authorization |
Signature - Security
To prevent replay attacks, either cross-chain or with multiple EntryPoint contract versions, the signature MUST depend on chainid and the EntryPoint address.
Entry-point
The core interface of the EntryPoint contract is as follows:
function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary);
The beneficiary is the address that will be paid with all the gas fees collected during the execution of the bundle.
Smart account
Core interface (IAccount)
The core interface required for the Smart Contract Account to have is:
interface IAccount {
function validateUserOp
(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
external returns (uint256 validationData);
}
The userOpHash is a hash over the userOp (except signature), entryPoint and chainId.
Extension (IAccountExecute)
The Smart Contract Account MAY implement the interface IAccountExecute
interface IAccountExecute {
function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
}
This method will be called by the EntryPoint with the current UserOperation, instead of executing the callData itself directly on the sender.
Requirement
| Requirement | MUST | SHOULD |
|---|---|---|
| Validate the caller | The Smart Contract Account must validate the caller is a trusted EntryPoint. | |
| Validate the signature | The Smart Contract Account must validate that the signature is a valid signature of the userOpHash. | |
| Handle signature mismatch (userOpHash) | The Smart Contract Account should return SIG_VALIDATION_FAILED (1) without reverting on signature mismatch. | |
| Handle error on signature mismatch | Any other error must revert. | |
| Early return behavior | The Smart Contract Account should not return early when returning SIG_VALIDATION_FAILED (1). Instead, it should complete the normal flow to enable performing a gas estimation for the validation function. | |
| Pay the EntryPoint | The Smart Contract Account must pay the EntryPoint at least the missingAccountFunds. | |
| Pay more than the minimum | The sender may pay more than the minimum to cover future transactions and can call withdrawTo to retrieve it later. |
|
| Return value | The return value must be packed with the aggregator/authorizer, validUntil, and validAfter timestamps. | |
| Aggregator/authorizer value | The aggregator/authorizer value must be 0 for valid signature, 1 for signature failure, or an address of an aggregator/authorizer contract (as per ERC-7766). | |
| validUntil timestamp | The validUntil timestamp must be a 6-byte value, or zero for “infinite”. The UserOperation is valid only up to this time. | |
| validAfter timestamp | The validAfter timestamp must be a 6-byte value. The UserOperation is valid only after this time. |
Semi-abstracted Nonce Support
Ethereum protocol VS ERC-4337
Ethereum protocol
In Ethereum protocol, the sequential transaction nonce value has several purposes:
- It is used as a replay protection method
- To determine the valid order of transaction being included in blocks.
- It also contributes to the transaction hash uniqueness, as a transaction by the same sender with the same nonce may not be included in the chain twice.
ERC-4337
Requiring a single sequential nonce value is limiting to the senders’ ability to define their custom logic with regard to transaction ordering and replay protection.
Instead of sequential nonce , this ERC implement a nonce mechanism that uses a single uint256 nonce value in the UserOperation, but treats it as two values:
- 192-bit “key”
- 64-bit “sequence”
Nonce in the EntryPoint
Representation
These values are represented on-chain in the EntryPoint contract. The EIP defines the following method in the EntryPoint interface to expose these values:
function getNonce(address sender, uint192 key) external view returns (uint256 nonce);
Nonce verification
For each key the sequence is validated by the EntryPoint for each UserOperation.
- If the nonce validation fails the
UserOperationis considered invalid and the bundle is reverted. - The
sequencevalue is incremented sequentially and monotonically for thesenderfor eachUserOperation. - A new
keycan be introduced with an arbitrary value at any point, with itssequencestarting at0.
This approach maintains the guarantee of UserOperation hash uniqueness on-chain on the protocol level while allowing Accounts to implement any custom logic they may need operating on a 192-bit “key” field, while fitting the 32 byte word.
Bundler: Reading and validating the nonce
When preparing the UserOperation bundlers may make a view call to this method to determine a valid value for the nonce field.
Bundler’s validation of a UserOperation SHOULD start with getNonce to ensure the transaction has a valid nonce field.
Multiple UserOperations
If the bundler is willing to accept multiple UserOperations by the same sender into their mempool, this bundler is supposed to track the key and sequence pair of the UserOperations already added in the mempool.
Entry-point
Required EntryPoint contract functionality
The EntryPoint method is handleOps, which handles an array of UserOperations
The EntryPoint’s handleOps function must perform the following steps (we first describe the simpler non-paymaster case).
It must make two loops: the verification loop and the execution loop.
Schema from ERC-4337

Verification loop
In the verification loop, the handleOps call must perform the following steps for each UserOperation
Smart Contract Account creation
- Create the
senderSmart Contract Account if it does not yet exist, using theinitcodeprovided in theUserOperation.- If the
factoryaddress is “0x7702”, then the sender MUST be an EOA with an EIP-7702 authorization designation. TheEntryPointvalidates the authorized address matches the one specified in theUserOperationsignature (see [Support for EIP-7702] authorizations). - If the
senderdoes not exist, and theinitcodeis empty, or does not deploy a contract at the “sender” address, the call must fail.
- If the
Fee computation
- calculate the maximum possible fee the
senderneeds to pay based on validation and call gas limits, and current gas values. - calculate the fee the
sendermust add to its “deposit” in theEntryPoint
CallvalidateUserOp
- Call
validateUserOpon thesendercontract, passing in theUserOperation, its hash and the required fee.- The Smart Contract Account SHOULD verify the
UserOperation’s signature, and pay the fee if thesenderconsiders theUserOperationvalid. - If any
validateUserOpcall fails,handleOpsmust skip execution of at least thatUserOperation, and may revert entirely.
- The Smart Contract Account SHOULD verify the
Cost validation
Validate the account’s deposit in the EntryPoint is high enough to cover the max possible cost (cover the already-done verification and max execution gas)
Execution loop
In the execution loop, the handleOps call must perform the following steps for each UserOperation:
-
Call the account with the
UserOperation’s calldata. It’s up to the account to choose how to parse the calldata; an expected workflow is for the account to have anexecutefunction that parses the remaining calldata as a series of one or more calls that the account should make. -
If the calldata starts with the methodsig
IAccountExecute.executeUserOp, then theEntryPointmust build a calldata by encodingexecuteUserOp(userOp,userOpHash)and call the account using that calldata. -
After the call, refund the account’s deposit with the excess gas cost that was pre-charged.
Penalty
A penalty of
10%(UNUSED_GAS_PENALTY_PERCENT) is applied on the amounts ofcallGasLimitandpaymasterPostOpGasLimitgas that remains unused. This penalty is only applied if the amount of the remaining unused gas is greater than or equal40000(PENALTY_GAS_THRESHOLD). This penalty is necessary to prevent theUserOperationsfrom reserving large parts of the gas space in the bundle but leaving it unused and preventing the bundler from including otherUserOperations. -
After the execution of all calls, pay the collected fees from all
UserOperationsto thebeneficiaryaddress provided by the bundler.
Paymaster (Extention)
The EntryPoint logic supports also paymasters that can sponsor transactions for other users. This feature can be used to allow application developers to subsidize fees for their users, allow users to pay fees with [ERC-20] tokens and many other use cases.
When the paymasterAndData field in the UserOperation is not empty, the EntryPoint implements a different flow for that UserOperation:
function validatePaymasterUserOp
(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
external returns (bytes memory context, uint256 validationData);
function postOp
(PostOpMode mode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)
external;
enum PostOpMode {
opSucceeded, // UserOperation succeeded
opReverted // UserOperation reverted. paymaster still has to pay for gas.
}

Entrypoint - Support
The EntryPoint must implement the following API to let entities like paymasters have a stake, and thus have more flexibility in their storage access (see reputation, throttling and banning section for details.)
// add a stake to the calling entity
function addStake(uint32 _unstakeDelaySec) external payable;
// unlock the stake (must wait unstakeDelay before can withdraw)
function unlockStake() external;
// withdraw the unlocked stake
function withdrawStake(address payable withdrawAddress) external;
The paymaster must also have a deposit, which the EntryPoint will charge UserOperation costs from. The deposit (for paying gas fees) is separate from the stake (which is locked).
The EntryPoint must implement the following interface to allow Paymasters (and optionally Accounts) to manage their deposit:
// return the deposit of an account
function balanceOf(address account) public view returns (uint256);
// add to the deposit of the given account
function depositTo(address account) public payable;
// add to the deposit of the calling account
receive() external payable;
// withdraw from the deposit of the current account
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external;
Bundler behavior upon receiving a UserOperation

Similar to an Ethereum transaction, the offchain flow of a UserOperation can be described as follows:
- Client sends a
UserOperationto the bundler through an RPC calleth_sendUserOperation. - Before including the
UserOperationin the mempool, the bundler runs the first validation of the newly received UserOperation.- If the
UserOperationfails validation, the bundler drops it and returns an error in response toeth_sendUserOperation.
- If the
- Later, once building a bundle, the bundler takes
UserOperationsfrom the mempool and runs the second validation of a singleUserOperationon each of them. I- If it succeeds, it is scheduled for inclusion in the next bundle, and dropped otherwise.
- Before submitting the new bundle onchain, the bundler performs the third validation of the entire
UserOperationsbundle.- If any of the
UserOperationsfail validation, the bundler drops them, and updates their reputation, as described in ERC-7562 in detail.
- If any of the
When a bundler receives a UserOperation, it must first run some basic sanity checks, namely that:
- Either the
senderis an existing contract, or theinitCodeis not empty (but not both) - If
initCodeis not empty, parse its first 20 bytes as a factory address or an EIP-7702 flag. Record whether the factory is staked, in case the later simulation indicates that it needs to be. If the factory accesses the global state, it must be staked - see reputation, throttling and banning section for details. - The
verificationGasLimitandpaymasterVerificationGasLimitsare lower thanMAX_VERIFICATION_GAS(500000) and thepreVerificationGasis high enough to pay for the calldata gas cost of serializing theUserOperationplusPRE_VERIFICATION_OVERHEAD_GAS(50000). - The
paymasterAndDatais either empty, or starts with the paymaster address, which is a contract that (i) currently has nonempty code on chain, (ii) has a sufficient deposit to pay for the UserOperation, and (iii) is not currently banned.- During simulation, the paymaster’s stake is also checked, depending on its storage usage - see reputation, throttling and banning section for details.
- The
callGasLimitis at least the cost of aCALLwith non-zero value. - The
maxFeePerGasandmaxPriorityFeePerGasare above a configurable minimum value that the bundler is willing to accept. At the minimum, they are sufficiently high to be included with the upcomingblock.basefee. - The
senderdoesn’t have anotherUserOperationalready present in the mempool (or it replaces an existing entry with the same sender and nonce, with a highermaxPriorityFeePerGasand an equally increasedmaxFeePerGas).- Only one
UserOperationper sender may be included in a single bundle. A sender is exempt from this rule and may have multipleUserOperationsin the mempool and in a bundle if it is staked (see reputation, throttling and banning section below).
- Only one
Security Considerations
The EntryPoint contract will need to be audited and formally verified, because it will serve as a central trust point for all [ERC-4337]. In total, this architecture reduces auditing and formal verification load for the ecosystem, because the amount of work that individual accounts have to do becomes much smaller: they need only
- verify the
validateUserOpfunction and its “check signature and pay fees” logic) - check that other functions are
msg.sender == ENTRY_POINTgated (perhaps also allowingmsg.sender == self),
Verification would need to cover two primary claims:
- Safety against arbitrary hijacking: The
EntryPointonly calls to thesenderwithuserOp.calldataand only ifvalidateUserOpto that specificsenderhas passed. - Safety against fee draining: If the
EntryPointcallsvalidateUserOpand passes, it also must make the generic call with calldata equal touserOp.calldata
Factory contracts
All factory contracts MUST check that all calls to the createAccount() function originate from the entryPoint.senderCreator() address.
Paymasters contracts
All paymaster contracts MUST check that all calls to the validatePaymasterUserOp() and postOp() functions originate from the EntryPoint.
Aggregator contracts
All paymaster contracts MUST check that all calls to the validateSignatures() function originates from the EntryPoint.
EIP-7702 delegated Smart Contract Accounts
All EIP-7702 delegated Smart Contract Account implementations MUST check that all calls to the initialization function originate from the entryPoint.senderCreator() address.
There is no way for the EntryPoint contract to know whether an EIP-7702 account has been initialized or not, and therefore the EIP-7702 account initialization code, can be called multiple times through EntryPoint.
- The Account code SHOULD only allow calling it once
- the Wallet Application SHOULD NOT pass the
initCoderepeatedly.
Smart Contract Accounts
Storage layout collisions
It is expected that most of ERC-4337 Smart Contract Account will be upgradeable, either via on-chain delegate proxy contracts or via EIP-7702.
When changing the underlying implementation, all Accounts MUST ensure that there are no conflicts in the storage layout of the two contracts.
One common approach to this problem is often referred to as “diamond storage” and is fully described in ERC-7201: Namespaced Storage Layout..
Transient Storage
Contracts using the EIP-1153 transient storage MUST take into account that ERC-4337 allows multiple UserOperations from different unrelated sender addresses to be included in the same underlying transaction. The transient storage MUST be cleaned up manually if contains any sensitive information or is used for access control.
FAQ
Who can make wallet execute actions
The validateUserOp and execution functions are expected to be gated with require(msg.sender == ENTRY_POINT), so only the trusted entry point can cause a wallet to perform any actions or pay fees.
See medium - ERC 4337: account abstraction without Ethereum protocol changes