Try it Live Run RLP examples in the interactive playground
Encodable Type
Encodable is a union type accepting any input that can be encoded to RLP:
type Encodable =
| Uint8Array
| BrandedRlp
| Array < Uint8Array | BrandedRlp | any >
Accepted Types
Uint8Array - Raw byte arrays (encoded as RLP strings):
const bytes = new Uint8Array ([ 1 , 2 , 3 ])
const encoded = Rlp . encode ( bytes )
// => Uint8Array([0x83, 1, 2, 3])
BrandedRlp - RLP data structures:
const data = Rlp ( new Uint8Array ([ 1 , 2 , 3 ]))
const encoded = Rlp . encode ( data )
Arrays - Encoded as RLP lists:
const list = [ new Uint8Array ([ 1 ]), new Uint8Array ([ 2 ])]
const encoded = Rlp . encode ( list )
// => Uint8Array([0xc4, 0x01, 0x02])
Nested Arrays - Arbitrary nesting supported:
const nested = [
new Uint8Array ([ 1 ]),
[
new Uint8Array ([ 2 ]),
[ new Uint8Array ([ 3 ])]
]
]
const encoded = Rlp . encode ( nested )
Usage Patterns
Transaction Encoding
Ethereum transactions use RLP encoding for signing and broadcasting:
import { Rlp } from 'tevm'
// Legacy transaction: [nonce, gasPrice, gas, to, value, data, v, r, s]
const txData = [
new Uint8Array ([ 0x00 ]), // nonce
new Uint8Array ([ 0x04 , 0xa8 , 0x17 , 0xc8 ]), // gasPrice: 78000000
new Uint8Array ([ 0x52 , 0x08 ]), // gas: 21000
new Uint8Array ( 20 ). fill ( 0x01 ), // to (address)
new Uint8Array ([ 0x00 ]), // value: 0
Bytes (), // data: empty
new Uint8Array ([ 0x1b ]), // v: 27
Bytes32 (). fill ( 0x02 ), // r: signature r
Bytes32 (). fill ( 0x03 ) // s: signature s
]
const encoded = Rlp . encode ( txData )
// Ready for broadcast or signature verification
Block Encoding
Block headers are RLP-encoded lists:
import { Rlp } from 'tevm'
// Simplified block header
const header = [
Bytes32 (). fill ( 0x01 ), // parentHash
Bytes32 (). fill ( 0x02 ), // uncleHash
new Uint8Array ( 20 ). fill ( 0x03 ), // coinbase (miner address)
Bytes32 (). fill ( 0x04 ), // stateRoot
Bytes32 (). fill ( 0x05 ), // transactionsRoot
Bytes32 (). fill ( 0x06 ), // receiptsRoot
new Uint8Array ( 256 ). fill ( 0x00 ), // logsBloom
new Uint8Array ([ 0x01 ]), // difficulty
new Uint8Array ([ 0x01 ]), // number
new Uint8Array ([ 0x5f , 0x5e , 0x100 ]), // gasLimit
new Uint8Array ([ 0x00 ]), // gasUsed
Bytes4 (), // timestamp
Bytes () // extraData
]
const encoded = Rlp . encode ( header )
// Use for block hash calculation
const blockHash = keccak256 ( encoded )
Custom Schema
Define typed structures with RLP encoding:
import { Rlp } from 'tevm'
interface StorageProof {
key : Uint8Array
value : Uint8Array
proof : Uint8Array []
}
function encodeStorageProof ( proof : StorageProof ) : Uint8Array {
return Rlp . encode ([
proof . key ,
proof . value ,
proof . proof // Nested list of proof nodes
])
}
const proof : StorageProof = {
key: new Uint8Array ([ 0x01 , 0x02 ]),
value: new Uint8Array ([ 0x03 , 0x04 ]),
proof: [
Bytes32 (). fill ( 0x05 ),
Bytes32 (). fill ( 0x06 )
]
}
const encoded = encodeStorageProof ( proof )
Encoding Rules
RLP encoding uses the first byte (prefix) to indicate data type and length:
Single Byte (0x00-0x7f)
Bytes with value < 0x80 encode as themselves:
const byte = new Uint8Array ([ 0x7f ])
const encoded = Rlp . encode ( byte )
// => Uint8Array([0x7f]) (no prefix)
Short String (0-55 bytes)
For byte arrays 0-55 bytes, prefix with 0x80 + length:
// Empty bytes
const empty = Bytes ()
const encoded = Rlp . encode ( empty )
// => Uint8Array([0x80])
// 3 bytes
const bytes = new Uint8Array ([ 1 , 2 , 3 ])
const encoded = Rlp . encode ( bytes )
// => Uint8Array([0x83, 1, 2, 3])
// 0x83 = 0x80 + 3
Long String (56+ bytes)
For byte arrays 56+ bytes, use long form: [0xb7 + length_of_length, ...length_bytes, ...bytes]
// 56 bytes (minimum for long form)
const long = new Uint8Array ( 56 ). fill ( 0x42 )
const encoded = Rlp . encode ( long )
// => Uint8Array([0xb8, 56, ...long])
// 0xb8 = 0xb7 + 1 (length needs 1 byte)
// 256 bytes (length needs 2 bytes)
const longer = new Uint8Array ( 256 ). fill ( 0x42 )
const encoded = Rlp . encode ( longer )
// => Uint8Array([0xb9, 0x01, 0x00, ...longer])
// 0xb9 = 0xb7 + 2 (length needs 2 bytes)
// [0x01, 0x00] = 256 in big-endian
Short List (< 56 bytes total)
For lists with total payload < 56 bytes, prefix with 0xc0 + total_length:
// Empty list
const empty = []
const encoded = Rlp . encode ( empty )
// => Uint8Array([0xc0])
// List with 2 single bytes
const list = [ new Uint8Array ([ 0x01 ]), new Uint8Array ([ 0x02 ])]
const encoded = Rlp . encode ( list )
// => Uint8Array([0xc2, 0x01, 0x02])
// 0xc2 = 0xc0 + 2
Long List (56+ bytes total)
For lists with total payload >= 56 bytes, use long form: [0xf7 + length_of_length, ...length_bytes, ...encoded_items]
// Create list with 60 bytes total payload
const items = Array ({ length: 30 }, () => new Uint8Array ([ 0x01 , 0x02 ]))
const encoded = Rlp . encode ( items )
// First bytes: [0xf8, 60, ...]
// 0xf8 = 0xf7 + 1 (length needs 1 byte)
Algorithm
The encode method dispatches to specialized encoders:
Uint8Array → Uses byte string encoding
BrandedRlp (bytes) → Encodes as byte string
BrandedRlp (list) → Encodes as list
Array → Recursively encodes each element as list
// These are equivalent
const manual = Rlp . encodeList ([
Rlp . encodeBytes ( new Uint8Array ([ 1 ])),
Rlp . encodeBytes ( new Uint8Array ([ 2 ]))
])
const automatic = Rlp . encode ([
new Uint8Array ([ 1 ]),
new Uint8Array ([ 2 ])
])
Use encode for general-purpose encoding. Use encodeBytes or encodeList when you know the specific type for slightly better performance.
Pre-sizing Buffers
Calculate size before encoding for better performance:
import { Rlp } from 'tevm'
const items = [
new Uint8Array ([ 1 , 2 , 3 ]),
new Uint8Array ([ 4 , 5 , 6 ])
]
// Calculate size without encoding
const size = Rlp . getEncodedLength ( items )
console . log ( `Will need ${ size } bytes` )
// Then encode
const encoded = Rlp . encode ( items )
Tree-shaking
Use specific encoders when type is known:
import { encodeBytes , encodeList } from 'tevm/BrandedRlp'
// More efficient than generic encode()
const bytesEncoded = encodeBytes ( new Uint8Array ([ 1 , 2 , 3 ]))
const listEncoded = encodeList ([ bytes1 , bytes2 ])
Caching
Cache encoded results when encoding the same data multiple times:
const data = new Uint8Array ([ 1 , 2 , 3 ])
const encoded = Rlp . encode ( data )
// Reuse cached result
for ( let i = 0 ; i < 1000 ; i ++ ) {
processEncoded ( encoded ) // No re-encoding
}
Encoding is allocation-heavy for large data structures. Consider using WASM implementation for performance-critical operations.
See Also