--- eip: 8223 title: Contract Payer Transaction description: A transaction type where gas is paid by the target contract via a canonical payer registry with static validation author: Ben Adams (@benaadams), Giulio Rebuffo (@Giulio2002) discussions-to: https://ethereum-magicians.org/t/eip-8223-contract-payer-transactions/28202 status: Draft type: Standards Track category: Core created: 2026-04-11 requires: 1559, 2718, 7708 --- ## Abstract This EIP introduces a new [EIP-2718](./eip-2718.md) transaction type - the **sponsored transaction** - in which gas fees are charged to the contract at `tx.to` rather than to the transaction sender. Authorization is mediated by a canonical **payer registry** system contract: the target contract must have explicitly registered `tx.sender` as its authorized sender. Validation requires no EVM code execution - only one storage read from the registry and one balance check on the payer - making it compatible with inclusion lists (FOCIL) and stateless validation. ## Motivation Gas sponsorship - where an entity other than the transaction sender pays for gas - is a frequently proposed Ethereum feature. Current proposals approach this through in-transaction code execution: - [EIP-8141](./eip-8141.md) (Frame Transaction) introduces execution frames with a `VERIFY` mode and `APPROVE` opcode, enabling arbitrary payer logic but requiring EVM execution during validation. - [EIP-8175](./eip-8175.md) (Composable Transaction) adds a `fee_auth` field that executes the payer's account code to provide ETH. Both approaches introduce code execution into the transaction validation path, complicating static analysis for inclusion lists and increasing the attack surface for denial-of-service via expensive validation. Many sponsorship use cases - particularly smart contract accounts that pay their own gas when called - do not require arbitrary payer logic. They need only: (1) the contract opts in to paying gas for a specific caller, and (2) the protocol charges the contract instead of the caller. This EIP provides exactly that, with validation reducible to static state reads. ![Gasless controller operation via Contract Payer Transaction](../assets/eip-8223/gasless-controller-operation.svg) ### Compatibility with Partial Statelessness (VOPS) Under Validity-Only Partial Statelessness (VOPS)[^1], nodes validate transactions using only account-trie data (balances, nonces, code hashes) and shed contract storage tries entirely. Sponsorship proposals that require reading arbitrary contract storage or executing payer code during validation are incompatible with this model. This EIP's validation reads are bounded to the account trie (sender nonce and balance, payer balance) plus a single storage slot from one known predeploy at `0x13`. A VOPS node would only need to additionally retain the storage trie for `PAYER_REGISTRY_ADDRESS` - one contract, not the full storage universe - to validate sponsored transactions. This is the same class of exception VOPS already makes for protocol-level system contracts. ### The Accidental Sponsorship Problem Any sponsorship mechanism that relies on reading storage from arbitrary contracts risks accidental sponsorship - existing deployed contracts may have storage values that happen to satisfy an authorization check. A canonical system registry eliminates this: no contract can become a gas payer without explicitly calling the registry's `authorize` function, which did not exist before this EIP's activation. ### One-to-One Payer Binding Each payer contract authorizes exactly one sender address (one-to-one from payer to sender). However, many payer contracts can independently authorize the same sender (many-to-one from payers to sender). A single EOA can therefore control multiple smart accounts that each pay their own gas - each account registers the same EOA as its authorized sender via `authorize`, and the EOA sends sponsored transactions to whichever account it wants to interact with. Combined with the mempool rule that limits each authorized sender to at most one sponsored transaction in the pool, a change in payer balance or registry entry triggers revalidation of at most one transaction. This is O(1) revalidation per payer, compared to the O(N) worst case of one-to-many sponsorship models where multiple senders depend on a single payer. ### Key Rotation Because the smart contract account holds all assets and the controlling EOA holds none, key rotation is a single registry update: the contract owner calls `authorize(newEOA)` and the old key is instantly deauthorized for sponsored transactions. No asset migration is required - the account address, balances, token holdings, and onchain identity remain unchanged. The new EOA can be a freshly generated key with no prior onchain history. This is simpler than EOA key rotation, which today requires transferring all assets to a new address. ### Protocol Gas-Funded Operations A protocol or DAO can deploy a dedicated smart contract account with scoped roles (parameter updates, oracle feeds, keeper operations), fund it with ETH from the treasury, and call `authorize(operatorEOA)`. The operator runs the protocol using sponsored transactions - the account pays gas, the operator's EOA holds nothing. Handover is a single `authorize(newOperatorEOA)` call - the new operator inherits the account's roles, permissions, and gas budget without any multi-step permission setup, and the old operator is simultaneously deauthorized (one-to-one binding means the new authorization replaces the old). Revocation without handover uses the same call: `authorize(address(0))` removes both the operational role and the gas sponsorship. In either case, no fund recovery is required - the account's remaining ETH stays in the protocol's control. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). ### Parameters | Parameter | Value | | --- | --- | | `SPONSORED_TX_TYPE` | `0x07` | | `PAYER_REGISTRY_ADDRESS` | `0x0000000000000000000000000000000000000013` | ### Sponsored Transaction A new [EIP-2718](./eip-2718.md) transaction is introduced where the `TransactionType` is `SPONSORED_TX_TYPE` and the `TransactionPayload` is the RLP serialization of: ``` rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, y_parity, r, s]) ``` The fields follow the same semantics as [EIP-1559](./eip-1559.md) with the following constraint: - `to` MUST NOT be empty. Sponsored contract creation is not supported. - `to` MUST have non-empty code. Only contracts can be gas payers. The `nonce` is the sender's nonce, incremented as normal. The sender is recovered from the signature `(y_parity, r, s)` using standard ECDSA recovery over the signing hash: ``` keccak256(SPONSORED_TX_TYPE || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list])) ``` The [EIP-2718](./eip-2718.md) `ReceiptPayload` for this transaction is `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`. ### Payer Registry Predeploy At the block where this EIP activates, the code at `PAYER_REGISTRY_ADDRESS` (`0x13`) MUST be set to the exact runtime bytecode specified in the [Reference Implementation](#payer-registry-runtime-bytecode). `0x13` is the next address after the precompiles allocated by [EIP-2537](./eip-2537.md) (`0x0b`-`0x11`) and [EIP-7997](./eip-7997.md) (`0x12`). The contract is placed in the precompile address range because it is read by the protocol during transaction validation, not only by EVM execution. #### Storage Layout The contract uses direct address-keyed storage via inline assembly - the storage slot for a given payer IS its address, with no Solidity mapping hash: ``` storage[uint256(payer_address)] = uint256(authorized_sender_address) ``` Both values are stored as 256-bit words with the address occupying the low 160 bits. The upper 96 bits MUST be zero. The canonical contract enforces this by masking on write; the protocol MUST compare only the low 160 bits when reading the registry during validation. #### Contract Interface ```solidity // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.28; /// @title Payer Registry - EIP-8223 /// @notice Canonical system contract for gas sponsorship authorization. /// Each payer (msg.sender) registers exactly one authorized sender. /// The protocol reads storage[payer] directly during sponsored /// transaction validation - no EVM execution required. contract PayerRegistry { /// @notice Emitted when a payer sets or revokes authorization. /// @param payer The contract that will pay gas (always msg.sender). /// @param sender The address now authorized, or address(0) on revoke. event Authorized(address indexed payer, address indexed sender); /// @notice Authorize `sender` to submit sponsored transactions where /// msg.sender pays for gas. Pass address(0) to revoke. /// @dev Uses direct-slot storage: sstore(caller, sender). /// The protocol reads this slot without executing contract code. function authorize(address sender) external { assembly { sstore(caller(), and(sender, 0xffffffffffffffffffffffffffffffffffffffff)) } emit Authorized(msg.sender, sender); } /// @notice Returns the authorized sender for `payer`, or address(0). /// @dev Uses direct-slot storage: sload(payer). Mask to 160 bits on read. function getAuthorized(address payer) external view returns (address sender) { assembly { sender := and(sload(payer), 0xffffffffffffffffffffffffffffffffffffffff) } } } ``` `authorize` is the only state-mutating function. Only `msg.sender` can set its own payer entry - no account can register another account as a gas payer. `getAuthorized` is a convenience view for on-chain queries; the protocol does not call it. Both functions MUST revert if `calldatasize < 36` (4-byte selector plus one 32-byte ABI word). Trailing calldata beyond the first argument is ignored, consistent with standard Solidity ABI decoding for single-argument external functions. #### Protocol Storage Access During transaction validation, the protocol reads `storage[uint256(tx.to)]` from `PAYER_REGISTRY_ADDRESS` directly, without executing the contract's code. Because the contract uses direct address-keyed slots (not a Solidity mapping), the storage key is simply the payer address zero-extended to 32 bytes - no `keccak256` hashing. This is a single storage slot read from a known predeploy address - equivalent in complexity to the storage reads already performed for [EIP-4788](./eip-4788.md) beacon roots and [EIP-2935](./eip-2935.md) block hashes. Clients MAY optimize this read by maintaining the payer registry state in a dedicated data structure. ### Transaction Validation A sponsored transaction is valid if and only if all of the following hold: 1. `to` is not empty. 2. `to` has non-empty code (`EXTCODESIZE(to) > 0`). Only contracts can be gas payers. 3. The signature is valid and the sender is recoverable. 4. `sender != to`. The sender and payer MUST be distinct addresses. 5. `chain_id` matches the current chain. 6. `max_fee_per_gas >= base_fee_per_gas` - as in [EIP-1559](./eip-1559.md). 7. `max_fee_per_gas >= max_priority_fee_per_gas` - as in [EIP-1559](./eip-1559.md). 8. `gas_limit >= intrinsic_gas` - where `intrinsic_gas` is computed identically to [EIP-1559](./eip-1559.md) (21,000 base cost plus calldata costs and access-list charges). 9. `nonce == sender.nonce`. 10. `payer_registry_storage[to] == sender` - the authorized sender in the payer registry at `PAYER_REGISTRY_ADDRESS` for key `to`, compared using only the low 160 bits, equals the recovered sender. 11. `to.balance >= gas_limit * max_fee_per_gas` - the payer has sufficient balance to cover the maximum gas cost. 12. `sender.balance >= value` - the sender has sufficient balance for the value transfer, if any. If any check fails, the transaction is **invalid** and MUST NOT be included in a block. ### Mempool Rules Clients MUST NOT hold more than one sponsored transaction per recovered sender address in the transaction pool, whether pending (executable at the current nonce) or queued (future nonce). If a new sponsored transaction arrives from a sender that already has one sponsored transaction in the pool, the client SHOULD replace the existing sponsored transaction only if the new transaction has the same nonce as the existing sponsored transaction and strictly higher `max_fee_per_gas` and `max_priority_fee_per_gas`. This constraint - covering both pending and queued transactions - is REQUIRED to achieve O(1) revalidation per payer. Because each payer authorizes exactly one sender, and each sender can contribute at most one sponsored transaction to the pool, each payer can have at most one relevant sponsored transaction in the pool. It also bounds churn from externally invalidated sponsorship: a payer balance change, registry update, payer code transition between empty and non-empty, sender nonce change, or sender balance change for a sponsored transaction with `value > 0` can invalidate the sponsored transaction, so carrying many sponsored transactions from the same sender would create avoidable revalidation work with no additional protocol benefit. The replacement criterion uses `max_fee_per_gas` and `max_priority_fee_per_gas` because both are transaction-local quantities that do not depend on the current block's `base_fee_per_gas`, and requiring both to increase prevents a replacement from offering a weaker fee cap or a lower effective tip. This is similar in spirit to [EIP-7702](./eip-7702.md), which recognizes that some transaction types have different implications for transaction-pool management, but this EIP makes the pool invariant explicit because the O(1) revalidation property depends on it. Clients MUST revalidate the sponsored transaction for a payer when the payer's balance or registry slot at `PAYER_REGISTRY_ADDRESS` changes. Clients MUST also revalidate the sponsored transaction when `tx.to`'s code changes in a way that can affect the `EXTCODESIZE(to) > 0` validity condition. Clients MUST also revalidate the sponsored transaction for a sender when the sender's nonce changes, or when the sender's balance changes for a sponsored transaction with `value > 0`. ### Transaction Execution Execution of a valid sponsored transaction proceeds as follows: 1. **Compute effective gas price**: Let `effective_gas_price = base_fee_per_gas + min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas)`. 2. **Gas funding**: Transfer `gas_limit * effective_gas_price` from `to.balance` to `sender.balance`. The validation check (step 11 above) ensures the payer can afford this at `max_fee_per_gas`; the actual transfer uses `effective_gas_price`, consistent with [EIP-1559](./eip-1559.md). 3. **Gas escrow**: Deduct `gas_limit * effective_gas_price` from `sender.balance`. This is the standard [EIP-1559](./eip-1559.md) gas purchase, applied to the sender who now holds the payer's funds. 4. **Nonce increment**: Set `sender.nonce = sender.nonce + 1`. 5. **Execute**: Perform a message call from `sender` to `to` with `value`, `data`, and `gas_limit - intrinsic_gas` available gas. `CALLER` (`msg.sender`) is the transaction sender. `ORIGIN` (`tx.origin`) is the transaction sender. 6. **Gas refund**: Add `(gas_limit - gas_used) * effective_gas_price` to `sender.balance`. This is the standard [EIP-1559](./eip-1559.md) refund. 7. **Refund return**: Transfer `(gas_limit - gas_used) * effective_gas_price` from `sender.balance` to `to.balance`. This returns unused gas funds to the payer. 8. **Priority fee**: Add `gas_used * (effective_gas_price - base_fee_per_gas)` to the coinbase balance. 9. **Base fee burn**: `gas_used * base_fee_per_gas` is burned. Steps 2-3 and 6-7 are each atomic: the sender's balance is transiently increased (step 2) then immediately decreased (step 3), so the sender never observes the gas funds during execution. The net effect is identical to deducting gas directly from the payer, but the intermediate balance movements are real and emit [EIP-7708](./eip-7708.md) Transfer logs. The value transfer from `sender` to `to` occurs as part of normal message call execution in step 5. ### Interaction with EIP-7702 If the sender has an [EIP-7702](./eip-7702.md) delegation designator, the delegation is resolved as normal for code execution purposes. The payer registry check and gas charging are independent of the sender's delegation status. If `tx.to` has an [EIP-7702](./eip-7702.md) delegation designator, the delegation is resolved for code execution (the call executes the delegated code in the context of `tx.to`), but the payer registry check and gas escrow use the literal `tx.to` address, not the delegation target. ### Interaction with EIP-7708 [EIP-7708](./eip-7708.md) requires that all ETH transfers emit logs. Every balance transfer in the [Transaction Execution](#transaction-execution) steps is a real state change. The two new transfers introduced by this EIP - the gas funding (step 2, `tx.to` -> `sender`) and the refund return (step 7, `sender` -> `tx.to`) - MUST each emit exactly one [EIP-7708](./eip-7708.md) Transfer log using the same format [EIP-7708](./eip-7708.md) defines for all ETH transfer logs: | Field | Gas funding log (step 2) | Refund return log (step 7) | | --- | --- | --- | | `address` | `SYSTEM_ADDRESS` | `SYSTEM_ADDRESS` | | `topics[0]` | `keccak256('Transfer(address,address,uint256)')` | `keccak256('Transfer(address,address,uint256)')` | | `topics[1]` | `tx.to` | `sender` | | `topics[2]` | `sender` | `tx.to` | | `data` | `gas_limit * effective_gas_price` | `(gas_limit - gas_used) * effective_gas_price` | Where `SYSTEM_ADDRESS` is `0xfffffffffffffffffffffffffffffffffffffffe` as defined by [EIP-4788](./eip-4788.md). These two logs are included in the transaction receipt in execution order: the gas funding log (step 2) is emitted before any logs produced by the transaction call, and the refund return log (step 7) is emitted after all logs produced during execution. This ordering is consensus-critical. All other logs (value transfer, in-execution calls, priority fee, base fee burn) are emitted by the standard [EIP-1559](./eip-1559.md) and [EIP-7708](./eip-7708.md) machinery applied to the sender, with no modifications. Because the sender actually holds and pays the gas, receipts, `effectiveGasPrice`, and all gas-related logs work identically to [EIP-1559](./eip-1559.md) transactions. No synthetic logs are introduced beyond the two explicitly defined above. ## Rationale ### Why a new transaction type? A new transaction type allows the sender to explicitly opt in to sponsored execution on a per-transaction basis. Without this, every transaction sent to a contract that has authorized the sender would automatically be sponsored, which may not be the sender's intent. ### Transaction format flexibility This EIP specifies the sponsored transaction as a standalone [EIP-2718](./eip-2718.md) type for clarity, but the core mechanism - the payer registry predeploy, the `registry[tx.to] == sender` validation rule, and the payer-side gas escrow - is independent of the transaction envelope. If the Ethereum transaction format evolves through proposals such as [EIP-8175](./eip-8175.md) (Composable Transaction) or [EIP-8141](./eip-8141.md) (Frame Transaction), registry-based sponsorship could be expressed as a capability or frame mode within those formats rather than as a separate type. The payer registry and validation logic would remain unchanged; only the signaling mechanism (dedicated type byte vs. capability flag vs. frame mode) would differ. ### Why not use EIP-7702 alone? [EIP-7702](./eip-7702.md) gives EOAs smart contract functionality by delegating code execution, but it does not change who pays gas. After setting a 7702 delegation, the EOA still needs ETH in its own balance to send transactions - `sender.balance >= gas_limit * max_fee_per_gas` is still enforced. 7702 enables batching, privilege de-escalation, and custom validation logic, but the gas always comes from the transaction sender. This EIP solves the complementary problem: the contract at `tx.to` pays gas, not the sender. #### Gasless hot-key The two compose to provide key separation with gasless hot-key operation: 1. **EOA-1** (master key) uses [EIP-7702](./eip-7702.md) to delegate to smart wallet code. EOA-1 now has code and keeps its existing address, assets, ENS name, and token approvals. EOA-1's key goes into cold storage. 2. **EOA-1** (via delegated code) calls `registry.authorize(EOA-2)`. 3. **EOA-2** (hot key) sends sponsored transactions with `tx.to = EOA-1`. EOA-2 holds zero ETH. EOA-1 pays gas. 4. The delegated code at EOA-1 executes the call. Key rotation: EOA-1's delegated code calls `authorize(EOA-3)`. EOA-2 loses sponsored access instantly. If EOA-2 is compromised, the attacker has an empty EOA - use EOA-1's cold key to revoke. #### Account consolidation A user with multiple EOAs (EOA-1, EOA-2, EOA-3) can upgrade each via [EIP-7702](./eip-7702.md) and have each call `authorize(EOA-new)` for the same fresh signing key. All existing accounts - with their addresses, assets, approvals, and onchain history - are now controlled by a single signer without moving any assets. The many-to-one authorization model (see [One-to-One Payer Binding](#one-to-one-payer-binding)) makes this possible: each account independently authorizes the same sender. This EIP also works without [EIP-7702](./eip-7702.md): a standalone smart contract account (deployed via `CREATE2`) can hold assets and authorize an EOA directly. The 7702 composition is useful when the user wants to upgrade existing EOAs into smart accounts while preserving their addresses. ### Why not wait for a more general sponsorship EIP? The core contribution of this EIP is the payer registry predeploy, not the transaction type. The registry - where contracts explicitly authorize a sender for gas sponsorship via a canonical system contract at a known address - is infrastructure that any static-validation sponsorship mechanism needs. If [EIP-8141](./eip-8141.md) or [EIP-8175](./eip-8175.md) ships a more general sponsorship model, the registry remains useful: the authorization check (`registry[tx.to] == sender`) and the predeploy at `0x13` are consumed by the general solution, not deprecated by it. Only the signaling mechanism (dedicated transaction type vs. capability flag vs. frame mode) would change, and the transaction type is the simplest part of the spec. ### Why tx.to as the payer? For the primary use case - smart contract accounts paying their own gas when called - the payer IS the call target. Using `tx.to` eliminates the need for an additional transaction field and provides natural binding: sponsorship only applies to calls directed at the payer contract. This prevents a payer from being charged for arbitrary transactions it has no involvement in. ### Why one-to-one instead of one-to-many? A one-to-many mapping would allow a single contract to sponsor multiple users. However, this creates O(N) mempool revalidation when the payer's balance changes - the same problem [EIP-8141](./eip-8141.md) identifies as the hard public-mempool case. The one-to-one authorization constraint, combined with the single-tx-per-sender mempool rule, limits revalidation to O(1) per payer: each payer authorizes at most one sender, and each sender contributes at most one sponsored transaction to the pool. The sender-scoped cap is also useful because sponsorship can be invalidated by external state changes (payer balance, registry entry, payer code becoming empty, sender nonce, and sender balance for nonzero-value transactions), so admitting multiple sponsored transactions from the same sender would add churn without increasing expressiveness. One-to-one authorization alone is not sufficient - without the mempool cap, a single sender could still queue a nonce-ordered sequence against the same payer. This follows the same general pattern as [EIP-7702](./eip-7702.md): the transaction format itself is not the only consensus concern, because client transaction-pool behavior must also account for the new validity interactions introduced by the type. For use cases requiring one-to-many sponsorship (e.g., application-level relayers), [EIP-8175](./eip-8175.md) or [EIP-8141](./eip-8141.md) are more appropriate. ### Why a predeploy contract instead of an account-level field? A predeploy at a precompile-range address requires no changes to the account model and allows the authorization state to be queried by other contracts via standard calls. It follows established Ethereum precedent for protocol-read state: [EIP-4788](./eip-4788.md) and [EIP-2935](./eip-2935.md) use system contracts; [EIP-7997](./eip-7997.md) uses a predeploy at `0x12`. Placing the registry at `0x13` reflects that the protocol reads this state during transaction validation - the same class of protocol-level action as precompile execution. It also avoids conceptual collision with [EIP-7702](./eip-7702.md)'s use of the account code field. ### Why not read authorization from the payer's own storage? Reading a storage slot from an arbitrary contract during validation is unsafe: existing deployed contracts may have storage values at common slots that accidentally satisfy the check. A canonical registry ensures that authorization is always the result of an explicit `authorize` call that could not have occurred before this EIP's activation. ### Why pre-execution escrow? Without upfront gas escrow, the payer contract could spend down its own ETH during execution and leave the transaction undercollateralized. [EIP-8175](./eip-8175.md) identifies this same requirement. The gas flow is: payer funds the sender, sender pays gas via standard [EIP-1559](./eip-1559.md) mechanics, unused gas is refunded to the sender, sender returns the refund to the payer. Every step is a real balance transfer, which means [EIP-7708](./eip-7708.md) logs, receipts, and `effectiveGasPrice` all work identically to normal transactions. The funding and return transfers use `effective_gas_price`, not `max_fee_per_gas`, to avoid freezing the spread during execution. ## Backwards Compatibility This EIP introduces a new transaction type and a new predeploy contract. It does not modify the behavior of existing transaction types. Contracts that wish to act as gas payers MUST explicitly call the payer registry. No existing contract is affected unless it takes this action. The `SPONSORED_TX_TYPE` value `0x07` does not conflict with any finalized transaction types: `0x01` ([EIP-2930](./eip-2930.md)), `0x02` ([EIP-1559](./eip-1559.md)), `0x03` ([EIP-4844](./eip-4844.md)), `0x04` ([EIP-7702](./eip-7702.md)). ## Reference Implementation ### Payer Registry Runtime Bytecode The canonical runtime bytecode for the payer registry predeploy is: ``` 0x5f3560e01c8063b6a5d7de14601b5763c731fc7b14607e575f5ffd5b506024361060a65760043573ffffffffffffffffffffffffffffffffffffffff16335560043573ffffffffffffffffffffffffffffffffffffffff16337ff5a7f4fb8a92356e8c8c4ae7ac3589908381450500a7e2fd08c95600021ee8895f5fa3005b6024361060a6576004355473ffffffffffffffffffffffffffffffffffffffff165f5260205ff35b5f5ffd ``` This is 170 bytes long. Its `keccak256` code hash is: ``` 0x95fc87eed2393785342038c61323e9b975b23fa917fbda7b9bc721bfdd7b3e5c ``` The following EVM assembly is an equivalent human-readable rendering of that runtime bytecode: ``` // Entrypoint: dispatch on selector push0 // [0] calldataload // [calldata[0:32]] push1 0xe0 shr // [selector] // authorize(address) = 0xb6a5d7de dup1 // [selector, selector] push4 0xb6a5d7de // [0xb6a5d7de, selector, selector] eq // [match, selector] push1 AUTHORIZE // [AUTHORIZE, match, selector] jumpi // [selector] // getAuthorized(address) = 0xc731fc7b push4 0xc731fc7b // [0xc731fc7b, selector] eq // [match] push1 GET // [GET, match] jumpi // [] // No match → revert push0 push0 revert // authorize(address sender) AUTHORIZE: jumpdest pop // [] push1 0x24 calldatasize // [calldatasize, 0x24] lt // [calldatasize < 36] push1 BADARGS // [BADARGS, calldatasize < 36] jumpi // [] push1 0x04 calldataload // [raw_sender] push20 0xffffffffffffffffffffffffffffffffffffffff and // [sender] mask to 160 bits caller // [caller, sender] sstore // [] storage[caller] = sender // emit Authorized(msg.sender, sender) push1 0x04 calldataload // [raw_sender] push20 0xffffffffffffffffffffffffffffffffffffffff and // [sender] mask to 160 bits caller // [caller, sender] // Authorized(address indexed payer, address indexed sender) // topic0 = keccak256("Authorized(address,address)") push32 0xf5a7f4fb8a92356e8c8c4ae7ac3589908381450500a7e2fd08c95600021ee889 push0 push0 log3 // log3(0, 0, topic0, caller, sender) stop // getAuthorized(address payer) → address GET: jumpdest push1 0x24 calldatasize // [calldatasize, 0x24] lt // [calldatasize < 36] push1 BADARGS // [BADARGS, calldatasize < 36] jumpi // [] push1 0x04 calldataload // [payer] sload // [raw_authorized] push20 0xffffffffffffffffffffffffffffffffffffffff and // [authorized] mask to 160 bits push0 mstore // [] mem[0:32] = authorized push1 0x20 push0 return // return mem[0:32] BADARGS: jumpdest push0 push0 revert ``` The contract is installed at `PAYER_REGISTRY_ADDRESS` (`0x13`) as a predeploy in the genesis state of the activating fork, with its code set to the exact runtime bytecode above. Implementations MAY reproduce the bytecode from this assembly using the `sys-asm` toolchain, but the hex byte sequence above is canonical. ### Usage Example A smart contract account authorizing its controller for sponsored transactions: ```solidity interface IPayerRegistry { function authorize(address sender) external; function getAuthorized(address payer) external view returns (address); } IPayerRegistry constant PAYER_REGISTRY = IPayerRegistry(PAYER_REGISTRY_ADDRESS); contract SmartAccount { address public controller; constructor(address initialController) { controller = initialController; // Authorize controller to submit sponsored txs targeting this account PAYER_REGISTRY.authorize(initialController); } /// @notice Rotate controller - atomically updates both control and /// gas sponsorship authorization in one state transition. function transferControl(address newController) external { require(msg.sender == controller, "Not controller"); controller = newController; PAYER_REGISTRY.authorize(newController); } /// @notice Revoke all sponsored access. function revokeSponsorship() external { require(msg.sender == controller, "Not controller"); PAYER_REGISTRY.authorize(address(0)); } function execute(address target, uint256 value, bytes calldata data) external { require(msg.sender == controller, "Not controller"); (bool ok,) = target.call{value: value}(data); require(ok, "Execution failed"); } receive() external payable {} } ``` The controller can then send sponsored transactions (type `SPONSORED_TX_TYPE`) with `tx.to = address(SmartAccount)`. The protocol verifies `registry[SmartAccount] == controller`, escrows gas from `SmartAccount`'s balance, and executes the call. The controller's EOA needs no ETH. ## Security Considerations ### Payer Balance Drainage The gas escrow mechanism prevents undercollateralization during execution. However, a malicious authorized sender can drain the payer's ETH through the gas escrow itself by submitting transactions with high `gas_limit * max_fee_per_gas`. Payer contracts SHOULD implement appropriate controls and monitor balance levels. The payer can revoke authorization at any time by calling `authorize(address(0))`. ### Observable Balance During Execution The gas funding transfer (step 2 of [Transaction Execution](#transaction-execution)) moves `gas_limit * effective_gas_price` from `to.balance` to `sender.balance` before the contract executes. Because the transaction then calls `to`, the contract observes a lower `address(this).balance` for the duration of the call. The unused-gas refund is only returned to the payer after execution completes (step 7). Payer contracts should account for this: a contract that attempts to sweep `address(this).balance` during a sponsored call will transfer less than its full pre-transaction balance. The funded portion is not lost - it is returned to the payer after execution - but it is not visible to `BALANCE` or `SELFBALANCE` during the call. Payer contracts designed for this EIP should reserve headroom above the maximum gas funding and should not rely on `address(this).balance` reflecting the full pre-transaction balance. ### Registry Manipulation During Execution The payer registry is checked during validation, not during execution. If a sponsored transaction's execution modifies the registry (e.g., the payer revokes authorization mid-execution), this does not affect the current transaction's gas payment. The change takes effect for subsequent transactions. ### Mempool Considerations A sponsored transaction becomes invalid when: - The payer's balance drops below `gas_limit * max_fee_per_gas`. - The payer changes its authorized sender in the registry. - The payer no longer has non-empty code. - The sender's nonce changes. - The sender's balance drops below `value`, if `value > 0`. The [Mempool Rules](#mempool-rules) ensure each payer has at most one relevant sponsored transaction and each sender has at most one sponsored transaction in the pool, so invalidation remains O(1) for the affected payer or sender. ### Front-Running Registry Updates If a payer contract updates its authorized sender (e.g., during a control rotation), the old sender's pending transactions remain valid until the update lands onchain. Payer contracts that require atomic rotation SHOULD update the registry in the same state transition as the control change. ### Interaction with Account Abstraction This EIP is complementary to full account abstraction proposals ([EIP-8141](./eip-8141.md), [EIP-8175](./eip-8175.md)). It covers the specific case of a contract paying for calls directed at itself with static validation. For arbitrary payer logic, cross-contract sponsorship, or alternative signature schemes, the more general proposals are appropriate. [^1]: soispoke, "A pragmatic path towards Validity-Only Partial Statelessness (VOPS)," *Ethereum Research* (Execution Layer Research), April 29, 2025, available at `https://ethresear.ch/t/a-pragmatic-path-towards-validity-only-partial-statelessness-vops/22236`. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).