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: 0 n ,
gasPrice: 20000000000 n , // 20 gwei
gasLimit: 21000 n ,
to: recipientAddress ,
value: 1000000000000000000 n , // 1 ETH
data: new Uint8Array (),
v: 37 n , // Chain ID 1
r: signatureR ,
s: signatureS ,
}
// Contract creation (to: null)
const deploy : Transaction . Legacy = {
type: Transaction . Type . Legacy ,
nonce: 0 n ,
gasPrice: 20000000000 n ,
gasLimit: 3000000 n ,
to: null , // Contract creation
value: 0 n ,
data: contractBytecode ,
v: 37 n ,
r: signatureR ,
s: signatureS ,
}
// Contract call
const contractCall : Transaction . Legacy = {
type: Transaction . Type . Legacy ,
nonce: 1 n ,
gasPrice: 25000000000 n ,
gasLimit: 100000 n ,
to: contractAddress ,
value: 0 n ,
data: encodedFunctionCall ,
v: 37 n ,
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: 37 n ,
// ...
}
const chainId = Legacy . getChainId . call ( tx )
console . log ( chainId ) // 1n
// Pre-EIP-155
const oldTx : Transaction . Legacy = {
type: Transaction . Type . Legacy ,
// ...
v: 27 n ,
// ...
}
const noChainId = Legacy . getChainId . call ( oldTx )
console . log ( noChainId ) // null
Implementation:
if ( tx . v === 27 n || tx . v === 28 n ) {
return null // Pre-EIP-155
}
return ( tx . v - 35 n ) / 2 n // 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 : 9 n ,
gasPrice : 20000000000 n ,
gasLimit : 21000 n ,
to : 0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e ,
value : 1000000000000000000 n ,
data : 0 x ,
v : 37 n ,
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: 20000000000 n , // 20 gwei
gasLimit: 21000 n ,
value: 1000000000000000000 n , // 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:
No access lists - Cannot pre-declare storage access
Fixed gas price - No dynamic fee market support
Chain ID in signature - Wastes space compared to explicit field
No blob support - Cannot attach L2 data
No authorization lists - Cannot delegate to contracts
Migration to EIP-1559
Converting legacy to EIP-1559:
function upgradeToEIP1559 (
legacy : Transaction . Legacy ,
baseFee : bigint ,
priorityFee = 1000000000 n
) : Transaction . EIP1559 {
return {
type: Transaction . Type . EIP1559 ,
chainId: Legacy . getChainId . call ( legacy ) || 1 n ,
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 % 2 n ),
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