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

Transaction Utilities

Utility methods for working with transactions across all types.

format

Format transaction to human-readable string.
function format(tx: Any): string

Usage

import { format } from 'tevm/Transaction'

const tx: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  nonce: 5n,
  to: recipientAddress,
  value: 1500000000000000000n,
  // ...
}

const formatted = format(tx)
// "EIP-1559 tx to 0x742d35Cc..., value: 1.5 ETH, nonce: 5"

// Contract creation
const deploy: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  nonce: 0n,
  to: null,
  value: 0n,
  // ...
}

format(deploy)
// "Legacy tx contract creation, value: 0 ETH, nonce: 0"
Source: format.ts:6-20

getGasPrice

Get transaction gas price (handles all types).
function getGasPrice(tx: Any, baseFee?: bigint): bigint

Parameters

  • tx: Any - Any transaction type
  • baseFee?: bigint - Required for EIP-1559+ transactions

Returns

bigint - Gas price in wei

Throws

  • Error("baseFee required for EIP-1559+ transactions") - If baseFee missing for dynamic fee transactions

Usage

import { getGasPrice } from 'tevm/Transaction'

// Legacy/EIP-2930: Returns fixed gasPrice
const legacyTx: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  gasPrice: 20000000000n,
  // ...
}
getGasPrice(legacyTx)  // 20000000000n

// EIP-1559+: Calculates effective gas price
const eip1559Tx: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  maxPriorityFeePerGas: 2000000000n,
  maxFeePerGas: 30000000000n,
  // ...
}
const baseFee = 15000000000n
getGasPrice(eip1559Tx, baseFee)  // 17000000000n (15 + 2)

// Missing baseFee throws
try {
  getGasPrice(eip1559Tx)  // Error!
} catch (e) {
  console.error(e.message)  // "baseFee required for EIP-1559+ transactions"
}
Source: getGasPrice.ts:16-38

hasAccessList

Check if transaction supports access lists.
function hasAccessList(tx: Any): boolean

Returns

  • true - For EIP-2930, EIP-1559, EIP-4844, EIP-7702
  • false - For Legacy

Usage

import { hasAccessList, getAccessList } from 'tevm/Transaction'

const tx: Transaction.Any = /* ... */

if (hasAccessList(tx)) {
  const accessList = getAccessList(tx)
  console.log('Access list:', accessList)
} else {
  console.log('No access list support')
}
Source: hasAccessList.ts:7-9

getAccessList

Get transaction access list.
function getAccessList(tx: Any): AccessList

Returns

  • AccessList - Transaction access list
  • [] - Empty array for Legacy transactions

Usage

import { getAccessList } from 'tevm/Transaction'

// EIP-1559 with access list
const eip1559: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  accessList: [
    { address: addr1, storageKeys: [key1, key2] }
  ],
  // ...
}
getAccessList(eip1559)
// [{ address: addr1, storageKeys: [key1, key2] }]

// Legacy returns empty
const legacy: Transaction.Legacy = { /* ... */ }
getAccessList(legacy)  // []
Source: getAccessList.ts:7-12

getChainId

Get transaction chain ID.
function getChainId(tx: Any): bigint | null

Returns

  • bigint - Chain ID
  • null - For pre-EIP-155 Legacy transactions

Usage

import { getChainId } from 'tevm/Transaction'

// EIP-1559 (explicit chainId field)
const eip1559: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  chainId: 1n,
  // ...
}
getChainId(eip1559)  // 1n

// Legacy with EIP-155
const legacy: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  v: 37n,  // chainId = (37 - 35) / 2 = 1
  // ...
}
getChainId(legacy)  // 1n

// Pre-EIP-155 Legacy
const oldLegacy: Transaction.Legacy = {
  type: Transaction.Type.Legacy,
  v: 27n,
  // ...
}
getChainId(oldLegacy)  // null
Source: getChainId.ts:8-13

isSigned

Check if transaction has a signature.
function isSigned(tx: Any): boolean

Returns

  • true - If transaction has non-zero r and s
  • false - If r or s is all zeros

Usage

import { isSigned, getSender } from 'tevm/Transaction'

const signedTx: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  r: Bytes32().fill(1),
  s: Bytes32().fill(2),
  // ...
}
isSigned(signedTx)  // true

const unsignedTx: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  r: Bytes32(),  // All zeros
  s: Bytes32(),  // All zeros
  // ...
}
isSigned(unsignedTx)  // false

// Use for conditional logic
if (isSigned(tx)) {
  const sender = getSender(tx)
  console.log('Signed by:', sender)
}
Source: isSigned.ts:7-14

assertSigned

Assert transaction is signed (throws if not).
function assertSigned(tx: Any): void

Throws

  • Error("Transaction is not signed") - If r or s is all zeros

Usage

import { assertSigned, getSender } from 'tevm/Transaction'

