Skip to main content

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)
}