--- eip: 8398 title: Native Account Abstraction Transactions description: Add a native transaction for portable Keystore accounts author: Chris Hunter (@chunter-cb) , Pedro Gomes (@pedrouid) discussions-to: https://ethereum-magicians.org/t/composable-native-account-abstraction/29526 status: Draft type: Standards Track category: Core created: 2026-08-27 requires: 155, 170, 1559, 2028, 2718, 2929, 7702, 7708, 8401 --- ## Abstract This proposal introduces an [EIP-2718](./eip-2718.md) transaction type for accounts using the portable Keystore defined by [EIP-8401](./eip-8401.md). It provides native custom authentication, call batching, gas sponsorship, account-change transport, and keyed sequential transaction nonces without requiring nodes to execute arbitrary wallet validation code. ## Motivation [EIP-8401](./eip-8401.md) separates account authority from transaction transport. Its actors, authenticators, deterministic accounts, and signed configuration changes work through ordinary EVM calls, including [ERC-4337](./eip-4337.md) deployments. Chains can additionally provide a predictable native path. Each transaction declares its authenticator, allowing nodes to identify the validation work before execution. A bounded canonical authenticator set keeps validation enumerable, while the native envelope supplies batching, sponsorship, and parallel sequential nonce channels. Advanced controls such as policy-gated actors, account locking, and nonce-free transactions are left to dependent extensions. ## 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 and RFC 8174. ### Overview A transaction authenticates its sender and optional payer against the [EIP-8401](./eip-8401.md) Keystore. The protocol invokes the declared authenticator, resolves the returned actor configuration, applies this proposal's scope rules, consumes a keyed sequential nonce, and executes the declared calls. The Keystore remains usable without this transaction type. Chains that do not implement this proposal use an EVM transport such as [ERC-4337](./eip-4337.md). This proposal owns the transaction envelope, transaction-specific scope assignments, native validation and execution, keyed nonces, gas accounting, account-change transport, transaction context, receipts, and RPC behavior. EIP-8401 owns the persistent actor and configuration model. #### Account Types | Account type | Native behavior | |---|---| | EOA | A raw secp256k1 signature resolves the sender; an account with no code is auto-delegated to `DEFAULT_ACCOUNT_ADDRESS` | | Existing smart account | The account first imports or configures actors through EIP-8401 | | New smart account | A create entry invokes the EIP-8401 deterministic creation semantics before call execution | ### Adoption Profiles The AA transaction is specified once but adopted under one of two **profiles**. A chain MUST declare which profile it activates; the profile fixes the parameters this specification otherwise leaves open: the [intrinsic-gas schedule](#intrinsic-gas), how the enshrined [canonical authenticator set](#canonical-authenticator-set) evolves, and whether the AA transaction path accepts authenticators outside that set. The **canonical set is accepted on the transaction path under both profiles**, always at a constant enshrined validation cost, so a wallet targeting the canonical set behaves identically everywhere; the profiles differ only in what else is allowed and how it changes, never in transaction semantics. **L1 profile (normative schedule, permissive acceptance).** For a base layer, where the gas schedule *is* the specification and consensus validity MUST NOT depend on any out-of-band configuration. Under this profile: - The [intrinsic-gas schedule](#intrinsic-gas) is **normative**. The component values in this document are protocol constants, identical for every node, and MAY change only through a hard fork. A node MUST NOT adopt a different schedule. - Canonical authenticators are **enshrined on the validation path** with a constant validation cost (their fixed `sender_auth_cost` / `payer_auth_cost` component values); no EVM runs to authenticate a canonical actor, and the enshrined set changes only through a hard fork. - Authenticator acceptance is **permissive**: the AA transaction path accepts **any** authenticator whose `STATICCALL` returns within `MAX_AUTHENTICATION_GAS`, canonical or not (see [Authenticators](#authenticators)). No allowlist is maintained; a base layer can afford this bounded, single-call validation. Under the L1 profile, two independent implementations that agree on this document produce identical block validity. **L2 profile (configurable schedule, canonical-only path).** For an L2 or any high-throughput EVM that wants to track a local cost model, move faster than L1 governance, and keep the transaction path to fixed-cost validation. Under this profile a chain MAY: - adopt a different [intrinsic-gas schedule](#intrinsic-gas) (for example to mirror a future EVM repricing), provided it is fixed under that chain's consensus and identical across its nodes; - extend the enshrined constant-cost canonical set it accepts through the [companion ERC](#canonical-authenticator-set) process and node configuration. Authenticator acceptance is **canonical-only**: the AA transaction path accepts only the enshrined canonical set (fixed validation cost, no arbitrary `STATICCALL` on the hot path). Non-canonical authenticators remain registrable and usable through the EVM (config-change calls, recovery flows, [ERC-4337](./eip-4337.md)), just not on the AA transaction path. An L2-profile chain is still internally consistent; wallets that need cross-chain portability SHOULD rely only on the canonical set and the recommended schedule. The gas-schedule policy and the acceptance policy are independent choices; the two profiles name the common pairings, and a chain MAY combine them differently. What is universal is the **canonical set**: every compliant chain accepts it on the AA transaction path at a constant enshrined cost, so a wallet can rely on canonical authenticators working everywhere. Additional authenticators may be registered on any chain (for example, recovery flows), but are usable on the AA transaction path only where a chain's policy is permissive; otherwise they are reached through the EVM. Throughout this specification, "recommended" values and schedules mean **required under the L1 profile** and **default-but-overridable under the L2 profile**; the normative *structure* (the gas-formula components, the authenticator interface, the canonical set) holds under both. ### Authenticators The authenticator interface, actor binding, expiry, implicit address-bound actor, and persistent configuration are defined by [EIP-8401](./eip-8401.md). #### Canonical Authenticator Set Canonical authenticators are enshrined on the validation path with constant validation costs. Their native execution MUST produce the same result as their EIP-8401 authenticator contracts. Whether an authenticator outside the canonical set is accepted on the transaction path is the chain's acceptance policy: - A permissive chain authenticates it with one `STATICCALL` bounded by `MAX_AUTHENTICATION_GAS`. - A canonical-only chain rejects it on the native transaction path. The actor remains usable through an EVM transport. The canonical set and deterministic contract addresses are catalogued in a companion ERC. Every compliant chain MUST accept the canonical set. ### AA Actor Scope EIP-8401 stores a `uint16` scope word and defines `scope == 0x0000` as admin. This proposal assigns: | Bit | Value | Name | Authority | |---|---|---|---| | 0 | `0x01` | `SENDER` | May originate a native AA transaction | | 1 | `0x02` | Reserved | Companion extension | | 2 | `0x04` | `NONCE` | May consume a sequential transaction nonce | | 3 | `0x08` | `SELF_PAYER` | May pay the account's own gas | | 4 | `0x10` | `SPONSOR_PAYER` | May pay gas for another account | | 5–15 | | Reserved | Future pure grants | For sender authentication, require admin or `SENDER`. A restricted sender using a sequential transaction nonce additionally requires `NONCE`. For self-payment, require admin or `SELF_PAYER`. For sponsorship, require admin or `SPONSOR_PAYER`. Unknown and reserved bits grant no transaction authority. ### Keyed Transaction Nonces Nonce state is managed by the precompile at `NONCE_MANAGER_ADDRESS`. The transaction's `nonce_key` selects an independent sequential channel, and `nonce_sequence` is the expected sequence. | `nonce_key` | Meaning | |---|---| | `0` | Standard sequential channel | | `1` through `NONCE_KEY_MAX - 1` | Wallet-defined parallel sequential channels | | `NONCE_KEY_MAX` | Reserved for a companion extension | For every key defined by this proposal, `nonce_sequence` MUST equal the current sequence for `(sender, nonce_key)`. Inclusion increments that sequence regardless of call success. A restricted actor requires `NONCE`; an admin may use any sequential key. Mempool replacement candidates share `(sender, nonce_key, nonce_sequence)`. ### AA Transaction Type A new [EIP-2718](./eip-2718.md) transaction with type `AA_TX_TYPE`: ``` AA_TX_TYPE || rlp([ chain_id, sender, // Sender address (20 bytes) | empty for EOA signature nonce_key, // uint256: nonce channel selector nonce_sequence, // uint64: sequence number valid_after, // uint64: Unix timestamp (ms or s, auto-detected); 0 = no lower bound valid_before, // uint64: Unix timestamp (ms or s, auto-detected); 0 = no expiry max_priority_fee_per_gas, max_fee_per_gas, gas_limit, account_changes, // Account creation, config change, and/or delegation operations | empty calls, // [[call, ...], ...] where call = rlp([to, data]) | empty metadata, // opaque attribution/annotation bytes | empty payer, // empty = sender-paid, payer_address = specific payer sender_auth, // raw ECDSA r||s||v (EOA, sender empty) | authenticator || data (configured actor) payer_auth // empty = self-pay | authenticator || data = sponsored (same format as sender_auth) ]) call = rlp([to, data]) // to: address, data: bytes ``` #### Field Definitions | Field | Description | |-------|-------------| | `chain_id` | Chain ID per [EIP-155](./eip-155.md) | | `sender` | Sending account address. **Required** (non-empty) for configured actor signatures. **Empty** for EOA signatures; address recovered via ecrecover. The presence or absence of `sender` is the sole distinguisher between EOA and configured actor signatures. | | `nonce_key` | `uint256` nonce channel selector. `0` is the standard sequential channel, `1` through `NONCE_KEY_MAX - 1` are wallet-defined parallel sequential channels, and `NONCE_KEY_MAX` is reserved for an extension. | | `nonce_sequence` | `uint64` expected sequence within `nonce_key`. Must match the current sequence for `(sender, nonce_key)` and is incremented after inclusion regardless of execution outcome. | | `valid_after` | `uint64` Unix timestamp, **milliseconds or seconds** (auto-detected per [Timestamp Normalization](#field-definitions)); encoded as a canonical minimal-length RLP integer. After normalization to milliseconds, transaction invalid when `block.timestamp * 1000 < valid_after`. A value of `0` means no lower bound. Note: many mempools will not hold a not-yet-active transaction and MAY reject one whose `valid_after` is in the future, so it is best used for near-term activation rather than long-dated scheduling. | | `valid_before` | `uint64` Unix timestamp, **milliseconds or seconds** (auto-detected per [Timestamp Normalization](#field-definitions)); encoded as a canonical minimal-length RLP integer. After normalization to milliseconds, transaction invalid when `block.timestamp * 1000 > valid_before`. A value of `0` means no expiry. | | `max_priority_fee_per_gas` | Priority fee per gas unit ([EIP-1559](./eip-1559.md)) | | `max_fee_per_gas` | Maximum fee per gas unit ([EIP-1559](./eip-1559.md)) | | `gas_limit` | Maximum gas budget for sender-intrinsic gas (intrinsic gas excluding payer authentication) and call execution (see [Intrinsic Gas](#intrinsic-gas)). Payer authentication is metered separately and does not draw from `gas_limit` | | `account_changes` | **Empty**: No account changes. **Non-empty**: Array of typed entries: create (type `0x00`) for account deployment, config change (type `0x01`) for actor management, and delegation (type `0x02`) for code delegation. See [Account Changes](#account-changes) | | `calls` | **Empty**: No calls. **Non-empty**: Array of call phases; see [Call Execution](#call-execution) | | `metadata` | **Empty**: No metadata. **Non-empty**: Opaque attribution or annotation bytes. See [Transaction Metadata](#transaction-metadata) | | `payer` | Gas payer identity. **Empty**: Sender pays. **20-byte address**: This specific payer required. See [Payer Modes](#payer-modes) | | `sender_auth` | See [Signature Format](#signature-format) | | `payer_auth` | Payer authorization. **Empty**: self-pay. **Non-empty**: `authenticator \|\| data`, same format as `sender_auth`. See [Payer Modes](#payer-modes) | **Millisecond window.** The transaction window (`valid_after`/`valid_before`) is denominated in milliseconds, while on-chain actor expiries are seconds; this split is deliberate and there is no second-denominated variant of these fields. Because `block.timestamp` has second granularity, the **effective** activation/expiry resolution equals the chain's block-timestamp resolution (1 second on most chains today); the millisecond field exists for off-chain clock compatibility (e.g. `Date.now()`, sequencer clocks) and forward-compatibility with sub-second block times, not for finer resolution than the chain itself provides. Can easily be scaled on ingress. **Timestamp Normalization.** `valid_after` and `valid_before` MAY be expressed in either seconds or milliseconds; a node accepts **both at the same time** and decides the unit per value, not per node or per chain. There is no mode flag: the unit is a fixed function of the value against the threshold `TIMESTAMP_MS_THRESHOLD` = `100_000_000_000` (10^11), applied independently to each field: - a value **below** `TIMESTAMP_MS_THRESHOLD` is **always** interpreted as **seconds** and normalized to milliseconds as `value * 1000`; - a value **at or above** `TIMESTAMP_MS_THRESHOLD` is **always** interpreted as **milliseconds** and used as-is; - a `0` value means "no bound" and is not normalized. This is unambiguous for every realistic value: a 64-bit Unix timestamp in seconds does not reach 10^11 until the year 5138, while any Unix timestamp in milliseconds has exceeded 10^11 since 1973. Because the rule is a pure function of the value, every node derives the same normalized timestamp, so this is consensus-safe (no node-local configuration affects block validity). All validity-window comparisons in this specification operate on the normalized millisecond value; wherever `valid_after` or `valid_before` appears in a comparison below, it denotes the normalized value. #### Intrinsic Gas **Intrinsic gas** follows the standard Ethereum meaning: the total cost to include the transaction. As in standard transactions it accounts for signature authentication, here both the sender's authentication (`sender_auth_cost`) and the payer's (`payer_auth_cost`) are included. **Full-EVM metering model (normative).** The reference cost of an AA transaction is what its work costs when metered as ordinary EVM execution, over four phases: 1. **Account changes.** Apply `account_changes` in order: a create entry calls `createAccount` and config-change entries call `applySignedAccountChanges` (both metered as ordinary Keystore contract calls), and a delegation entry performs a standard [EIP-7702](./eip-7702.md) delegation, metered at the standard delegation cost. 2. **Validation.** Authenticate the transaction: `STATICCALL` the `sender_auth` authenticator, and (when sponsored) the `payer_auth` authenticator, each followed by the single `actor_config` SLOAD that resolves and authorizes the actor. Metered as ordinary STATICCALL execution. 3. **Execution.** Dispatch the transaction's `calls`, metered as ordinary EVM execution. 4. **Bound.** The transaction's total metered gas is bounded by `effective_gas_limit` (defined below), which is the gas limit for the transaction as a whole. This model is the normative reference for **results**: whatever pricing a chain uses, a transaction MUST be accepted, authenticated, and executed to the same outcome and state changes that phases 1–3 produce under ordinary EVM. A chain MAY implement phases 1 and 2 (account-change application and authentication) as **native** code where applicable (for the enshrined canonical authenticator set and the Keystore reads it depends on), running no EVM for that work and instead charging a fixed gas cost per authenticator and per account-change operation (see [Portability](#portability)). The native path MUST match the Full-EVM model in results, but it need **not** match it in gas: its fixed schedule is its own consensus-fixed pricing, not a re-derivation of what EVM metering would charge. Whether phases 1–2 are metered pure-EVM or priced by a native fixed schedule is decided per chain (see [Adoption Profiles](#adoption-profiles)); phase 3 is always ordinary EVM execution. The per-component values below are that native fixed schedule for phases 1–2. They approximate the EVM access and data-availability costs ([EIP-2929](./eip-2929.md) / [EIP-2028](./eip-2028.md)) at the time of writing, but a native chain charges them as flat costs rather than metering the underlying EVM. Their normative status follows the chain's [adoption profile](#adoption-profiles): - **L1 profile:** the values below are **protocol constants** (the gas schedule *is* part of the specification), identical for every node and changeable only through a hard fork. A chain MUST NOT adopt a different intrinsic-gas schedule under this profile. - **L2 profile:** the values below are a **recommended default**. A chain MAY reprice them (for example to track a future EVM repricing or a local cost model), provided the schedule is fixed under that chain's consensus and identical across its nodes. The formula's structure (its components and what each one accounts for) is normative under both profiles; only the absolute numbers may vary, and only under the L2 profile. ``` intrinsic_gas = AA_BASE_COST + tx_payload_cost + nonce_cost + bytecode_cost + account_changes_cost + auto_delegation_cost + sender_auth_cost + payer_auth_cost ``` **Sender-intrinsic gas** is intrinsic gas excluding payer authentication and is bounded by `gas_limit`. For self-pay transactions `payer_auth_cost` is `0`, so sender-intrinsic gas equals intrinsic gas: ``` sender_intrinsic_gas = intrinsic_gas - payer_auth_cost = AA_BASE_COST + tx_payload_cost + nonce_cost + bytecode_cost + account_changes_cost + auto_delegation_cost + sender_auth_cost ``` Intrinsic gas is charged before `calls` run. Since `payer_auth_cost` is metered outside `gas_limit`, the gas available to `calls` is: ``` execution_gas_available = gas_limit - sender_intrinsic_gas ``` `payer_auth_cost` is metered separately and charged to the payer on top of `gas_limit`; it does not draw from `gas_limit` and cannot reduce the gas available to `calls`. This isolation is required: `payer_auth` is excluded from both the sender and payer signature hashes (see [Signature Payload](#signature-payload)) and is selected unilaterally by the payer. If it consumed `gas_limit`, a payer could choose an expensive authenticator to starve `calls` and alter execution behavior. Keeping it separate makes the gas available to `calls` a function of sender-signed fields alone. For the same reason the data-availability cost of the serialized `payer_auth` bytes is charged in `payer_auth_cost` rather than `tx_payload_cost`: those bytes are chosen by the payer and excluded from the signed hashes, so their cost is metered outside `gas_limit` and never reduces the sender's execution budget. The payer reimburses `payer_auth_cost` regardless (the payer pays all gas). Because `payer_auth_cost` sits outside `gas_limit`, total `gasUsed` can exceed `gas_limit` on a sponsored transaction. The overrun is bounded by an **effective gas limit**: ``` effective_gas_limit = gas_limit + MAX_AUTHENTICATION_GAS ``` `MAX_AUTHENTICATION_GAS` is the ceiling on a single authenticator's execution: an authenticator outside the canonical set is `STATICCALL`ed with at most this much gas, and it also bounds how much the payer's authenticator (metered outside `gas_limit`) may add on top of the sender-signed `gas_limit`. Its **suggested value is 100,000 gas**. A transaction whose resolved `payer_auth_cost` exceeds `MAX_AUTHENTICATION_GAS` is rejected at [Mempool Acceptance](#mempool-acceptance) rather than metered, so `gasUsed <= effective_gas_limit` always holds and block-gas accounting stays bounded. For self-pay transactions `payer_auth_cost` is `0` and `effective_gas_limit == gas_limit`. Under the L1 profile `MAX_AUTHENTICATION_GAS` is fixed under consensus; under the L2 profile a chain MAY choose its own value (see [Adoption Profiles](#adoption-profiles)). `sender_auth_cost`, by contrast, is included in `gas_limit`: the sender chooses its own authenticator, and both sender and payer sign over `gas_limit`, so both commit to the full sender-side budget before inclusion. **`sender_auth_cost`**: authenticator execution + 1 SLOAD (`actor_config`). **`payer_auth_cost`**: 0 for self-pay (`payer` empty). Otherwise, the same `sender_auth_cost` model applies to the payer's authenticator (authenticator execution + 1 `actor_config` SLOAD), **plus** the data-availability cost of the serialized `payer_auth` field itself (16 gas per non-zero byte, 4 per zero byte, per [EIP-2028](./eip-2028.md)). These `payer_auth` bytes are excluded from `tx_payload_cost` and metered here instead, so the entire payer-authentication cost, execution and bytes alike, sits outside `gas_limit` and is paid by the payer. | Component | Value | |-----------|-------| | `AA_BASE_COST` | 15,000 gas: the fixed per-transaction overhead of the AA path, analogous to the standard 21,000 base cost but excluding the components broken out separately below (signature authentication is `sender_auth_cost`/`payer_auth_cost`; calldata is `tx_payload_cost`). It covers transaction decoding, sender/actor resolution and the `actor_config` access accounting common to every AA transaction, nonce dispatch, and receipt assembly. Like the other values it is a protocol constant under the L1 profile that only an L2-profile chain MAY reprice (see [Adoption Profiles](#adoption-profiles)) | | `tx_payload_cost` | Standard per-byte cost over the RLP-serialized transaction **excluding the `payer_auth` field**: 16 gas per non-zero byte, 4 gas per zero byte, consistent with [EIP-2028](./eip-2028.md). Ensures all sender-signed fields (`account_changes`, `sender_auth`, `calls`, `metadata`, etc.) are charged for data availability. The `payer_auth` bytes are chosen unilaterally by the payer and excluded from the signed hashes, so their byte cost is metered in `payer_auth_cost` (outside `gas_limit`) rather than here | | `nonce_cost` | 22,100 gas for first use of a `nonce_key` (cold SLOAD + SSTORE set), otherwise 5,000 gas (cold SLOAD + warm SSTORE reset) | | `bytecode_cost` | 0 if no create entry in `account_changes`. Otherwise: 32,000 (deployment base) + code deposit cost (200 gas per deployed byte). Byte costs for `code` are covered by `tx_payload_cost`; the create entry's initial-actor slot writes are covered by `account_changes_cost` | | `account_changes_cost` | Per applied **create** entry: one `actor_config` slot write per initial actor (22,100 gas each: cold SLOAD + SSTORE set). Per applied **config change** entry: authentication cost plus the storage writes for each mutated slot. Per applied **delegation** entry: 4,600 gas (indicator deposit, 200 × 23 bytes). Per **skipped** config-change entry: 2,100 gas (sequence SLOAD). `0` when there is no create, config-change, or delegation entry. | | `auto_delegation_cost` | Delegation indicator deposit: 4,600 gas (200 × 23 bytes for the `0xef0100 \|\| address` indicator) when a code-less `sender` is auto-delegated to `DEFAULT_ACCOUNT_ADDRESS` (Block Execution step 4). 0 otherwise. | #### Signature Format Signature format is determined by the `sender` field: **EOA signature** (`sender` empty): Raw 65-byte ECDSA signature `(r || s || v)`. The sender address is recovered via ecrecover. **Configured actor signature** (`sender` set): ``` authenticator (20 bytes) || data ``` The first 20 bytes identify the authenticator address. When the authenticator is `K1_AUTHENTICATOR`, `data` is raw ECDSA `(r || s || v)` and the protocol handles ecrecover natively. For all other authenticators, `data` is authenticator-specific; each authenticator defines its own wire format. ##### Validation 1. **Resolve sender**: If `sender` empty, ecrecover derives the sender address (EOA path) with `actorId = bytes32(uint256(uint160(sender)))`. If `sender` set, read the first 20 bytes of `sender_auth` as the authenticator address. 2. **Authenticate**: Route by authenticator address. For the EOA path (`sender` empty), ecrecover was already performed in step 1. For `K1_AUTHENTICATOR` (`address(1)`), the protocol natively ecrecovers from `data` (as `r || s || v`), returning `actorId = bytes32(uint256(uint160(recovered_address)))`. For all other authenticators, call `authenticator.authenticate(hash, data)` via STATICCALL, returning `actorId` (or `bytes32(0)` for invalid). `address(0)` is never a valid authenticator selector (it is the empty `actor_config` sentinel). 3. **Authorize**: **Self-actor (native secp256k1) rule**: if authentication in step 2 used the native secp256k1 path (the EOA path or `K1_AUTHENTICATOR`) and `actorId == bytes32(uint256(uint160(sender)))`, resolve the self-actor from the inline default-EOA config in the packed account-state slot: reject if `DEFAULT_EOA_REVOKED` is set; otherwise take `scope` and `expiry` from the inline fields (all-zero = unrestricted, non-expiring full owner, i.e. admin). Otherwise SLOAD `actor_config(sender, actorId)`, reject if its reserved bytes are nonzero (version gate, see [EIP-8401 storage](./eip-8401.md#account-state)), and require that the stored authenticator address matches the effective authenticator (this covers every other actor, including a non-secp256k1 self). In either case, if the resolved `expiry` is non-zero, also require `block.timestamp <= expiry`; an expired actor is rejected. 4. **Check scope**: Read the resolved `scope` byte (from the inline default-EOA config for the secp256k1 self-actor, or `actor_config` otherwise) and check it against the context being authorized per [AA Actor Scope](#aa-actor-scope). For `sender_auth`, require admin or `SENDER`. Payer scope is checked when the payer is resolved (see [Validation Flow](#validation-flow) step 6). 5. **Check nonce scope**: For sender-context transactions, require the resolved actor be admin or carry `NONCE`. #### Signature Payload Sender and payer use different type bytes for domain separation, preventing signature reuse attacks: **Sender signature hash**, all tx fields through `payer`, excluding `sender_auth` and `payer_auth`: ``` keccak256(AA_TX_TYPE || rlp([ chain_id, sender, nonce_key, nonce_sequence, valid_after, valid_before, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, account_changes, calls, metadata, payer ])) ``` **Payer signature hash**, all tx fields through `payer`, excluding `sender_auth` and `payer_auth`: ``` keccak256(AA_PAYER_TYPE || rlp([ chain_id, sender, nonce_key, nonce_sequence, valid_after, valid_before, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, account_changes, calls, metadata, payer ])) ``` The `sender` field in the payer signature hash MUST be the resolved sender address. In the EOA path (`sender` empty in the transaction wire format), the recovered sender address (from `sender_auth` ecrecover, see [Validation](#validation) step 1) MUST be substituted into the `sender` position before computing this hash; it MUST NOT be encoded as the empty wire-format value. This binds the payer's signature to the specific resolved sender and prevents cross-sender replay of payer signatures (see [Payer Security](#security-considerations)). The `payer` field MUST also be included so the payer's signature is bound to the account being charged; without it, a payer authorization could be redirected to any other account that authorizes the same actor with SPONSOR_PAYER scope (e.g., via the [EIP-8401 delegate authenticator](./eip-8401.md#delegate-authenticator)). #### Payer Modes Gas payment and sponsorship are controlled by two independent fields: **`payer`**, the sender's commitment regarding the gas payer, included in the sender's signed hash: | Value | Mode | Description | |-------|------|-------------| | empty | Self-pay | Sender pays their own gas | | `payer_address` (20 bytes) | Sponsored | Sender binds tx to a specific sponsor | **`payer_auth`** uses the same `authenticator || data` format as `sender_auth`: | `payer` | `payer_auth` | Payer Address | Validation | |---------|-------------|---------------|------------| | empty | empty | `sender` | Self-pay: no separate `payer_auth`; the resolved sender actor MUST be admin or carry `SELF_PAYER` | | `sender` address | `authenticator (20) \|\| data` | `sender` | Self-pay via a dedicated gas key: payer account == sender. The resolved payer actor MUST be admin or carry `SELF_PAYER` | | other address | `authenticator (20) \|\| data` | `payer` field | Sponsored: the resolved payer actor MUST be admin or carry `SPONSOR_PAYER` | ### Transaction Metadata The `metadata` field is optional opaque bytes for attaching attribution or annotation data to a transaction, for example builder/app attribution, a payment reference or memo, or a commitment to off-chain data. Legacy transactions carry such data as a "data suffix" appended to `tx.input`; because [calls](#call-phases) replace the single input blob, `metadata` provides the equivalent home. `metadata` is part of the signed transaction (covered by both the [sender and payer signature payloads](#signature-payload)) and is charged per byte through `tx_payload_cost` like any other transaction bytes. It does not affect validation or execution. Off-chain consumers (indexers, explorers) read it directly from the transaction. Producers MAY use any encoding. ### Account Changes The `account_changes` field transports EIP-8401 operations and native code delegation before calls execute. Entries are applied in order and are atomic per phase. | Type | Name | Meaning | |---|---|---| | `0x00` | Create | Create the sender through EIP-8401 | | `0x01` | Config change | Apply an EIP-8401 signed configuration batch | | `0x02` | Delegation | Set EIP-7702 delegation for the sender | Nodes SHOULD enforce a configurable mempool limit on configuration entries. #### Create Entry ```text rlp([ 0x00, user_salt, code, initial_actors ]) ``` Each initial actor is `[actorId, authenticator, scope]`. The actor set, account address, deployment header, and storage initialization MUST follow [EIP-8401](./eip-8401.md#account-creation). Validation requires: 1. A canonical, strictly sorted EIP-8401 initial actor set. 2. Runtime code does not exceed the [EIP-170](./eip-170.md) maximum. 3. The derived address equals the resolved transaction sender. 4. The destination is fresh: zero code size and zero account nonce. 5. `sender_auth` resolves to an initial actor bound to the declared authenticator. The protocol places the supplied runtime code directly and initializes the EIP-8401 state. Its result MUST match the EIP-8401 `createAccount` EVM path. #### Config Change Entry ```text rlp([ 0x01, channel, sequence, changes, auth ]) change = rlp([ change_type, payload ]) ``` This is the transaction encoding of EIP-8401 `SignedAccountChanges`. The operation payload is ABI-encoded as required by EIP-8401. Native application MUST match `applySignedAccountChanges`, including authorization, atomicity, expiry handling, storage, sequence consumption, events, and reverts. The batch may use the EIP-8401 Local or Multichain channel. Its admin authorization is independent from `sender_auth`. #### Delegation Entry Delegation entries set an [EIP-7702](./eip-7702.md)-style delegation indicator: ```text rlp([ 0x02, target ]) ``` The sender MUST have authenticated through the native secp256k1 path, with the address-derived self actor and admin scope. A generic authenticator that returns the same actor ID does not qualify. `target == address(0)` clears delegation. Otherwise `target` MUST contain deployed code and MUST NOT itself be delegated. Multiple delegation entries in one transaction are invalid. #### Execution of Account Changes Account changes execute before calls: 1. Validate all entry encodings and transaction-level constraints. 2. Apply create entries through EIP-8401 semantics. 3. Apply configuration entries in order. 4. Apply delegation entries. 5. Revert the account-change phase atomically if any entry fails. A successful account-change phase persists even when a later call phase fails, as specified below. ### Execution #### Call Execution The protocol dispatches calls directly from `sender` to each call's `to` address: | Parameter | Value | |-----------|-------| | `from` (caller) | `sender` (the sender) | | `to` | `call.to` | | `tx.origin` | `sender` | | `msg.sender` at target | `sender` | | `msg.value` | 0 | | `data` | `call.data` | Calls carry no ETH value. ETH transfers are initiated by the account's wallet bytecode via the CALL opcode (see [Why No Value in Calls?](#why-no-value-in-calls)). ##### Call Phases `calls` is a two-level structure: an ordered array of **phases**, where each phase is an ordered array of individual calls (`[[call, ...], [call, ...]]`) — **sequentially-committed atomic call groups**. This gives two levels of atomicity where calls grouped within a phase are all-or-nothing, while phases commit independently in sequence. Phases execute in order from a single gas pool (`gas_limit`). Within each phase, calls execute in order and are **atomic** so if any call in a phase reverts, all state changes for that phase are discarded and remaining phases are **skipped**. Completed phases **persist** and their state changes are committed and survive later phase reverts. **Common patterns**: - **Simple call**: `[[call]]`, one phase, one call (`call = rlp([to, data])`) - **Atomic batch**: `[[call_a, call_b, call_c]]`, one phase, all-or-nothing - **Sponsor + user**: `[[sponsor_payment], [user_action_a, user_action_b]]`, sponsor in phase 0 (committed), user actions in phase 1 (atomic, skipped if sponsor fails) #### Transaction Context The Transaction Context precompile at `TX_CONTEXT_ADDRESS` provides read-only access to the current AA transaction's metadata. It reads directly from the client's in-memory transaction state; protocol "writes" are effectively zero-cost. Gas is charged as a base cost plus 3 gas per 32 bytes of returned data, matching `CALLDATACOPY` pricing. The static, sender-signed fields (`sender`, `payer`, `calls`, `gas_limit`) are populated during authentication and call execution, so a payer authenticator can inspect the transaction it is being asked to sponsor and decide on chain, at validation time, whether to pay. The authenticated `actorId` is resolved only after authentication succeeds, so it is available during execution only. | Function | Returns | Available | |----------|---------|-----------| | `getTransactionSender()` | `address`, the account executing calls (`sender`) | Validation + Execution | | `getTransactionPayer()` | `address`, gas payer (`sender` for self-pay, payer for sponsored) | Validation + Execution | | `getTransactionCalls()` | `Call[][]`, the full `calls` array (`Call = (address to, bytes data)`) | Validation + Execution | | `getTransactionGasLimit()` | `uint256`, the sender-side gas budget (`gas_limit`) before intrinsic costs and call execution | Validation + Execution | | `getTransactionSenderActorId()` | `bytes32`, authenticated actor's actorId | Execution only | Under the [L1 profile](#adoption-profiles) this getter set and its validation-time availability are normative; under the L2 profile a chain MAY populate the precompile during execution only. If the wallet needs the authenticator address or scope, it calls `getActorConfig(account, actorId)` on the Keystore contract. Keeping actor configuration out of the precompile limits the precompile to immutable transaction context. **Chains without this transaction type**: No code at `TX_CONTEXT_ADDRESS`; `STATICCALL` returns zero/default values. ### Portability The same EIP-8401 account can use different transaction transports on different chains: | Component | Chain implementing this proposal | Chain without this transaction type | |---|---|---| | Authority | EIP-8401 Keystore | Same EIP-8401 contract | | Authentication | Native or bounded `STATICCALL` | Authenticator contracts through EVM | | Configuration | `account_changes` or EVM call | `applySignedAccountChanges` | | Creation | Native create entry | EIP-8401 CREATE2 factory | | Transaction nonce | Nonce Manager | Transport-specific, such as ERC-4337 EntryPoint | | Execution | `AA_TX_TYPE` | ERC-4337 or wallet-specific | All EIP-8401 contracts SHOULD use deterministic addresses across chains. The native path changes transport and pricing, not account authority. ### Validation Flow #### Mempool Acceptance 1. Parse and structurally validate `sender_auth`. Verify `account_changes` contains at most one create entry (type `0x00`, must be first) and at most one delegation entry (type `0x02`). Nodes SHOULD enforce a configurable limit on the number of config change entries (type `0x01`). 2. Resolve sender: if `sender` set, use it; if empty, ecrecover from `sender_auth` 3. Determine effective actor state: a. If create entry present in `account_changes`: verify address derivation, `code_size(sender) == 0`, use `initial_actors` b. Else: read from Keystore storage 4. If config change or delegation entries are present in `account_changes`, simulate applying config operations in sequence and skip already-applied entries. For delegation entries, verify `code_size(sender) == 0` or an existing delegation designator. 5. Validate `sender_auth` against resulting actor state (see [Validation](#validation)), require admin or `SENDER` for the sender context, and require admin or `NONCE` for sequential nonce use. Payer scope (SELF_PAYER / SPONSOR_PAYER) is checked in step 6, not here — the sender actor is not required to carry a payer bit. If delegation entries are present, the resolved actor must additionally hold admin EOA-actor delegation authority (see [Delegation Entry](#delegation-entry)). 6. Resolve payer from `payer` and `payer_auth`: - `payer` empty and `payer_auth` empty: self-pay. Payer is `sender`; require the resolved sender actor be admin or carry `SELF_PAYER`. Reject if balance insufficient. - `payer` = `sender` (explicit) with `payer_auth`: self-pay via a dedicated gas key. Validate `payer_auth` against the account and require the resolved actor be admin or carry `SELF_PAYER`. Reject if balance insufficient. - `payer` = a different 20-byte address (sponsored): validate `payer_auth` against the payer and require the resolved actor be admin or carry `SPONSOR_PAYER`. - In every mode, reject the transaction if the resolved `payer_auth_cost` exceeds `MAX_AUTHENTICATION_GAS`, so total gas stays within `effective_gas_limit = gas_limit + MAX_AUTHENTICATION_GAS` (see [Intrinsic Gas](#intrinsic-gas)). 7. Require `nonce_key < NONCE_KEY_MAX` and `nonce_sequence == current_sequence(sender, nonce_key)`, then verify payer ETH balance and validity bounds. The transaction's validity window and the resolved actor's liveness MUST all hold: after normalizing `valid_after`/`valid_before` to milliseconds (see [Timestamp Normalization](#field-definitions)), the transaction is valid only when `valid_after == 0 || valid_after <= block.timestamp * 1000` and `valid_before == 0 || block.timestamp * 1000 <= valid_before`, and the resolved actor satisfies the [EIP-8401 actor expiry](./eip-8401.md#actors-and-authenticators) rule. Any bound violation rejects the transaction. Nodes MAY also reject a transaction whose `valid_before` is too near to be reliably included, and MAY reject rather than hold one whose `valid_after` is not yet active. 8. Mempool threshold: gas payer's pending count below node-configured limits. 9. Treat a pending transaction with the same `(sender, nonce_key, nonce_sequence)` as a replacement candidate under the node's standard fee-bump rules. Nodes recognize the enshrined canonical authenticator set and authenticate any other accepted authenticator via a single `STATICCALL` bounded by `MAX_AUTHENTICATION_GAS`, per the [Canonical Authenticator Set](#canonical-authenticator-set) rules. #### Block Execution 1. If `account_changes` contains delegation entries, require admin EOA-actor delegation authority (see [Delegation Entry](#delegation-entry)). 2. ETH gas precharge from payer (`sender` for self-pay). The payer is precharged for the whole transaction, `effective_gas_limit × max_fee_per_gas`, which reserves `gas_limit` plus `payer_auth_cost` (payer authentication execution and its serialized `payer_auth` bytes, both metered outside `gas_limit`; see [Intrinsic Gas](#intrinsic-gas)). The block reserves `effective_gas_limit` against its gas target for the same reason. Transaction is invalid if the payer's balance cannot cover the precharge. 3. Increment the Nonce Manager sequence for `(sender, nonce_key)`. 4. If `code_size(sender) == 0` and no create entry and no delegation entry is present in `account_changes`, auto-delegate `sender` to `DEFAULT_ACCOUNT_ADDRESS` (set code to `0xef0100 || DEFAULT_ACCOUNT_ADDRESS`). This delegation persists. A delegation entry that clears the indicator (`target = address(0)`) returns the account to code-less state, but auto-delegation re-applies on its next transaction: a code-less account cannot permanently opt out of auto-delegation without placing non-delegation bytecode (e.g. via a create entry). 5. Process `account_changes` entries in order (see [Execution of Account Changes](#execution-of-account-changes)). 6. Set the authenticated `actorId` on the Transaction Context precompile. The static sender-signed fields (`sender`, `payer`, `calls`, `gas_limit`) are already exposed from validation onward; `actorId` becomes readable from this point (execution). 7. Execute `calls` per [Call Execution](#call-execution) semantics. Settlement charges the payer for gas actually used, sender-intrinsic gas plus executed `calls` (together bounded by `gas_limit`) plus `payer_auth_cost`, and refunds the remainder of the precharge. Because `payer_auth_cost` is metered outside `gas_limit`, it is charged in full and is never part of the refundable unused-`gas_limit` amount. For step 5, the protocol SHOULD inject log entries into the transaction receipt matching this proposal's events, following the protocol-injected log pattern established by [EIP-7708](./eip-7708.md). These protocol-injected logs are emitted only for AA transactions. ### RPC Extensions **`eth_getTransactionCount`**: Extended with an optional `nonceKey` parameter (`uint256`) to query keyed transaction nonce channels. Reads from the Nonce Manager precompile at `NONCE_MANAGER_ADDRESS`. **`eth_getTransactionReceipt`**: AA transaction receipts include: - `payer` (address): Gas payer address (`sender` for self-pay, specified payer for sponsored). - `status` (uint8): `0x01` = all phases succeeded (or `calls` was empty), `0x00` = one or more phases reverted. Existing tools checking `status == 1` remain correct for the success path. **`status == 0` does not imply "no state change."** Committed earlier phases (the sponsor pattern relies on this), auto-delegation, applied `account_changes`, nonce consumption, and gas payment all persist through a later phase revert. Consumers MUST NOT treat a failed AA receipt as a no-op. `gasUsed` includes `payer_auth_cost` (see [Intrinsic Gas](#intrinsic-gas)). - `phaseStatuses` (uint8[]): Per-phase status array. Each entry is `0x01` (success) or `0x00` (reverted). Phases after a revert are not executed and reported as `0x00`. Empty if `calls` was empty. **`eth_estimateGas`** / **`eth_call`**: Accept the AA transaction fields (`sender`, `nonceKey`, `accountChanges`, `calls`, `validAfter`, `validBefore`, `metadata`, `payer`, `senderAuth`, `payerAuth`) alongside the standard request object and price the request as an `AA_TX_TYPE` transaction. Because authentication gas is determined by the auth blob's *shape* rather than a valid signature (see [Intrinsic Gas](#intrinsic-gas)), the request is priced from an **unsigned** representative blob; no signature is produced or verified. The sender is taken from `sender` or the standard `from` (equivalently) and if both are present they MUST be equal. ### Constants | Name | Value | Meaning | |---|---|---| | `AA_TX_TYPE` | `0x79` | EIP-2718 transaction type | | `AA_PAYER_TYPE` | `0x7A` | Payer-signature domain separator | | `AA_BASE_COST` | 15000 | Base intrinsic gas | | `NONCE_MANAGER_ADDRESS` | `0x813000000000000000000000000000000000aa01` | Nonce Manager precompile | | `TX_CONTEXT_ADDRESS` | `0x813000000000000000000000000000000000aa02` | Transaction Context precompile | | `DEFAULT_ACCOUNT_ADDRESS` | Deterministic deployment address | Default delegated wallet implementation | | `NONCE_KEY_MAX` | `2^256 - 1` | Reserved nonce key | | `TIMESTAMP_MS_THRESHOLD` | `100_000_000_000` | Seconds/milliseconds threshold | | `MAX_AUTHENTICATION_GAS` | Chain parameter | Bound for one non-canonical authenticator | The EIP-8401 constants `KEYSTORE_ADDRESS`, `K1_AUTHENTICATOR`, `CONTRACT_ESTABLISHED`, and `DEFAULT_EOA_REVOKED` are used unchanged. ## Rationale ### Why a Native Transaction? A native envelope exposes authentication and replay inputs before wallet execution. Nodes can bound validation and enumerate the state that invalidates a pending transaction without simulating arbitrary account code. ### Why Keyed Sequential Nonces? Independent keys allow unrelated workflows to proceed in parallel while retaining simple monotonic replay protection for every channel. ### Why Reuse EIP-8401 Account Changes? The same signed configuration batch can be relayed through an EVM call or transported natively. Wallet authority therefore does not depend on chain support for this transaction type. ### Why Reserve Extension Values? Reserving a scope bit and `NONCE_KEY_MAX` permits dependent standards to extend the same actor representation and transaction envelope without changing base transaction behavior. ### Why No Value in Calls? Each call executes with `msg.value == 0`. Accounts transfer ETH by calling an account implementation that explicitly performs the value transfer, keeping value movement visible in call data and compatible with policy extensions. ## Backwards Compatibility Existing EOAs, smart contracts, and ERC-4337 infrastructure continue to operate unchanged. EIP-8401 accounts are usable without this proposal. Adoption adds a native transaction path but does not require account migration. A transaction using a sequential nonce key below `NONCE_KEY_MAX` and no assigned extension values remains valid on chains implementing compatible extensions. ## Reference Implementation ### ITransactionContext (Precompile) ```solidity interface ITransactionContext { struct Call { address to; bytes data; } function getTransactionSender() external view returns (address); function getTransactionPayer() external view returns (address); function getTransactionCalls() external view returns (Call[][] memory); function getTransactionGasLimit() external view returns (uint256); function getTransactionSenderActorId() external view returns (bytes32); } ``` Read-only. Gas is charged as a base cost plus 3 gas per 32 bytes of returned data. `getTransactionSender`, `getTransactionPayer`, `getTransactionCalls`, and `getTransactionGasLimit` read static sender-signed fields and are available during both authentication and call execution; `getTransactionSenderActorId` is available during execution only (the actor is resolved after authentication). ### INonceManager (Precompile) ```solidity interface INonceManager { function getNonce(address account, uint256 nonceKey) external view returns (uint64); } ``` Read-only. This proposal reads nonce keys below `NONCE_KEY_MAX`; the maximum value is reserved for an extension. The protocol manages nonce storage directly; there are no state-modifying functions. Gas is charged as a base cost plus a cold SLOAD (2,100 gas) for the first read of an `(account, nonceKey)` pair in the transaction, or a warm SLOAD (100 gas) for subsequent reads of the same slot, consistent with [EIP-2929](./eip-2929.md) access rules. ## Security Considerations **Validation surface:** Canonical authenticators are pure functions of the transaction hash, signature data, and EIP-8401 actor configuration. Their enumerable invalidators are actor changes and sequential nonce consumption. Permissive acceptance of non-canonical authenticators widens that surface to state read by those contracts. **Replay protection:** The signature binds `chain_id`, nonce key, nonce sequence, and validity window. Inclusion increments the selected sequential channel regardless of call outcome. **Scope enforcement:** The protocol must apply sender, nonce, and payer grants after authentication. Unknown and reserved bits grant nothing. **Authenticator binding:** The actor ID returned by an authenticator must resolve to an EIP-8401 configuration bound to that authenticator. **Implicit actor:** Only the raw EOA path or `K1_AUTHENTICATOR` may use EIP-8401's implicit address-bound actor. **Payer replay:** The payer digest binds the resolved sender and payer. For a raw EOA transaction, validation substitutes the recovered sender address into the payer digest. **Account changes:** Native creation and configuration must exactly match EIP-8401 contract results. Divergence would produce transport-dependent authority. **Delegation:** Only native secp256k1 authentication of the admin self actor may authorize delegation. Arbitrary authenticators cannot claim this path by returning the account address. **Transaction context:** Contracts must treat zero values from `TX_CONTEXT_ADDRESS` as absence of the native transaction context on unsupported chains. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).