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.
- 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.
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.
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.
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.
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.
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 0xc0–0xff. A typed transaction is defined to use a type byte in 0x00–0x7f. 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 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.
- 1 one byte, 0x00–0x7f — names the format
- 2 opaque bytes only that type knows how to decode
→ Step 4: watch the family grow.
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.”