EIP-7702 Set Code for EOAs

Final · Account abstraction · EIP status Final · created 2024-05-07

Adds a new transaction type that lets an externally owned account (EOA) permanently set, on its own account, a delegation indicator pointing to specific contract code. It can behave like a smart contract wallet while keeping the existing address.

Why it is needed

An EOA is the simplest kind of account: it can only send a single call signed with a private key. As a result, even a common task such as approving a token and then using it must be split into two transactions today, no other account can pay the fee on its behalf, and because the private key is the entire authority, it is not possible to hand out only part of that authority. The source diagnoses that, even though the smart contract wallet ecosystem has advanced greatly, EOAs hold it back and these usability improvements have not spread across applications. This EIP lets an authorization signed by an EOA be carried in a transaction, which writes a delegation indicator into the account's code slot. Afterwards, when that account is called, the code at the address the indicator points to runs in the context of that account. The goal is to make batching, sponsorship, and privilege de-escalation available without changing the address.

What changes

Today

After EIP-7702

Who is affected

In depth

New transaction type and authorization_list

Adds an EIP-2718 transaction whose TransactionType is SET_CODE_TX_TYPE (0x04). The payload is rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, signature_y_parity, signature_r, signature_s]), and the outer fields have the same meaning as in EIP-4844, so a creation transaction with a null destination is not allowed. authorization_list is a list of [chain_id, address, nonce, y_parity, r, s] tuples, and the transaction itself is invalid if its length is 0. This design prevents type 4 from being used as a general-purpose transaction format. Range checks apply to each field, such as chain_id < 2**256, nonce < 2**64, and an address length of 20 bytes. Blob carrying and contract creation are intentionally excluded.

Authorization tuple processing

The authorization_list is processed after the sender nonce is incremented and before execution begins. For each tuple, the client (1) checks that the chain ID is 0 or the current chain, (2) checks that nonce < 2**64 - 1, and (3) recovers the authority with ecrecover over msg = keccak(MAGIC || rlp([chain_id, address, nonce])) (s must be at most secp256k1n/2 per EIP-2). It then (4) adds the authority to accessed_addresses, (5) verifies that the code is empty or already delegated, (6) verifies that the nonce matches, (8) writes 0xef0100 || address into the code slot, and (9) increments the nonce by 1. If address is 0x0, no indicator is written and the code hash is reset to the empty code hash, restoring the account to a pure EOA. If any step fails, only that tuple is skipped and processing moves on to the next one, and if there are several tuples for the same authority, the address of the last valid tuple applies. Delegation indicators that have already been processed are not reverted even if transaction execution fails.

Delegation indicator and per-opcode behavior

The delegation indicator uses the 0xef prefix banned by EIP-3541 to distinguish it from ordinary code. CALL, CALLCODE, DELEGATECALL, STATICCALL, and transactions whose destination is a delegated account all follow the pointer and run the code at address in the context of the authority. During code reads, only CODESIZE and CODECOPY operate on the actual delegated code, while EXTCODESIZE returns 23, the size of the indicator itself. EXTCODEHASH and similar operations also read the indicator as is, because following the delegation would let an account pose as having a particular code hash and break existing contracts that use the code hash as a behavioral guarantee. If an account delegates to a precompile address, the code is treated as empty and the call succeeds without executing anything. If a delegation points to another delegation, the client fetches only the first code and stops tracing the chain.

Gas cost and upfront charging

The intrinsic cost inherits from EIP-2930: 21000 + 16 * non-zero calldata bytes + 4 * zero bytes + 1900 * access list storage keys + 2400 * access list addresses, and PER_EMPTY_ACCOUNT_COST (25000) * the length of the authorization list is added on top. The sender pays for every tuple regardless of its validity or duplication. If the authority account already exists during processing, PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST (12500) is added to the refund counter. This structure of charging the worst-case cost first and refunding later is meant to avoid per-authorization state lookups when computing intrinsic gas and to judge validity quickly with only a sender account lookup. If resolving the delegated code touches a cold account, an additional COLD_ACCOUNT_READ_COST of 2600 gas is charged, and 100 gas if it is warm. PER_AUTH_BASE_COST of 12500 is the 12016 gas estimated from the impact, covering calldata carrying, address recovery, state reads, and code writes, rounded up.

Broken invariants and the tx.origin problem

This EIP breaks three invariants. First, the guarantee that an account balance decreases only through transactions sent by that account disappears. A delegated account's balance can decrease no matter who calls it. Second, the guarantee that an EOA nonce does not increase during execution disappears, because delegated code can invoke creation operations. Third, the guarantee that tx.origin == msg.sender is true only in the top-level frame disappears. Of the three uses of this check, the source analyzes that checking for an EOA is unaffected, atomic sandwich (flash loan style) defenses are broken, and for reentrancy protection it found no real-world cases (the search is non-exhaustive). The EIP also amends the EIP-3607 restriction so that only an EOA whose code is a valid delegation indicator may originate transactions.

Security responsibilities of delegation contracts and wallets

A poorly written delegation contract can give an attacker near-complete control over the signer EOA. The source states that the delegation contract must include a replay protection mechanism (nonce), value, gas, and target/calldata in the account owner's signature. If any is missing, signature reuse, gas exhaustion caused by a malicious sponsor, and arbitrary contract calls become possible. Because delegation sets only code without executing anything, an observer can front-run initialization, and to prevent this the initial setup calldata must be verified with ecrecover as signed by the EOA key. When changing the delegation target, storage collisions must be avoided, and the source gives as an example placing the layout in slots based on a unique identifier, as in ERC-7201. There is also a wallet-side principle. Since no safe interface exists for letting an app propose a delegation signature to a user, wallets must not provide such an interface, and wallets must audit delegation code closely.

Transaction propagation and pool management

Until now, an EOA could only transfer value, so a node could statically judge the validity of each account's pending transactions. After delegation, anyone can call that account's code in the middle of a transaction, so it cannot be known statically whether the balance has been swept away, and one transaction can invalidate other accounts' pending transactions. The source recommends that clients accept only one pending transaction for an EOA that has a delegation indicator. As an alternative, it also mentions an extension in which a transaction lists the accounts to hydrate. The fact that a nonce can increase several times within a single transaction must also be considered in propagation design. A sponsoring relayer can spend gas without being compensated because of authorization invalidation (nonce increase) or asset withdrawal, so a deposit or reputation system should be considered.

Status and recent discussion

This EIP is in Final status. In call records, it was discussed at ACDE #243 on 08.13 and AMA #14 on 08.29, but no decision was made. There was a recent debate over the Binance hack. Account abstraction contributor decentrek said EIP-7702 itself was not broken, but that it is true the batching feature helped the hacker drain 64 tokens including ETH and USDT in a single transaction and increased the damage, and urged improving operational practices and moving to endgame account abstraction such as the EIP-8141 frame transaction. On the other hand, FigoETH, AzianMike, ETH_MAXER and others countered that 7702 is a safe and neutral UX tool and that the problem was the private key leak and Binance's sloppiness.

Original EIP · ← EIPs