Skip to main content

Documentation Index

Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt

Use this file to discover all available pages before exploring further.

Try it Live

Run Transaction examples in the interactive playground

Legacy Transactions

Original Ethereum transaction format with fixed gas price and EIP-155 replay protection.

Overview

Legacy transactions (Type 0) are the original transaction format used since Ethereum genesis. They use a fixed gasPrice and encode chain ID in the v signature component for replay protection (EIP-155).

Type Definition

type Legacy = {
  type: Type.Legacy       // 0x00
  nonce: bigint
  gasPrice: bigint        // Fixed gas price in wei
  gasLimit: bigint
  to: AddressType | null  // null for contract creation
  value: bigint           // Amount in wei
  data: Uint8Array        // Contract data or empty
  v: bigint               // Signature component + chain ID
  r: Uint8Array           // 32-byte signature
  s: Uint8Array           // 32-byte signature
}
Source: types.ts:59-72

Creating Legacy Transactions

import * as Transaction from 'tevm/Transaction'

// Simple transfer
const transfer: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  nonce: 0n,
  gasPrice: 20000000000n,  // 20 gwei
  gasLimit: 21000n,
  to: recipientAddress,
  value: 1000000000000000000n,  // 1 ETH
  data: new Uint8Array(),
  v: 37n,  // Chain ID 1
  r: signatureR,
  s: signatureS,
}

// Contract creation (to: null)
const deploy: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  nonce: 0n,
  gasPrice: 20000000000n,
  gasLimit: 3000000n,
  to: null,  // Contract creation
  value: 0n,
  data: contractBytecode,
  v: 37n,
  r: signatureR,
  s: signatureS,
}

// Contract call
const contractCall: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  nonce: 1n,
  gasPrice: 25000000000n,
  gasLimit: 100000n,
  to: contractAddress,
  value: 0n,
  data: encodedFunctionCall,
  v: 37n,
  r: signatureR,
  s: signatureS,
}

EIP-155: Chain ID Encoding

EIP-155 adds replay protection by encoding chain ID in the v value.

v Value Calculation

// Pre-EIP-155 (not recommended)
v = 27 + yParity  // yParity is 0 or 1

// Post-EIP-155 (recommended)
v = chainId * 2 + 35 + yParity

Examples

// Mainnet (chain ID 1)
v = 1 * 2 + 35 + 0 = 37  // yParity 0
v = 1 * 2 + 35 + 1 = 38  // yParity 1

// Goerli (chain ID 5)
v = 5 * 2 + 35 + 0 = 45  // yParity 0

// Polygon (chain ID 137)
v = 137 * 2 + 35 + 0 = 309  // yParity 0

getChainId

Extract chain ID from v value.
function getChainId(tx: BrandedTransactionLegacy): bigint | null

Returns

  • bigint - Chain ID if EIP-155 transaction
  • null - If pre-EIP-155 transaction (v = 27 or 28)

Usage

import { Legacy } from 'tevm/Transaction'

const tx: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  // ...
  v: 37n,
  // ...
}

const chainId = Legacy.getChainId.call(tx)
console.log(chainId)  // 1n

// Pre-EIP-155
const oldTx: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  // ...
  v: 27n,
  // ...
}

const noChainId = Legacy.getChainId.call(oldTx)
console.log(noChainId)  // null
Implementation:
if (tx.v === 27n || tx.v === 28n) {
  return null  // Pre-EIP-155
}
return (tx.v - 35n) / 2n  // EIP-155
Source: Legacy/getChainId.js

Methods

All standard transaction methods work with legacy transactions:
import { Legacy } from 'tevm/Transaction'

// Serialization
const bytes = Legacy.serialize.call(legacyTx)
const decoded = Legacy.deserialize(bytes)

// Hashing
const txHash = Legacy.hash.call(legacyTx)
const signingHash = Legacy.getSigningHash.call(legacyTx)

// Signing
const sender = Legacy.getSender.call(legacyTx)
const isValid = Legacy.verifySignature.call(legacyTx)

// Chain ID
const chainId = Legacy.getChainId.call(legacyTx)

RLP Encoding

Legacy transactions encode directly as RLP list (no type prefix):
rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])
Example:
// Transaction
{
  nonce: 9n,
  gasPrice: 20000000000n,
  gasLimit: 21000n,
  to: 0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e,
  value: 1000000000000000000n,
  data: 0x,
  v: 37n,
  r: 0x1234...,
  s: 0x5678...
}

// RLP encodes to:
// [0xf8, 0x6c, ...] - RLP list, no type prefix

Signing Hash

Legacy signing hash includes chain ID for EIP-155 transactions:
// Pre-EIP-155
signingHash = keccak256(rlp([nonce, gasPrice, gasLimit, to, value, data]))

// Post-EIP-155
signingHash = keccak256(rlp([nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]))
                                                                          ^^^^^^^^^^^^^^^
                                                                          adds chain ID + zeros

Gas Cost Calculation

Legacy transactions use simple gas cost:
const totalCost = gasPrice * gasUsed + value
Example:
const tx: Transaction.Legacy = {
  gasPrice: 20000000000n,  // 20 gwei
  gasLimit: 21000n,
  value: 1000000000000000000n,  // 1 ETH
  // ...
}

// Maximum cost
const maxCost = tx.gasPrice * tx.gasLimit + tx.value
// 20 gwei * 21000 + 1 ETH = 0.00042 ETH + 1 ETH = 1.00042 ETH

Limitations

Legacy transactions have several limitations compared to newer types:
  1. No access lists - Cannot pre-declare storage access
  2. Fixed gas price - No dynamic fee market support
  3. Chain ID in signature - Wastes space compared to explicit field
  4. No blob support - Cannot attach L2 data
  5. No authorization lists - Cannot delegate to contracts

Migration to EIP-1559

Converting legacy to EIP-1559:
function upgradeToEIP1559(
  legacy: Transaction.Legacy,
  baseFee: bigint,
  priorityFee = 1000000000n
): Transaction.EIP1559 {
  return {
    type: Transaction.Type.EIP1559,
    chainId: Legacy.getChainId.call(legacy) || 1n,
    nonce: legacy.nonce,
    maxPriorityFeePerGas: priorityFee,
    maxFeePerGas: baseFee + priorityFee,
    gasLimit: legacy.gasLimit,
    to: legacy.to,
    value: legacy.value,
    data: legacy.data,
    accessList: [],
    yParity: Number(legacy.v % 2n),
    r: legacy.r,
    s: legacy.s,
  }
}

When to Use

Legacy transactions should be used:
  • When required by older infrastructure
  • On networks without EIP-1559 support
  • When interacting with wallets that only support legacy format
Prefer EIP-1559 for:
  • Modern Ethereum mainnet
  • Better gas price estimation
  • More predictable transaction costs
  • Access list optimization

EIP Reference