Skip to main content

Try it Live

Run RLP examples in the interactive playground

RLP Types

RLP data type system defining bytes data, list data, and type guards for working with RLP structures.

Overview

RLP uses a discriminated union type to represent two kinds of data:
  • BytesData - Leaf nodes containing raw bytes
  • ListData - Nested arrays of RLP data
Type guards provide runtime type checking and TypeScript type narrowing for safe data access.

BrandedRlp

Core type representing RLP data structures.
export type BrandedRlp =
  | { type: "bytes"; value: Uint8Array }
  | { type: "list"; value: BrandedRlp[] }
Branded type using discriminated union pattern. The type field distinguishes between bytes and list variants. Properties:
  • type: "bytes" | "list" - Discriminant field
  • value: Uint8Array | BrandedRlp[] - Data payload
Source: BrandedRlp.ts:4-6

BytesData Variant

Represents a leaf node with raw bytes:
type BytesData = {
  type: "bytes"
  value: Uint8Array
}
Examples:
import { Rlp } from 'tevm'

// Single byte
const single: BrandedRlp = {
  type: 'bytes',
  value: new Uint8Array([0x7f])
}

// Empty bytes
const empty: BrandedRlp = {
  type: 'bytes',
  value: Bytes()
}

// Multiple bytes
const bytes: BrandedRlp = {
  type: 'bytes',
  value: new Uint8Array([1, 2, 3, 4, 5])
}

// Created via constructor
const data = Rlp(new Uint8Array([1, 2, 3]))
// => { type: 'bytes', value: Uint8Array([1, 2, 3]) }

ListData Variant

Represents a nested array of RLP data:
type ListData = {
  type: "list"
  value: BrandedRlp[]
}
Examples:
import { Rlp } from 'tevm'

// Empty list
const empty: BrandedRlp = {
  type: 'list',
  value: []
}

// List of bytes
const list: BrandedRlp = {
  type: 'list',
  value: [
    { type: 'bytes', value: new Uint8Array([1]) },
    { type: 'bytes', value: new Uint8Array([2]) }
  ]
}

// Nested lists
const nested: BrandedRlp = {
  type: 'list',
  value: [
    { type: 'bytes', value: new Uint8Array([1]) },
    {
      type: 'list',
      value: [
        { type: 'bytes', value: new Uint8Array([2]) },
        { type: 'bytes', value: new Uint8Array([3]) }
      ]
    }
  ]
}

// Created via constructor
const listData = Rlp([
  new Uint8Array([1]),
  new Uint8Array([2])
])
// => {
//   type: 'list',
//   value: [
//     { type: 'bytes', value: Uint8Array([1]) },
//     { type: 'bytes', value: Uint8Array([2]) }
//   ]
// }

Type Guards

Runtime type checking functions that narrow TypeScript types.

isData

