EIP-4337: account abstraction without touching the protocol
EOAs are rigid: one key, gas only in ETH, one action at a time. Account abstraction makes accounts programmable — and EIP-4337 pulls it off with zero consensus changes, using a higher-layer transaction, a separate mempool, bundlers, and a singleton EntryPoint.
- EOAs vs contracts
- the mempool
There are two kinds of Ethereum account: a contract (programmable, but it can’t start anything — it only runs when called) and an externally-owned account (an EOA — it can start transactions, but it’s rigid). Everything you do begins from an EOA, and an EOA is stuck with one signing key, gas paid only in ETH, and one action per transaction. People wanted accounts that are programmable — multisig, passkeys, social recovery, batching, letting someone else pay your gas. That’s account abstraction. The obvious way to get it is to change the protocol so accounts can be contracts with their own rules — a hard fork, years of coordination. EIP-4337 asks a sharper question: can we get all of it with zero consensus changes? Let’s derive how.
The problem: the EOA is a straitjacket
Every transaction originates from an EOA, and an EOA can only do exactly what the protocol hard-codes. Its validity rule is fixed: one valid secp256k1 signature over the transaction, the right nonce, and enough ETH. That single rule rules out almost everything people want.
One key means losing it loses everything — no multisig, no hardware passkeys, no social recovery. Gas only in ETH means you must pre-hold ETH to do anything — no sponsor, no paying in USDC. One action per transaction means approve then swap is two signatures, two fees. A contract account could encode any rule you like ( account abstraction Letting an account's validity be defined by its own code — any signature scheme, any gas policy, batching — instead of the protocol's one fixed ECDSA-and-ETH rule. ) — but a contract can’t initiate a transaction; it only executes when something calls it.
→ Step 2: invent a transaction that lives one layer up.
A higher-layer transaction: the UserOperation
Don’t touch what a transaction is. Add a new object one layer above it: a UserOperation A 4337 struct describing what a smart account wants to do — the account, the calldata, gas limits, and a signature the account itself will verify with whatever scheme it likes. Not an Ethereum transaction; it travels in a separate mempool. . It describes what a smart account wants done — the account address, the callData (the action, which may be a batch), gas limits, and a signature the account itself will check however it likes. The user signs it with whatever scheme their account uses — an ECDSA key, a phone’s passkey, a 2-of-3 multisig — and broadcasts it to a separate, alternative mempool just for UserOperations, distinct from the regular transaction mempool.
Nothing about consensus changed here. A UserOperation isn’t a transaction the protocol understands — it’s an application-layer message in a new peer-to-peer network. Which raises the obvious problem:
→ Step 3: have someone wrap it and run it.
Bundlers wrap them; the EntryPoint runs them
Enter the bundler An ordinary node (an EOA) that watches the UserOperation mempool, packs a batch of UserOps into one normal transaction to the EntryPoint, fronts the L1 gas, and is reimbursed from the accounts' deposits. : an ordinary node — anyone can run one — that watches the UserOp mempool, picks a batch, and wraps them into one normal Ethereum transaction. It sends that transaction to a singleton EntryPoint A single audited contract (one per network) that 4337 routes everything through. Its handleOps loops over each UserOp doing validation then execution, and accounts/paymasters trust only it. contract, which loops over each UserOp in two strictly-separated phases:
- Validation — the EntryPoint calls the account’s
validateUserOp(...), which runs whatever custom logic the account defines (verify a passkey signature, a multisig threshold, a session key) and must agree to pay. - Execution — the EntryPoint calls the account with the UserOp’s
callData— the actual action, which can bundle several calls into one.
The bundler fronts the L1 gas and is reimbursed by the EntryPoint out of each account’s prefunded deposit. So why doesn’t it get cheated into including a UserOp that fails to pay? It simulates each UserOp’s validation off-chain first, and the rules forbid validation from reading other accounts’ storage — so a UserOp can’t change its own validity based on state a different transaction might move, and the simulation stays truthful right up to inclusion.
→ Step 4: let a third party pick up the bill.
Paymasters: someone else pays, or pay in any token
Add one optional party: a paymaster contract. A UserOperation can name a paymaster that agrees to cover its gas. That unlocks two things people really wanted. A dapp can sponsor your transactions — you hold no ETH at all and it just works. Or a paymaster can let you pay gas in an ERC-20 like USDC: it pulls the token from you and pays the ETH gas on your behalf. The EntryPoint settles up at the end and reimburses the bundler from the paymaster’s (or the account’s) deposit.