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
Section titled “Payload”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. A SENDER frame runs with
CALLER == tx.sender, and only after execution approval.
Signature entry
Section titled “Signature entry”| 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.
Signature hash and the elision rule
Section titled “Signature hash and the elision rule”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.
Static validity rules
Section titled “Static validity rules”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
valueonly in SENDER frames. - The atomic-batch flag (bit 2) is invalid on the last frame.
- Every entry’s
msgis empty or exactly 32 bytes, and a 32-bytemsgmust not be all-zero. - ARBITRARY entries must leave
signerabsent. - An ARBITRARY entry with a non-empty
msgcapssignatureat 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,flags0, zerovalue, anddataexactly 8 bytes — the big-endian expiry deadline.
Gas accounting
Section titled “Gas accounting” 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 {FRAME_LIMITS.permanentSigByteGas} per byte, non-elidable witnesses only+ Σ frame gas_limitTotal gas is not a field. It is derived, and the signed gas_limit must be at
least equal to 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.
Worked shapes
Section titled “Worked shapes”Spend from a rotatable ML-DSA-65 account
Section titled “Spend from a rotatable ML-DSA-65 account”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} {MLDSA_SIZES.mldsa65.signature.toLocaleString('en-US')} BRotation
Section titled “Rotation”frames: [0] VERIFY target: ∅ (self) flags: 3 gas: 30,000 data: 0x01 [1] SENDER target: ∅ (self) gas: 520,000 data: newActivePk ({MLDSA_SIZES.mldsa65.publicKey.toLocaleString('en-US')} B) ‖ keccak256(newBackupPk) (32 B)
signatures: [0] {ARBITRARY, msg: ∅, signature: ML-DSA-65 by the BACKUP key} {MLDSA_SIZES.mldsa65.signature.toLocaleString('en-US')} B [1] {ARBITRARY, msg: ∅, signature: the backup public key blob} {MLDSA_SIZES.mldsa65.publicKey.toLocaleString('en-US')} BThe 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.
Receipts
Section titled “Receipts”Frame transaction receipts carry an overall status, a per-frame receipt
array, and a payer field naming the account charged. A VERIFY frame that did
not approve and a SENDER frame that reverted are distinguishable.
Implementer notes
Section titled “Implementer notes”- 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_getTransactionByHashreturns 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.
See also
Section titled “See also”- 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.