try {
  assertSigned(tx)
  // Transaction is signed, safe to get sender
  const sender = getSender(tx)
  processTransaction(tx, sender)
} catch (e) {
  console.error('Cannot process unsigned transaction')
  return
}
Source: assertSigned.ts:6-13

Usage Patterns

Transaction Cost Calculation

import { getGasPrice } from 'tevm/Transaction'

function calculateMaxCost(
  tx: Transaction.Any,
  baseFee?: bigint
): bigint {
  const gasPrice = getGasPrice(tx, baseFee)
  return tx.gasLimit * gasPrice + tx.value
}

// Usage
const tx: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  maxPriorityFeePerGas: 2000000000n,
  maxFeePerGas: 30000000000n,
  gasLimit: 21000n,
  value: 1000000000000000000n,
  // ...
}

const maxCost = calculateMaxCost(tx, 15000000000n)
// gasLimit * effectiveGasPrice + value
// 21000 * 17000000000 + 1000000000000000000

Replay Protection Check

import { getChainId } from 'tevm/Transaction'
import { TransactionError } from 'tevm/errors'

function validateChainId(
  tx: Transaction.Any,
  expectedChainId: bigint
): void {
  const txChainId = getChainId(tx)

  if (txChainId === null) {
    throw new TransactionError('Transaction has no chain ID (pre-EIP-155)', {
      code: 'MISSING_CHAIN_ID',
      context: { txType: tx.type }
    })
  }

  if (txChainId !== expectedChainId) {
    throw new TransactionError(
      `Wrong chain: expected ${expectedChainId}, got ${txChainId}`,
      {
        code: 'WRONG_CHAIN_ID',
        context: { expected: expectedChainId, actual: txChainId }
      }
    )
  }
}

Access List Extraction

import { hasAccessList, getAccessList } from 'tevm/Transaction'

function extractAddresses(tx: Transaction.Any): Set<AddressType> {
  const addresses = new Set<AddressType>()

  // Add sender
  if (isSigned(tx)) {
    addresses.add(getSender(tx))
  }

  // Add recipient
  if (tx.to) {
    addresses.add(tx.to)
  }

  // Add access list addresses
  if (hasAccessList(tx)) {
    const accessList = getAccessList(tx)
    for (const item of accessList) {
      addresses.add(item.address)
    }
  }

  return addresses
}

Transaction Display

import { format, getGasPrice, getChainId } from 'tevm/Transaction'

function displayTransaction(
  tx: Transaction.Any,
  baseFee?: bigint
): void {
  console.log('Transaction Details')
  console.log('-------------------')
  console.log(format(tx))
  console.log('Chain ID:', getChainId(tx) || 'N/A')

  try {
    const gasPrice = getGasPrice(tx, baseFee)
    console.log('Gas Price:', gasPrice, 'wei')
  } catch {
    console.log('Gas Price: Requires base fee')
  }

  console.log('Signed:', isSigned(tx) ? 'Yes' : 'No')

  if (hasAccessList(tx)) {
    const accessList = getAccessList(tx)
    console.log('Access List Items:', accessList.length)
  }
}

Signature Validation

import { isSigned, assertSigned, verifySignature, getSender } from 'tevm/Transaction'

function validateTransaction(
  tx: Transaction.Any,
  expectedSender?: AddressType
): { valid: boolean; sender?: AddressType; error?: string } {
  // Check if signed
  if (!isSigned(tx)) {
    return { valid: false, error: 'Transaction not signed' }
  }

  // Verify signature
  if (!verifySignature(tx)) {
    return { valid: false, error: 'Invalid signature' }
  }

  // Get sender
  const sender = getSender(tx)

  // Check expected sender if provided
  if (expectedSender && !Address.equals(sender, expectedSender)) {
    return {
      valid: false,
      error: `Wrong sender: expected ${Address.toHex(expectedSender)}, got ${Address.toHex(sender)}`
    }
  }

  return { valid: true, sender }
}

Transaction Pool Entry

import {
  isSigned,
  getSender,
  getChainId,
  getGasPrice,
  hash
} from 'tevm/Transaction'

interface PoolEntry {
  tx: Transaction.Any
  hash: HashType
  sender: AddressType
  gasPrice: bigint
  timestamp: number
}

function createPoolEntry(
  tx: Transaction.Any,
  baseFee?: bigint
): PoolEntry {
  assertSigned(tx)

  return {
    tx,
    hash: hash(tx),
    sender: getSender(tx),
    gasPrice: getGasPrice(tx, baseFee),
    timestamp: Date.now()
  }
}

Performance Tips

  1. Cache getSender results - ECDSA recovery is expensive
  2. Batch getGasPrice calls - Reuse baseFee for multiple transactions
  3. Check isSigned before getSender - Avoid unnecessary recovery attempts
  4. Use hasAccessList before getAccessList - Skip unnecessary checks
// Bad: Redundant checks
if (tx.type !== Transaction.Type.Legacy) {
  const accessList = getAccessList(tx)
}

// Good: Single check
if (hasAccessList(tx)) {
  const accessList = getAccessList(tx)
}