Orca Crypto
Menu
Start Here
Learn
Chains
Exchanges
Markets
Tools
Safety
Glossary
More
Our tokens Book a session
Expert · The transaction supply chain

Account abstraction internals: ERC-4337 and EIP-7702

The account becomes code. Here is exactly what runs, in what order, who pays for it, and where it breaks.

Loading live prices
By the Orca Crypto teamUpdated 2026-09-28How we check this15 min readExpert
The short answer

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:

FieldWhat it is for
senderThe smart account the operation acts for
nonceReplay protection: a 192 bit key and a 64 bit sequence in one word
factory, factoryDataDeploy the account on first use; or the 0x7702 flag and initialization data for an EIP-7702 account
callDataWhat the account should execute
callGasLimit, verificationGasLimitSeparate budgets for execution and for validation
preVerificationGasPays the bundler for everything the EntryPoint cannot meter: calldata, the base transaction cost, fixed overhead
maxFeePerGas, maxPriorityFeePerGasEIP-1559 style fee caps
paymaster and its two gas limits, paymasterDataAn optional contract that pays instead of the account
signatureOpaque 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).

nonce = key × 264 + sequenceEach 192 bit key is an independent sequential channel, so an account can run parallel streams of operations.

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.

  1. Verification loop

    For each operation: deploy the sender through its factory if initCode is set; compute the maximum possible cost; call validateUserOp(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 call validatePaymasterUserOp. On a failed validation the reference EntryPoint reverts the whole bundle with a FailedOp error naming the operation, which is why bundlers simulate before sending.

  2. Execution loop

    For each validated operation: call the account with its callData (or through executeUserOp if the callData asks for it), then postOp on 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.

  3. Settlement

    Collected fees go to beneficiary. A 10% penalty applies to unused callGasLimit and paymasterPostOpGasLimit, 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:

validationData = aggregator | (validUntil << 160) | (validAfter << 208)aggregator: 0 for a valid signature, 1 for a failed one, or an aggregator address. validUntil and validAfter are 6 byte timestamps; validUntil 0 means no expiry. From v0.9 either can be a block number, flagged by its top bit.

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:

VersionReleasedWhat changed
v0.62023EntryPoint managed nonces with keys and sequences
v0.7February 2024Packed struct, separate paymaster gas limits, simulation moved out of the deployed contract, unused gas penalty
v0.82025, for PectraNative EIP-7702 support, EIP-712 userOpHash, penalty threshold of 40,000 gas
v0.9November 2025paymasterSignature for parallel signing, block number validity ranges, getCurrentUserOpHash
text
// Canonical EntryPoint singletons (eth-infinitism releases)
v0.6   0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
v0.7   0x0000000071727De22E5E9d8BAf0edAc6f37da032
v0.8   0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108
v0.9   0x433709009B8330FDa32311DF1C2AFA402eD8D009

Each 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:

RuleWhat 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 stakedValidation 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 gasLeaking 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 128One 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 dayCheap sybil entities that invalidate operations in bulk
Reputation per entity: ops seen versus ops included, then throttling and bansA 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.

authority = ecrecover( keccak256( 0x05 ‖ rlp([chain_id, address, nonce]) ), y_parity, r, s )0x05 is the MAGIC domain separator, so an authorization can never be mistaken for a transaction signature.
code(authority) = 0xef0100 ‖ address23 bytes. 0xef cannot begin deployed code (EIP-3541), so a designator can never be confused with a real contract.

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_id 0 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:

solidity
// 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 initialize function, 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’s senderCreator and 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.
An authorization is a key handover
A single 7702 authorization hands the delegate complete control of the account, including every token and approval it holds, and with 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:

AspectERC-7579ERC-6900
PhilosophyMinimal interfaces, maximum room for account vendorsStronger permission model with explicit declarations
Module typesValidator (1), executor (2), fallback handler (3), hook (4); one module can be severalValidation, validation hook, execution and execution hook modules
Executionexecute(bytes32 mode, bytes), where the mode packs call type (single, batch, staticcall, delegatecall) and exec typeExecution functions installed per selector, each tied to the validation functions allowed to authorize it
PermissioningLeft mostly to the account; hooks are optionalValidation 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

SystemWhere validation livesWho can pay
zkSync EraIn the protocol: accounts implement validateTransaction and executeTransaction, and the protocol calls themThe account through payForTransaction, or a paymaster after prepareForPaymaster
StarknetEvery account is a contract with __validate__ and __execute__, called in that order by the sequencer, with limits on what validation may doThe account itself
SolanaNot programmable: the runtime checks Ed25519 signatures. Programmable wallets are programs holding assets in PDAsAny signer can be the fee payer, so sponsorship is native
Ethereum, proposedEIP-8141 frame transactions (type 0x06): a transaction is a sequence of frames that validate, approve gas payment and executeDefined 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.

Kept on this device only. Nothing is sent anywhere.

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.

Share this page

Was this page useful?