Skip to content

Transaction type 0x06

The byte-level authority for building frame transactions. For what they are and why, see Frame transactions.

Signed transactions travel as 0x06 ‖ rlp(payload) via eth_sendRawTransaction. The acknowledgement hash is the keccak-256 of the raw bytes.

payload = [chain_id, nonce, sender, gas_limit, frames, signatures,
tip_cap, fee_cap, blob_fee_cap, blob_hashes]
Frame = [mode, flags, target, gas_limit, value, data]
TxSig = [scheme, signer, msg, signature]
Field Type Notes
chain_id uint 8141
nonce uint The sender account’s nonce. Read it from the chain.
sender 20 bytes The account. Explicit, not recovered.
gas_limit uint Transaction-level, signed. Frost-local field.
frames list 1–64 frames, executed in order.
signatures list 0–128 entries.
tip_cap uint Priority fee. Set to 0 — the chain is fee-blind.
fee_cap uint The bid for inclusion.
blob_fee_cap uint EIP-4844.
blob_hashes list EIP-4844 versioned hashes.

There is no to field. Each frame carries its own target; an empty target means tx.sender.

Field Values
mode 0x00 DEFAULT · 0x01 VERIFY · 0x02 SENDER
flags bit 0 may approve payment · bit 1 may approve execution · bit 2 atomic batch
target 20 bytes, or empty for tx.sender
gas_limit uint, this frame’s budget
value uint, nonzero only in SENDER frames
data bytes, this frame’s calldata

A VERIFY frame is static-called — it cannot modify state — and must call APPROVE for the transaction to proceed. The one exception is the expiry-verifier frame (a VERIFY frame with flags 0 targeting 0x…8141), which never approves and is only required not to fail. A SENDER frame runs with CALLER == tx.sender, and only after execution approval.

Field Values
scheme 0x00 ARBITRARY · 0x01 SECP256K1 · 0x02 P256
signer 20 bytes; must be absent for ARBITRARY
msg empty, or exactly 32 bytes (and a 32-byte msg must not be all-zero)
signature bytes

scheme is a verification mode, not an algorithm label. ARBITRARY means the protocol does not verify the entry at all; the bytes are made available to frame code via SIGPARAM. This is how a post-quantum signature rides in a transaction the protocol does not understand.

Protocol-verified schemes have fixed sizes: secp256k1 signatures are exactly 65 bytes, P256 exactly 128.