Checks if value is any RLP data structure.

    Implementation

    Checks for required structure:
    1. Value is object (not null)
    2. Has type field
    3. Has value field
    4. Type is either “bytes” or “list”
    function isData(value) {
      return (
        typeof value === "object" &&
        value !== null &&
        "type" in value &&
        "value" in value &&
        (value.type === "bytes" || value.type === "list")
      )
    }
    

    isBytesData

    Checks if value is bytes data specifically.

      Implementation

      Uses isData then checks type field:
      function isBytesData(value) {
        return isData(value) && value.type === "bytes"
      }
      

      isListData

      Checks if value is list data specifically.

        Implementation

        Uses isData then checks type field:
        function isListData(value) {
          return isData(value) && value.type === "list"
        }
        

        Type Patterns

        Discriminated Union Pattern

        RLP uses TypeScript discriminated unions for type safety:
        import { Rlp } from 'tevm'
        
        function processRlpData(data: BrandedRlp) {
          // TypeScript uses 'type' field to narrow types
          if (data.type === 'bytes') {
            // Here, TypeScript knows data.value is Uint8Array
            console.log('Bytes length:', data.value.length)
            const byte = data.value[0]  // OK
          } else {
            // Here, TypeScript knows data.value is BrandedRlp[]
            console.log('List items:', data.value.length)
            const item = data.value[0]  // OK, item is BrandedRlp
          }
        }
        

        Type Narrowing with Guards

        Combine type guards with TypeScript narrowing:
        import { Rlp } from 'tevm'
        
        function extractBytes(data: unknown): Uint8Array[] {
          // First check if it's RLP data at all
          if (!Rlp.isData(data)) {
            return []
          }
        
          // Now TypeScript knows data is BrandedRlp
          if (Rlp.isBytesData(data)) {
            // Return single item
            return [data.value]
          }
        
          // Must be list data
          const bytes: Uint8Array[] = []
          for (const item of data.value) {
            // Recursively extract bytes
            bytes.push(...extractBytes(item))
          }
          return bytes
        }
        

        Exhaustive Type Checking

        Ensure all type variants are handled:
        import { Rlp } from 'tevm'
        
        function serializeRlp(data: BrandedRlp): string {
          switch (data.type) {
            case 'bytes':
              return `bytes(${data.value.length})`
            case 'list':
              return `list(${data.value.length})`
            default:
              // TypeScript error if new type added
              const _exhaustive: never = data
              return _exhaustive
          }
        }
        

        Working with Types

        Creating RLP Data

        Use Rlp() constructor for automatic type detection:
        import { Rlp } from 'tevm'
        
        // From bytes
        const bytesData = Rlp(new Uint8Array([1, 2, 3]))
        // => { type: 'bytes', value: Uint8Array([1, 2, 3]) }
        
        // From array (becomes list)
        const listData = Rlp([
          new Uint8Array([1]),
          new Uint8Array([2])
        ])
        // => { type: 'list', value: [...] }
        
        // Already RLP data (pass through)
        const existing = { type: 'bytes', value: new Uint8Array([1]) }
        const same = Rlp(existing)
        // => Returns existing unchanged
        

        Manual Construction

        Create structures directly:
        import { Rlp } from 'tevm'
        
        // Manual bytes data
        const bytes: BrandedRlp = {
          type: 'bytes',
          value: new Uint8Array([1, 2, 3])
        }
        
        // Manual list data
        const list: BrandedRlp = {
          type: 'list',
          value: [bytes]
        }
        
        // Nested structure
        const nested: BrandedRlp = {
          type: 'list',
          value: [
            { type: 'bytes', value: new Uint8Array([1]) },
            {
              type: 'list',
              value: [
                { type: 'bytes', value: new Uint8Array([2]) }
              ]
            }
          ]
        }
        

        Type-safe Access

        Use type guards for safe access:
        import { Rlp } from 'tevm'
        
        function getFirstByte(data: BrandedRlp): number | undefined {
          if (Rlp.isBytesData(data)) {
            return data.value[0]
          }
          if (Rlp.isListData(data) && data.value.length > 0) {
            return getFirstByte(data.value[0])
          }
          return undefined
        }
        
        function countItems(data: BrandedRlp): number {
          if (Rlp.isBytesData(data)) {
            return 1
          }
          if (Rlp.isListData(data)) {
            return data.value.reduce((sum, item) => sum + countItems(item), 0)
          }
          return 0
        }
        

        Type Validation

        Runtime Validation

        Type guards perform runtime validation:
        import { Rlp } from 'tevm'
        
        function validateRlpData(value: unknown): asserts value is BrandedRlp {
          if (!Rlp.isData(value)) {
            throw new Error('Invalid RLP data structure')
          }
        
          if (value.type === 'bytes') {
            if (!(value.value instanceof Uint8Array)) {
              throw new Error('Bytes data must have Uint8Array value')
            }
          } else if (value.type === 'list') {
            if (!Array.isArray(value.value)) {
              throw new Error('List data must have array value')
            }
            // Recursively validate items
            for (const item of value.value) {
              validateRlpData(item)
            }
          }
        }
        
        // Usage
        const data: unknown = JSON.parse(jsonString)
        validateRlpData(data)
        // Now TypeScript knows data is BrandedRlp
        

        Type Assertions

        Assert types when you know the structure:
        import { Rlp } from 'tevm'
        
        function processTransaction(data: BrandedRlp) {
          // We know transactions are lists
          if (data.type !== 'list') {
            throw new Error('Transaction must be list')
          }
        
          // We know list has 9 items
          if (data.value.length !== 9) {
            throw new Error('Invalid transaction')
          }
        
          // Access fields safely
          const [nonce, gasPrice, gas, to, value, calldata, v, r, s] = data.value
        
          // Each field should be bytes
          if (!Rlp.isBytesData(nonce)) {
            throw new Error('Nonce must be bytes')
          }
        }
        

        Encodable

        Input type for encoding operations:
        type Encodable =
          | Uint8Array
          | BrandedRlp
          | Array<Uint8Array | BrandedRlp | any>
        
        Accepts raw bytes, RLP data, or nested arrays. See Encoding.

        Decoded

        Output type from decoding operations:
        type Decoded = {
          data: BrandedRlp
          remainder: Uint8Array
        }
        
        Contains decoded RLP data and remaining bytes. See Decoding.

        Type Examples

        Transaction Type

        import { Rlp } from 'tevm'
        
        type Transaction = {
          nonce: Uint8Array
          gasPrice: Uint8Array
          gas: Uint8Array
          to: Uint8Array
          value: Uint8Array
          data: Uint8Array
          v: Uint8Array
          r: Uint8Array
          s: Uint8Array
        }
        
        function toRlp(tx: Transaction): BrandedRlp {
          return {
            type: 'list',
            value: [
              { type: 'bytes', value: tx.nonce },
              { type: 'bytes', value: tx.gasPrice },
              { type: 'bytes', value: tx.gas },
              { type: 'bytes', value: tx.to },
              { type: 'bytes', value: tx.value },
              { type: 'bytes', value: tx.data },
              { type: 'bytes', value: tx.v },
              { type: 'bytes', value: tx.r },
              { type: 'bytes', value: tx.s }
            ]
          }
        }
        
        function fromRlp(data: BrandedRlp): Transaction {
          if (data.type !== 'list' || data.value.length !== 9) {
            throw new Error('Invalid transaction')
          }
        
          const [nonce, gasPrice, gas, to, value, calldata, v, r, s] = data.value
        
          if (!data.value.every(Rlp.isBytesData)) {
            throw new Error('All fields must be bytes')
          }
        
          return {
            nonce: nonce.value,
            gasPrice: gasPrice.value,
            gas: gas.value,
            to: to.value,
            value: value.value,
            data: calldata.value,
            v: v.value,
            r: r.value,
            s: s.value
          }
        }
        

        Merkle Proof Type

        import { Rlp } from 'tevm'
        
        type MerkleProof = Uint8Array[]
        
        function proofToRlp(proof: MerkleProof): BrandedRlp {
          return {
            type: 'list',
            value: proof.map(node => ({
              type: 'bytes',
              value: node
            }))
          }
        }
        
        function proofFromRlp(data: BrandedRlp): MerkleProof {
          if (data.type !== 'list') {
            throw new Error('Proof must be list')
          }
        
          if (!data.value.every(Rlp.isBytesData)) {
            throw new Error('All proof nodes must be bytes')
          }
        
          return data.value.map(node => node.value)
        }