Protocol
2D nonces & nonceless
A transaction carries a 2D nonce: a channel key plus a sequence. Sequenced channels give ordered lanes; the reserved NONCE_KEY_MAX channel drops sequencing entirely and leans on expiry plus a fee-invariant replay identifier.
Two fields
Nonce state lives in a precompile at NONCE_MANAGER_ADDRESS; the protocol reads and increments it during processing and exposes a read-only getNonce() to the EVM. Every transaction carries two fields:
| Concept | Meaning |
|---|---|
| nonce_key (uint256) | Selects the nonce channel (lane). |
| nonce_sequence (uint64) | Expected sequence number within that channel (must be 0 for nonce-free). |
| Concept | Meaning |
|---|---|
| 0 | Standard sequential ordering. Mempool default. |
| 1 … NONCE_KEY_MAX − 1 | User-defined parallel channels, defined by wallets. |
| NONCE_KEY_MAX | Nonce-free. No nonce state read or incremented. |
Why parallel lanes
Independent workflows on one account should not queue behind each other. Extra nonce_key values are independent lanes: each channel has its own sequence, so a stuck transaction in one lane never blocks another. Restricted (non-admin) actors need the NONCE grant to use sequenced channels; without it they are limited to NONCE_KEY_MAX (nonceless), which is the default for a fresh session key.
Nonce-free mode
When nonce_key == NONCE_KEY_MAX, the protocol reads and increments no nonce counter and nonce_sequence MUST be 0. Ordering coordination disappears, which is ideal for high-throughput, order-independent workloads like games and bots. Replay protection comes from two things instead:
- A non-zero expiry: a short wall-clock window the transaction is valid within. Nodes SHOULD reject far-future expiries.
- A replay identifier: consensus dedup state (a fixed-capacity circular buffer of
replay_id → expiry) so the same logical transaction cannot be included twice inside its window.
The replay identifier
A nonce-free transaction has no (nonce_key, nonce_sequence) to key dedup and replacement on, so it carries a replay identifier that names the logical transaction independent of fees and authorization blobs. Standard and 2D transactions do not use it; they dedup and replace on (sender, nonce_key, nonce_sequence).
REPLAY_ID_TYPE = 0x7901
replay_id = keccak256(REPLAY_ID_TYPE || rlp([
chain_id, resolved_sender, expiry,
account_changes, calls, metadata,
payer
]))resolved_sender is the recovered address (EOA path) or the sender field (configured-actor path), so two distinct EOAs signing identical bodies still get distinct ids.
Fee bumps vs new logical transactions
This is the key mental model: a transaction can be bumped for gas, but any change to what it does is a new logical transaction.
| Concept | Meaning |
|---|---|
| Excluded from replay_id | max_fee_per_gas, max_priority_fee_per_gas, gas_limit, sender_auth, payer_auth. A fee bump (with a fresh payer_auth when sponsored) is a replacement of the same logical tx. |
| Included in replay_id | chain_id, resolved_sender, expiry, account_changes, calls, metadata, payer. Changing any of these (e.g. retargeting the payer) yields a different id: a new logical tx. |
Replacement rules follow from that. A fee bump MUST raise max_priority_fee_per_gas by the node's minimum (e.g. ≥10%) and be independently valid. Sequenced transactions replace on (sender, nonce_key, nonce_sequence); nonce-free transactions replace on (sender, replay_id), and builders MUST NOT include two with the same (sender, replay_id) in one block.
Repeating an action nonce-free
Because the logical transaction is fee-invariant, sending the exact same nonce-free action twice would collide on replay_id. Two ways to do real repeat work:
- Expiry as entropy:
expiryis part ofreplay_id, so the same action signed with a different (later) expiry is a distinct logical transaction. Repeated actions naturally carry fresh windows. - Batching: group the repeats into one transaction's
calls(phases) so a single logical transaction does the work, rather than N identical ones.
When you need strict ordering
If an actor genuinely needs an ordered, replay-protected sequence (a high-throughput automation key, say), it opts into a sequenced nonce_key with the NONCE grant and uses the full 2D space. Nonce-free is the default; sequencing is the opt-in.