Part III · Chapter 19 of 43 · Berlin

EIP-2718: the envelope that let Ethereum add transaction types

Ethereum had exactly one transaction format — an RLP list. Every new feature (access lists, the 1559 fee market, blobs, set-code) needed a new shape, and decoders had to guess which one they were looking at. EIP-2718 prefixes every transaction with a single type byte, turning one rigid format into an extensible, versioned family.

Updated Jun 23, 2026 · 11 min
Assumed
  • RLP encoding
  • what a transaction is

For years Ethereum had exactly one kind of transaction: a single RLP Recursive Length Prefix — Ethereum's canonical serialization. It encodes nested lists and byte strings; a transaction was the RLP encoding of the 9-element list [nonce, gasPrice, gasLimit, to, value, data, v, r, s]. -encoded list of nine fields. That was fine until people wanted to change what a transaction could be — add access lists, replace gasPrice with the 1559 base-fee/tip pair, carry blob commitments, attach a set-code authorization. Each new feature needs a new on-the-wire shape, and the moment there’s more than one shape, every decoder faces a question it was never built to answer: which kind of transaction is this? EIP-2718 is the small, unglamorous fix that made all the famous EIPs after it possible. Let’s derive it.

1 step One format, many wishes

The problem: a single shape that everyone must guess at

The original transaction is rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s]) — nothing more. There’s no version field, no tag, no marker. To add a feature you have to change that list: add an accessList, swap in new fee fields. But now two different transactions are floating around the network, and the only way to tell them apart is to sniff the RLP — count the fields, inspect the structure, and guess. Guesses are fragile: two formats can look alarmingly alike, and every wallet, node, and explorer needs the same brittle heuristic, kept in sync forever.

one shape for every transaction nonce gasPrice gas to value data v r s + access list? + new fee fields? decoder: which kind? guess add a feature and decoders must sniff the bytes to guess the kind
One RLP shape for every transaction, with no version tag. Add a feature and the bytes change, so every decoder must sniff the structure and guess which kind it's holding — fragile, and it gets worse with each new format.

There’s no clean way to say “this is a new kind of transaction, decode it with these rules.” The format has no room to announce itself.

→ Step 2: put a label on the envelope.

2 step Prefix a type byte

Wrap it in an envelope: one type byte, then the payload

Stop trying to encode the format inside the data. Instead, put a label on the outside. Define a typed transaction EIP-2718's envelope: a transaction encoded as a single type byte followed by an opaque, type-specific payload — TransactionType ‖ TransactionPayload. The type byte names the format; the payload is whatever that format defines. as exactly two parts concatenated: TransactionType ‖ TransactionPayload. The first byte is a small number naming the format; everything after it is an opaque payload that only that type’s rules know how to read. A decoder reads one byte, looks up the matching ruleset, and decodes the rest with certainty — no sniffing, no guessing.

type byte payload 0x02 ++ format-specific bytes decode as EIP-1559 tx a typed tx = type byte ++ payload; the byte says how to read the rest
A typed transaction is a type byte followed by a type-specific payload. The leading byte tells every decoder exactly which ruleset to apply — read one byte, then decode the rest with certainty.

This is just a versioned envelope. The type byte is a version/format selector; the payload’s meaning is defined per type. Adding a new format no longer means mutating an existing shape — it means claiming a new number and writing down what its payload looks like.

→ Step 3: make the two kinds impossible to confuse.

3 step Never collide with legacy

Backward compatibility for free: pick bytes RLP can’t produce

Here’s the elegant part. A legacy transaction is an RLP list, and RLP encodes a list with a first byte in the range 0xc00xff. A typed transaction is defined to use a type byte in 0x000x7f. Those ranges cannot overlap — an RLP list never starts below 0xc0, and a type byte never reaches it. So a decoder needs only to glance at the very first byte: if it’s ≥ 0xc0, it’s a legacy RLP transaction; if it’s ≤ 0x7f, it’s typed, and the byte’s value says which type. One comparison, zero ambiguity, both kinds coexisting forever.

the first byte decides which decoder runs a first byte typed · type byte gap · unused legacy · RLP 0x000x7f0xc00xff an RLP list always starts ≥ 0xc0; type bytes are ≤ 0x7f the empty gap 0x80 to 0xbf means they can never collide
The first byte alone disambiguates. A legacy RLP list always starts ≥ 0xc0; typed transactions are assigned 0x00–0x7f, a range RLP can never produce. So old and new transactions coexist with no collisions and no sniffing.

The values 0xc0 and above are reserved precisely to keep legacy transactions valid, and 0xff is left aside (reserved). The design didn’t add a marker to old transactions — it chose the new marker’s range so the old encoding already disambiguates itself.

Formula
typed_tx = TransactionType 1 TransactionPayload 2
  1. 1 one byte, 0x00–0x7f — names the format
  2. 2 opaque bytes only that type knows how to decode
A single leading byte turns “the transaction format” into an extensible, versioned family.

→ Step 4: watch the family grow.

4 step A versioned family

The payoff: every later EIP just claims a byte

Once the envelope exists, adding a transaction format becomes routine. EIP-2930 took 0x01 for access lists; EIP-1559 took 0x02 for the base-fee/tip market; EIP-4844 took 0x03 for blob-carrying transactions; EIP-7702 took 0x04 for set-code authorizations. Each is just a number plus a payload definition. And crucially, an old client that doesn’t recognize a type byte cleanly rejects the transaction instead of misreading it — unknown means unknown, not “guess.”

a versioned family; each feature claims a byte 0x00 legacy (pre-2718) 0x01 access lists · EIP-2930 0x02 base fee + tip · EIP-1559 0x03 blobs · EIP-4844 0x04 set EOA code · EIP-7702 0x05 the next type slots in old clients cleanly reject an unknown type byte
With a versioned envelope, each new feature simply claims the next type byte and defines its payload — 0x01 access lists, 0x02 the 1559 fee market, 0x03 blobs, 0x04 set-code. Old clients cleanly reject an unknown type rather than misdecoding it.
04 Go Deeper Where to take it from here