sig_hash = keccak256(0x06 ‖ rlp(payload'))

where payload' equals payload except that every signature entry whose msg is empty has its signature field replaced by the empty string.

Build with an empty witness → hash → sign → attach. Attaching does not change the hash.

Entries with a non-empty msg sign msg rather than the sig-hash, so they are hashed as-is and never elided.

The sig-hash commits to: chain ID, nonce, sender, the signed gas_limit, every frame including its calldata, the fee caps, and the shape of the signature list (count, schemes, and msgs). It does not commit to elided witness bytes — which is why a relayer that corrupts them can only invalidate a transaction, never redirect it.

Consensus rejects — in the pool and in a block — any frame transaction that violates these:

  • 1–64 frames; at most 128 signature entries.
  • mode ≤ 0x02; flags < 8.
  • Nonzero value only in SENDER frames.
  • The atomic-batch flag (bit 2) is invalid on the last frame.
  • Every entry’s msg is empty or exactly 32 bytes, and a 32-byte msg must not be all-zero.
  • ARBITRARY entries must leave signer absent.
  • An ARBITRARY entry with a non-empty msg caps signature at 4,096 bytes — those bytes are permanent.
  • Elidable (empty-msg) witnesses are capped at 16,384 bytes per entry and 16,384 bytes per transaction.
  • secp256k1 signatures are exactly 65 bytes; P256 exactly 128.
  • Expiry-verifier frames (a VERIFY frame resolving to the canonical verifier at 0x…8141): at most one per transaction, flags 0, zero value, and data exactly 8 bytes — the big-endian expiry deadline.
  • Validation prefix. Ignoring expiry frames, the first frame must be a VERIFY targeting tx.sender, optionally preceded by exactly one DEFAULT frame (the deploy frame). A VERIFY frame that immediately follows and targets another address is the paymaster frame. No prefix frame may carry the atomic-batch flag, and payment may be approved only inside the prefix.
  • A prefix that carries a deploy frame is valid only if the sender has no code before the prefix and code after it.
  • secp256k1 entries: byte 0 is the recovery id (0 or 1) and s is low (EIP-2 canonical). P256 entries are r ‖ s ‖ qx ‖ qy with low s, and signer must equal keccak256(qx ‖ qy)[12:].
15,000 intrinsic
+ 475 × frame count
+ calldata gas EIP-7623, signature bytes included
+ per-signature verification 2,800 secp256k1 · 6,700 P256 · 0 ARBITRARY
+ permanent-witness surcharge 40 per byte, non-elidable witnesses only
+ Σ frame gas_limit

Total gas is not a field. It is derived, and the signed gas_limit must be at least equal to it.

Each frame’s gas_limit also pays the frame-entry access charge. Before a frame dispatches, the resolved target’s EIP-2929 account access — 2,600 gas cold, 100 warm — is debited from the frame’s gas and the target becomes warm. A frame whose gas_limit cannot cover the charge halts at entry, consuming the whole limit and running nothing; if a frame reverts, the target’s warm status reverts with it.

The permanent-witness surcharge exists because ARBITRARY entries with a non-empty msg are never elided from the canonical encoding, so witness segregation can never prune them. They live in permanent history and are priced above the elidable-byte floor.

frames:
[0] VERIFY target: ∅ (self) flags: 3 gas: 30,000 data: ∅
[1] SENDER target: recipient value: V gas: 50,000 data: D
signatures:
[0] {ARBITRARY, msg: ∅, signature: ML-DSA-65 over sig_hash} 3,309 B
frames:
[0] DEFAULT target: factory flags: 0 gas: deploy budget data: salt (32 B) ‖ initcode
[1] VERIFY target: ∅ (self) flags: 3 gas: 30,000 data: ∅
[2] SENDER target: recipient value: V gas: 50,000 data: D
signatures:
[0] {ARBITRARY, msg: ∅, signature: ML-DSA-65 over sig_hash} 3,309 B

Frame 0 is the validation prefix’s optional leading DEFAULT frame, aimed at the CREATE2 factory at 0x4e59b44847b379578588920cA78FbF26c0B4956C. It installs the account’s code, frame 1 runs the freshly installed verifier, and the single signature covers the deployment parameters because the sig-hash commits to every frame’s data. The nonce is 0, and the sender must have no code before the prefix and code after it.

frames:
[0] VERIFY target: ∅ (self) flags: 3 gas: 30,000 data: 0x01
[1] SENDER target: ∅ (self) gas: 520,000
data: newActivePk (1,952 B) ‖ keccak256(newBackupPk) (32 B)
signatures:
[0] {ARBITRARY, msg: ∅, signature: ML-DSA-65 by the BACKUP key} 3,309 B
[1] {ARBITRARY, msg: ∅, signature: the backup public key blob} 1,952 B

The second entry is a public key, not a signature. It is elided from the sig-hash but authenticated against state — the account hashes it and compares against its stored backup commitment.

Frame transaction receipts carry an overall status, a per-frame receipt array, and a payer field naming the account charged. Each frame’s status is 1 (succeeded), 0 (reverted or ran out of gas) or 3 (skipped by an atomic-batch rollback), so a reverted SENDER frame is visible in the receipt.

A VERIFY frame that fails or does not APPROVE is different: it makes the whole transaction invalid. Block builders skip an invalid frame transaction deterministically, so it is never included and gets no receipt at all — a transaction that was acknowledged but has no receipt is the symptom to look for.

  • Validate against vectors, not prose. The reference implementation ships pinned test vectors covering every encoding on this page.
  • Do not re-hash a stripped transaction. Below the pruning horizon, eth_getTransactionByHash returns elided entries empty. Those bytes decode but do not hash to the canonical txid. Reassemble from the sidecar, or use a proof.
  • Nonces are contract nonces. A rotatable account is at 3 after its first send, a fixed-key v1 account at 2; spends add 1 and rotations add 2. Always read eth_getTransactionCount.
  • Submit to the ingress. Batched JSON-RPC is rejected.
  • Frame opcodes — the introspection surface frame code uses.
  • Account contracts — what is on the other end.
  • SDKs — implementations that already do all of this correctly.