Expert level
Account abstraction moves the question of whether a transaction is valid out of the protocol and into code the account controls. ERC-4337 does it without a fork: users sign UserOperations, bundlers package them into one transaction to a singleton EntryPoint contract, and strict validation rules keep bundlers safe from denial of service. EIP-7702, live since Pectra in May 2025, lets an ordinary EOA point at contract code with a 23 byte delegation designator, so existing addresses can become smart accounts.
What the protocol hard codes, and why it is hard to undo
An Ethereum EOA transaction is valid if a handful of checks pass: ecrecover on the signature yields an address, the nonce matches, the balance covers the maximum fee, the gas limit covers the intrinsic cost, and the chain id is right. None of those checks runs any contract code or reads any contract storage. That is what lets every node validate and propagate transactions cheaply, and it is why an EOA is stuck with one secp256k1 key, one sequential nonce and one way to pay for gas.
Account abstraction lets the account define validity in code: a passkey instead of a secp256k1 key, a multisig, a spending limit, a session key, someone else paying the fee. The difficulty is not writing that code. It is that validity now depends on state, and state can change between the moment a node checks an operation and the moment it lands. A node that accepts an operation which later turns out not to pay has done work for free. Every design choice in ERC-4337 follows from that one denial of service problem. For the user level view, see smart accounts, passkeys and gasless transactions.
The UserOperation
ERC-4337 avoids any consensus change by introducing a pseudo transaction, the UserOperation, that lives in its own mempool. Its fields, as the current specification defines them:
| Field | What it is for |
|---|---|
sender | The smart account the operation acts for |
nonce | Replay protection: a 192 bit key and a 64 bit sequence in one word |
factory, factoryData | Deploy the account on first use; or the 0x7702 flag and initialization data for an EIP-7702 account |
callData | What the account should execute |
callGasLimit, verificationGasLimit | Separate budgets for execution and for validation |
preVerificationGas | Pays the bundler for everything the EntryPoint cannot meter: calldata, the base transaction cost, fixed overhead |
maxFeePerGas, maxPriorityFeePerGas | EIP-1559 style fee caps |
paymaster and its two gas limits, paymasterData | An optional contract that pays instead of the account |
signature | Opaque to the protocol; only the account knows how to read it |
On chain the struct is packed: initCode is factory plus factoryData, accountGasLimits
and gasFees each pack two 16 byte values into one word, and paymasterAndData
concatenates the paymaster fields. The signature must commit to the chain id and the EntryPoint address, or an
operation could be replayed on another chain or another EntryPoint version. Factories deploy with CREATE2, so an
account’s address is known and fundable before it exists (see the EVM
under the hood).
The EntryPoint: two loops
A bundler calls handleOps(PackedUserOperation[] ops, address beneficiary) on the EntryPoint
singleton. It runs two loops, and the split between them is the core of the design.
Verification loop
For each operation: deploy the sender through its factory if
initCodeis set; compute the maximum possible cost; callvalidateUserOp(op, userOpHash, missingAccountFunds)on the account, which must check the signature and pay what its EntryPoint deposit lacks; if there is a paymaster, check its deposit and callvalidatePaymasterUserOp. On a failed validation the reference EntryPoint reverts the whole bundle with aFailedOperror naming the operation, which is why bundlers simulate before sending.Execution loop
For each validated operation: call the account with its
callData(or throughexecuteUserOpif the callData asks for it), thenpostOpon the paymaster if it asked for one, then refund unused prepaid gas. Execution can revert without affecting fee payment: the fee was secured in the first loop.Settlement
Collected fees go to
beneficiary. A 10% penalty applies to unusedcallGasLimitandpaymasterPostOpGasLimit, but only when at least 40,000 gas is left unused, so an operation cannot reserve a large share of the bundle and waste it.
Validation returns a single word that packs three values:
Returning 1 rather than reverting on a bad signature is deliberate: the specification asks accounts to run the full validation path anyway so bundlers can estimate its gas. Four EntryPoint versions matter in practice:
| Version | Released | What changed |
|---|---|---|
| v0.6 | 2023 | EntryPoint managed nonces with keys and sequences |
| v0.7 | February 2024 | Packed struct, separate paymaster gas limits, simulation moved out of the deployed contract, unused gas penalty |
| v0.8 | 2025, for Pectra | Native EIP-7702 support, EIP-712 userOpHash, penalty threshold of 40,000 gas |
| v0.9 | November 2025 | paymasterSignature for parallel signing, block number validity ranges, getCurrentUserOpHash |
// Canonical EntryPoint singletons (eth-infinitism releases)
v0.6 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
v0.7 0x0000000071727De22E5E9d8BAf0edAc6f37da032
v0.8 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108
v0.9 0x433709009B8330FDa32311DF1C2AFA402eD8D009Each version is a separate contract with separate deposits, and accounts must trust exactly the EntryPoint they were built for. Upgrading means changing which EntryPoint the account accepts calls from.
Validation rules: why bundlers can say yes
A bundler simulates validation offchain before accepting an operation. That is only meaningful if validation gives the same answer on chain a few blocks later, and if one operation cannot silently invalidate thousands of others. ERC-7562 (in Review) defines the rules for the shared, canonical mempool:
| Rule | What it prevents |
|---|---|
| Blocked opcodes during validation: ORIGIN, GASPRICE, BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, BASEFEE, BLOBHASH, BLOBBASEFEE, INVALID, SELFDESTRUCT, CREATE outside deployment, and BALANCE and SELFBALANCE unless the entity is staked | Validation that passes in simulation and fails on chain because the environment changed |
| GAS only when immediately followed by a CALL; no revert on out of gas | Leaking the gas limit or call depth, which lets code detect simulation |
| Storage limited to the account’s own, plus slots associated with it elsewhere: the slot holds the address, or is keccak(address ‖ x) + n for n up to 128 | One operation invalidating another sender’s operation. A token balance mapping keyed by the account qualifies, which is what makes token paymasters possible |
| Paymasters, factories and aggregators that touch shared storage must be staked: a minimum stake set per chain and an unstake delay of at least one day | Cheap sybil entities that invalidate operations in bulk |
| Reputation per entity: ops seen versus ops included, then throttling and bans | A paymaster that accepts operations in simulation and refuses them in the bundle |
Note what the stake is not: it is never slashed. It exists so that being banned has a price. ERC-4337 also has
bundlers reject a verificationGasLimit above 500,000. An operation that needs to break
these rules can still be served, through an alternative mempool whose bundlers agree to different rules,
at the cost of reaching fewer of them.
Bundlers and paymasters
A bundler takes operations over eth_sendUserOperation, validates each on receipt, again when
building a bundle, and then traces the whole bundle before sending it. It fronts the gas for the outer
transaction and is repaid through preVerificationGas and the fees the operations pay, so its business
is the spread. After that it has the same problem as any searcher: its transaction has to land, which in practice
means working through the builder market or being a builder.
A paymaster keeps an ETH deposit in the EntryPoint, which pays for operations, and optionally a stake, which buys
it storage access under the rules above. In validatePaymasterUserOp it decides whether to sponsor and
returns a context; in postOp it learns the actual gas cost and settles. A token paymaster
pre-charges the user the maximum cost in, say, USDC during validation and refunds the difference in
postOp. From v0.9 a paymaster can sign in parallel with the user through an appended
paymasterSignature that does not change the operation hash.
EIP-7702: code for an EOA
EIP-7702, live since the
Pectra upgrade on May 7, 2025, adds transaction type 0x04, the set code transaction. It carries an
authorization_list of tuples [chain_id, address, nonce, y_parity, r, s]. For each tuple,
before execution starts, the protocol recovers the signer and writes a delegation designator into its code.
From then on, any CALL to the EOA, or any transaction sent to it, executes the code at address in
the EOA’s own context: its balance, its storage, its address. EXTCODESIZE on the EOA returns
23, while CODESIZE inside the delegated execution returns the size of the real code. The EOA can still
sign ordinary transactions with its key. Four details matter for builders:
- A tuple with
chain_id0 is valid on every chain where the nonce matches. - Delegations persist until replaced. Delegating to the zero address clears the code.
- Processed delegations are not rolled back if the transaction’s execution reverts.
- The account’s nonce is checked and incremented per tuple, so a used authorization cannot be replayed. An EOA that sponsors its own delegation must sign nonce + 1, because its transaction nonce is incremented before the list is processed.
Gas: each tuple costs 25,000 (PER_EMPTY_ACCOUNT_COST) up front, and 12,500 goes to the refund
counter if the account already existed. Refunds are capped at one fifth of gas used (EIP-3529), so the refund only
arrives in full on larger transactions. A set code transaction that installs one delegation on an existing EOA
and calls an address with no code is charged 46,000 gas, can recover at most 9,200 of the 12,500, and nets 36,800.
It takes 62,500 gas of usage to collect the full refund.
How 7702 and 4337 compose
The two are complementary. EIP-7702 answers which code an existing address runs; ERC-4337 answers
who submits and who pays. On a chain with 7702, a UserOperation can arrive with an eip7702Auth
tuple signed by the sender. The bundler adds all such tuples to the authorization list of its bundle and sends a
type 4 transaction, so the EOA becomes a smart account in the same transaction that uses it. When the
factory field carries the 0x7702 flag, the operation hash covers the current delegate
address, so the operation cannot run against different code than the user signed for. Without the flag it can,
which the specification calls out. A minimal delegate that works both ways:
// A minimal account meant to be the EIP-7702 delegate of an EOA and an
// ERC-4337 account at the same time. Under delegation, address(this) IS the
// EOA: the EOA's own key is the signer and its storage is the account's.
// Illustrative only: no batching, no recovery, not audited.
pragma solidity ^0.8.28;
import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol";
import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract Minimal7702Account is IAccount {
// Immutables live in the delegate's bytecode, not in the EOA's storage,
// so they survive delegation and cannot collide with anything.
address public immutable entryPoint;
constructor(address ep) { entryPoint = ep; }
function validateUserOp(PackedUserOperation calldata op, bytes32 userOpHash, uint256 missingFunds)
external returns (uint256 validationData)
{
require(msg.sender == entryPoint, "not EntryPoint");
// From EntryPoint v0.8, userOpHash is an EIP-712 digest that already
// commits to the chain id and the EntryPoint address.
(address signer, ECDSA.RecoverError err, ) = ECDSA.tryRecover(userOpHash, op.signature);
// Return 1 (SIG_VALIDATION_FAILED) instead of reverting on a bad
// signature, so bundlers can still estimate validation gas.
validationData = (err == ECDSA.RecoverError.NoError && signer == address(this)) ? 0 : 1;
if (missingFunds != 0) {
(bool ok, ) = payable(msg.sender).call{value: missingFunds}("");
(ok); // a shortfall is caught by the EntryPoint's own deposit check
}
}
function execute(address target, uint256 value, bytes calldata data) external {
// The EOA calling itself also passes: msg.sender == address(this).
require(msg.sender == entryPoint || msg.sender == address(this), "unauthorized");
(bool ok, bytes memory ret) = target.call{value: value}(data);
if (!ok) {
assembly { revert(add(ret, 32), mload(ret)) }
}
}
receive() external payable {}
}The hazards
A delegated EOA combines the permanence of a key with the attack surface of a contract. The failure modes are specific and worth knowing by name.
- Storage collisions. Storage belongs to the EOA, not to the code, and survives a change of delegate. Two delegates that both use slot 0 for different things corrupt each other. The EIP recommends namespaced layouts (ERC-7201) and, when in doubt, a delegate that clears storage before migrating.
- Front run initialization. Setting a delegation runs no constructor. If the delegate has an
initializefunction, anyone who sees the delegation first can call it with their own owner. Initialization data must be signed by the EOA key and checked with ecrecover. ERC-4337 adds that initialization calls must come from the EntryPoint’ssenderCreatorand should only be accepted once, since the EntryPoint cannot tell whether a 7702 account was already initialized. - Broken assumptions about tx.origin.
require(tx.origin == msg.sender)no longer proves the caller is a plain key without code, and sandwich protections built on tx.origin can break. - Mempool invalidation. Once an EOA has code, anyone can call it, and that code can move its balance, so one transaction can invalidate pending transactions from another account. The EIP recommends that clients accept only one pending transaction per delegated EOA.
- Sponsor griefing. A sponsored EOA can bump its nonce or sweep its funds and leave a relayer paying for a failed transaction; relayers need bonds or reputation.
chain_id 0 it does so on every chain where the account’s nonce matches. The delegation does not expire. A phishing site that gets an authorization to a malicious delegate has effectively received the private key. Authorization signing belongs at the wallet level, never in a website’s request, and delegates must enforce replay protection, value, gas and target checks inside their own signed payloads. See reading what you sign and phishing and drainers.Modular accounts: ERC-7579 and ERC-6900
Once accounts are code, features like session keys, spending limits and recovery want to be modules that any account can install. Two standards compete to define the interface, and both are still Drafts:
| Aspect | ERC-7579 | ERC-6900 |
|---|---|---|
| Philosophy | Minimal interfaces, maximum room for account vendors | Stronger permission model with explicit declarations |
| Module types | Validator (1), executor (2), fallback handler (3), hook (4); one module can be several | Validation, validation hook, execution and execution hook modules |
| Execution | execute(bytes32 mode, bytes), where the mode packs call type (single, batch, staticcall, delegatecall) and exec type | Execution functions installed per selector, each tied to the validation functions allowed to authorize it |
| Permissioning | Left mostly to the account; hooks are optional | Validation is scoped per selector; a module manifest describes what it installs |
The risk to keep in view is the same for both: a module is code running with the account’s full authority. An executor module can move everything the account holds, so installing one deserves the same scrutiny as signing a 7702 authorization.
Native account abstraction elsewhere, and what comes next
| System | Where validation lives | Who can pay |
|---|---|---|
| zkSync Era | In the protocol: accounts implement validateTransaction and executeTransaction, and the protocol calls them | The account through payForTransaction, or a paymaster after prepareForPaymaster |
| Starknet | Every account is a contract with __validate__ and __execute__, called in that order by the sequencer, with limits on what validation may do | The account itself |
| Solana | Not programmable: the runtime checks Ed25519 signatures. Programmable wallets are programs holding assets in PDAs | Any signer can be the fee payer, so sponsorship is native |
| Ethereum, proposed | EIP-8141 frame transactions (type 0x06): a transaction is a sequence of frames that validate, approve gas payment and execute | Defined by the frames, without a third party relayer |
EIP-8141 is a Draft created in January 2026. In September 2026 the Ethereum Foundation’s protocol team described it as the locked in execution layer headliner for Hegota, the upgrade after Glamsterdam, with a stated motivation that goes beyond convenience: a path to post quantum signature schemes without a fork per scheme, and to retiring secp256k1 keys. It has no date, and its parameters will change. Until then, ERC-4337 plus EIP-7702 is how Ethereum does account abstraction, and the signature scheme an account verifies, including passkeys through the secp256r1 precompile added in Fusaka, is the account’s own choice.
Common questions
What is the difference between ERC-4337 and EIP-7702?
ERC-4337 is an application layer standard: UserOperations, bundlers, paymasters and an EntryPoint contract, with no protocol change. EIP-7702 is a protocol change, live since Pectra in May 2025, that lets an EOA set a delegation to contract code. They compose: 7702 gives an existing address smart account code, and 4337 handles submission, batching and gas sponsorship for it.
Why does ERC-4337 ban opcodes like TIMESTAMP during validation?
Because bundlers accept operations based on an offchain simulation. If validation could read the timestamp, block number or coinbase, it could pass in simulation and fail on chain, and the bundler would pay for the failure. ERC-7562 bans environment opcodes and restricts storage access so that simulation predicts the onchain result.
Can a paymaster lose its stake?
No. ERC-4337 stakes are never slashed. A stake only lets a paymaster, factory or aggregator use more storage during validation and makes sybil attacks on bundler reputation expensive, since a banned entity must lock a new stake with at least a one day unstake delay.
Can an EIP-7702 delegation be undone?
Yes. The EOA can sign a new authorization pointing at a different delegate, or at the zero address, which clears the code. Storage written under the old delegate stays, which is why delegates should use namespaced storage layouts. The private key keeps full control throughout, so a leaked key is still a total loss.
Which EntryPoint version should an account support?
The one its bundlers and paymasters support. Each version is a separate contract at its own address with separate deposits. As of September 2026 the latest release we could confirm was v0.9, from November 2025; v0.8 introduced native EIP-7702 support and EIP-712 operation hashes. An account trusts one EntryPoint address, so moving versions is an upgrade.
Where to go next
Next: Cross chain security: bridge trust models and messaging
Guide 11 of 16 in the Expert level. Your place is kept on this device, so you can pick up exactly here next time.