EIP-8141 Frame transactions (Frame Transaction)
Hegota Scheduled for Inclusion · Account abstraction · EIP status Draft · created 2026-01-29
Adds a new transaction type (0x06) that splits a transaction into multiple frames (contract calls) and lets account code decide verification, fee payment, and execution directly. It is a proposal to build account abstraction natively into the protocol.
Why it is needed
Today an Ethereum transaction verifies the sender in only one way, an elliptic curve signature (ECDSA). The account is bound to a private key, so the key cannot be changed, and there is no path to move to signature schemes that are safe against quantum computers. Bundling several operations at once, or having another account pay the fee, requires separate smart account code and an external intermediary (a relayer), which makes the structure more complex and the risk correspondingly higher. This EIP splits a transaction into multiple frames and runs each frame as a contract call, so that account code decides verification, fee approval, and execution. As a result, the protocol natively supports key rotation, switching to quantum-resistant signatures, batched calls, and alternative fee payment. The original text says this realizes the original vision of account abstraction, in which an account becomes an address that holds code.
What changes
Today
- The only way to verify the sender is an ECDSA signature, and because the account is bound to a key, the protocol does not support key rotation
- Bundling several calls into one transaction requires separate smart account code
- For another account to pay the fee, the transaction must go through an external intermediary (a relayer)
- Gas is specified as a single number
After EIP-8141
- The signature scheme is chosen from SECP256K1, P256, and arbitrary (ARBITRARY), and the verification method is decided by the account code
- Several frames go into one transaction, and an atomic batch flag groups them so that all succeed or all revert
- Inside the VERIFY frame, the APPROVE opcode sets the account that pays the fee (payer), so fee sponsorship works without an intermediary
- Each frame specifies its own execution gas limit and state gas limit
Who is affected
- Users: An account with no code can use frame transactions right away with the default code. Setting an expiry time, batching several operations, and transactions where another account pays the fee become possible at the protocol level.
- Wallet & app developers: They must handle the encoding of the new transaction type, signature hash computation, and per-frame receipts. The receipt has no transaction-level status, so interfaces must derive it themselves.
- Client teams and node operators: They must implement APPROVE and the introspection opcodes, two-dimensional gas accounting, and the new public mempool rules. It was decided on 10.12 ACDT #100 to include this in bogota-devnet-1.
- L2: At 08.27 ACDE #244, a condition was attached that spec changes and L2 collaboration continue regardless of SFI status.
In depth
Transaction structure and constants
An EIP-2718 transaction with type FRAME_TX_TYPE = 0x06. The payload is the RLP serialization of [chain_id, nonce, sender, frames, signatures, fees, blob_versioned_hashes]. Each frame is [mode, flags, target, limits, value, data], where limits is [execution, state] and fees is [max_fee, max_priority_fee_per_gas]. Unlike existing transactions, the sender address is written directly in a field and signatures are split out into a signatures list. The number of frames must be at least 1 and at most MAX_FRAMES = 64, and a target of None resolves to tx.sender. value can be nonzero only in SENDER mode. The fixed cost is FRAME_TX_INTRINSIC_COST = 12000, plus FRAME_TX_PER_FRAME_COST = 475 per frame, and ENTRY_POINT, used as the caller of DEFAULT and VERIFY frames, is address(0xaa). The fixed cost plus the sum of frame execution gas cannot exceed EIP-7825's TX_MAX_GAS_LIMIT (16,777,216).
Frame modes and flags
mode allows only three values: 0 DEFAULT, 1 VERIFY, 2 SENDER. For DEFAULT and VERIFY the caller is ENTRY_POINT, and for SENDER the caller is tx.sender. ORIGIN returns this caller at every call depth. A SENDER frame requires that sender_approved has already been set to true. Otherwise the transaction is invalid. A VERIFY frame runs as a STATICCALL and cannot change state, and only APPROVE changes the approval context. If a VERIFY frame reverts or halts with an exception, the whole transaction is invalid. For flags, bits 0-1 are the approval scope and bit 2 is the atomic batch flag (ATOMIC_BATCH_FLAG = 0x4). Bits 3 and above are reserved, so flags must be < 8. The atomic batch flag cannot be used on a VERIFY frame or on the last frame, and the next frame must not be a VERIFY frame either. A frame that belongs to a batch must have an approval scope of 0. The APPROVE_EXECUTION flag is allowed only when target is None or tx.sender.
Signature list and signature hash
Each entry in signatures is [scheme, signer, msg, signature]. scheme is 0x0 ARBITRARY (100 gas), 0x1 SECP256K1 (2800), or 0x2 P256 (6700), and 0x3 and above are reserved. For SECP256K1 and P256, signer is a 20-byte address, and if it is empty it is taken to be tx.sender. For ARBITRARY, signer must be empty. If msg is empty, the signature is over compute_sig_hash(tx), and if it is 32 bytes, the signature is over that digest. A 32-byte value of all zeros is invalid. A SECP256K1 signature is 65 bytes of v || r || s, where v must be 0 or 1 and s must be low-s. P256 is 128 bytes of r || s || qx || qy, and signer must equal keccak256(qx || qy)[12:]. All signatures verified by the protocol must pass before frame execution, and if any one fails the transaction is invalid. The signature hash is computed as keccak(0x06 || rlp(tx)) after replacing the signature bytes of each signature with an empty msg by its length (an integer), so a signer must know the byte length of every empty-msg signature before signing. The raw bytes of protocol-verified signatures cannot be read from the EVM, to allow future aggregation, and only ARBITRARY signatures can be read with SIGDATACOPY.
Execution flow and the APPROVE instruction (0xaa)
At the start of processing, the client checks tx.nonce == state[tx.sender].nonce, verifies all signatures, and then sets payer = None and sender_approved = false. Each frame starts on entry with gas_left = limits.execution and state_gas_left = limits.state, and first subtracts the warm/cold access cost (EIP-2929) of resolved_target. If the target has no code, the default code runs, and if it has an EIP-7702 delegation designator, the delegated code runs. APPROVE takes offset, length, and scope from the stack, ends the current call frame successfully, and changes the approval context. scope is one of APPROVE_PAYMENT (0x1), APPROVE_EXECUTION (0x2), or APPROVE_EXECUTION_AND_PAYMENT (0x3), and must be within the range that frame.flags allows. If ADDRESS != resolved_target, it reverts. APPROVE_EXECUTION sets sender_approved to true only when resolved_target is tx.sender. APPROVE_PAYMENT, when sender_approved is true, payer is not yet set, and the balance is sufficient, increments the sender's nonce, sets payer = resolved_target, and collects max_cost. At this point, if the tx.sender account does not exist, STATE_BYTES_PER_NEW_ACCOUNT * CPSB of state gas is subtracted. Executing APPROVE outside a frame transaction halts with an exception. The frames up to the one where payer is set are the validation prefix, and the frames after that are the execution body. If payer is not set after all frames finish, the transaction is invalid.
Atomic batches and receipts
An atomic batch is a contiguous range from frame i to frame j (j > i), where ATOMIC_BATCH_FLAG is set on frames i through j-1 and unset on j. If one frame in the batch fails, the state is reverted to just before the batch started and the remaining frames are skipped. The logs and state changes of frames that ran earlier are discarded, but the receipt keeps their execution status and execution gas used, empties the logs, and sets state gas to 0. The execution and state gas assigned to skipped frames is refunded at the end of the transaction. Inside a batch APPROVE is not possible, so there is never a need to roll back payer or sender_approved. The receipt payload is [cumulative_gas_used, payer, [frame_receipt, ...]], where frame_receipt is [status, gas_used, logs] and gas_used is [execution, state]. A new status code 0x2 is added to status, meaning a frame skipped because of a batch failure. The receipt has no transaction-level status, so an interface that must show a single status has to derive it itself. The logs bloom and log indices are the logs of the frame receipts concatenated in frame order. Between frames, TSTORE/TLOAD transient storage is discarded, and warm/cold records are shared across frames but are reverted along with a frame if that frame reverts.
Two-dimensional gas accounting and fee settlement
This follows EIP-8037's split of execution gas and state gas, but instead of the reservoir model it specifies limits.execution and limits.state explicitly for each frame. The fixed cost frame_tx_intrinsic_gas is FRAME_TX_INTRINSIC_COST + number of frames * FRAME_TX_PER_FRAME_COST + frame data cost + signature data cost + signature verification cost + value transfer cost (TX_VALUE_COST for each frame with a value). The bytes of a signature with an empty msg are all counted as nonzero, so its cost is determined by length alone. max_gas is the larger of standard_gas_limit and calldata_floor_gas + the sum of state limits. max_fee is a single budget covering execution, state, and blob gas, and the transaction is invalid if it is less than required_max_fee = max_gas * base_fee_per_gas + blob_gas * blob_base_fee. GASPRICE is base_fee_per_gas + max_priority_fee // max_gas, and is the same in all frames. State gas is attributed to the current frame's receipt, and a later frame's refill can reduce an earlier frame's gas_used.state. At settlement, after subtracting leftover gas and skipped budget, gas_used is obtained by applying the EIP-3529 refund (MAX_REFUND_QUOTIENT = 5) and the EIP-7976 calldata floor. base_fee_paid is burned, priority_fee_paid goes to the coinbase, and payer_refund is returned to the payer. Under EIP-7778, a block counts execution gas as the value before refunds and includes a transaction only if it fits within the remaining capacity of each of the two dimensions. Blobs are paid for from the max_fee budget at blob_base_fee, and a transaction does not have to carry any.
Expiry verification frame and default code
A VERIFY frame whose target is EXPIRY_VERIFIER (0x81413f0cF12e9b6a49B1D0439E081c577D57FfFf) is an expiry verification frame. Its data is an 8-byte big-endian expiry timestamp (EXPIRY_DATA_LENGTH = 8), and it reverts unless block.timestamp <= expiry_timestamp. flags, value, and limits.state must all be 0, and a transaction can contain only one. This contract is not installed by the protocol and is deployed like an ordinary contract, with runtime bytecode 0x60083614600a575f5ffd5b5f3560c01c4211601657005b5f5ffd. A client may skip EVM execution and compare directly if the result is the same. The default code lets an account with neither code nor an EIP-7702 delegation designator use frame transactions. In VERIFY mode it reads allowed_scope = frame.flags & APPROVE_SCOPE_MASK and reverts if it is 0. If APPROVE_EXECUTION is included it checks for a SECP256K1 signature (with empty msg) signed by resolved_target in signatures[0], and otherwise in signatures[1], and then executes APPROVE(allowed_scope). In SENDER or DEFAULT mode it ends successfully, like empty code. The default code itself uses no execution gas and incurs only the access cost on entry.
Introspection instructions, mempool rules, and current status
Six instructions that are used only inside a frame transaction are added. TXPARAM (0xb0) returns transaction information such as TXPARAM_SENDER, TXPARAM_SIG_HASH, TXPARAM_CURRENT_FRAME_INDEX, and TXPARAM_STATE_GAS_LEFT, and FRAMEPARAM (0xb3) returns frame information such as resolved_target, mode, flags, status, and gas_used, and both cost 2 gas. FRAMEDATALOAD (0xb1) and FRAMEDATACOPY (0xb2) read the data of other frames, like CALLDATALOAD and CALLDATACOPY. SIGPARAM (0xb4) returns signature metadata, and SIGDATACOPY (0xb5) copies only the raw bytes of ARBITRARY signatures. Executing them in any other transaction type halts with an exception. The public mempool rules are ERC-7562 minus staking and reputation, apply only to the validation prefix, and set MAX_VERIFY_GAS = 100_000, MAX_VERIFY_STATE_GAS = 500_000, and MAX_PENDING_TXS_USING_NON_CANONICAL_PAYMASTER = 1. The source status is Draft, so details may change. In the call records, ACDE #243 on 08.13 postponed the decision between EIP-8141 and EIP-8130, and ACDE #244 on 08.27 raised it to Scheduled for Inclusion (SFI) for Hegotá, on the condition that spec changes and L2 collaboration continue. ACDE #245 on 09.10 confirmed it as the headliner of Hegotá. ACDT #97 on 09.21 decided to start a separate frame-only devnet, and ACDT #100 on 10.12 decided to include it in bogota-devnet-1.