Skip to main content
Access lists (EIP-2930) specify which addresses and storage slots a transaction will access, enabling gas savings by pre-warming these locations before execution.

Overview

AccessList is a branded array type representing EIP-2930 access lists. Each entry contains a contract address and the storage keys that will be accessed at that address.
export type Item = {
  address: AddressType;
  storageKeys: readonly HashType[];
};

export type BrandedAccessList = readonly Item[] & {
  readonly __brand?: typeof accessListSymbol;
};

Quick Start

import * as AccessList from '@tevm/voltaire/AccessList';
import * as Address from '@tevm/voltaire/Address';
import * as Hash from '@tevm/voltaire/Hash';

// Create empty access list
const empty = AccessList.create();

// Create from items
const list = AccessList.from([
  {
    address: Address("0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e"),
    storageKeys: [
      Hash("0x0000000000000000000000000000000000000000000000000000000000000001"),
      Hash("0x0000000000000000000000000000000000000000000000000000000000000002"),
    ]
  }
]);

API Reference

Constructors

MethodDescription
create()Create empty access list
from(items | bytes)Create from array of items or RLP bytes
fromBytes(bytes)Decode from RLP-encoded bytes

Gas Calculation

MethodDescription
gasCost(list)Total gas cost of access list itself
gasSavings(list)Net gas savings vs cold access
hasSavings(list)Returns true if access list saves gas

Queries

MethodDescription
includesAddress(list, address)Check if address is in list
includesStorageKey(list, address, key)Check if storage key is accessible
keysFor(list, address)Get all storage keys for address
addressCount(list)Count of addresses in list
storageKeyCount(list)Total storage keys across all addresses
isEmpty(list)Check if list has no entries

Manipulation

MethodDescription
withAddress(list, address)Add address (returns new list)
withStorageKey(list, address, key)Add storage key (returns new list)
merge(...lists)Combine multiple access lists
deduplicate(list)Remove duplicate addresses and keys

Validation & Conversion

MethodDescription
is(value)Type guard for AccessList
isItem(value)Type guard for AccessList Item
assertValid(list)Throws if list is invalid
toBytes(list)RLP-encode to bytes

Constants

ConstantValueDescription
ADDRESS_COST2400nGas per address in access list
STORAGE_KEY_COST1900nGas per storage key
COLD_ACCOUNT_ACCESS_COST2600nGas for cold account access
COLD_STORAGE_ACCESS_COST2100nGas for cold storage access
WARM_STORAGE_ACCESS_COST100nGas for warm storage access

Practical Examples

Transaction with Access List

import * as AccessList from '@tevm/voltaire/AccessList';
import * as Transaction from '@tevm/voltaire/Transaction';
import * as Address from '@tevm/voltaire/Address';

// Build access list for Uniswap swap
const accessList = AccessList.from([
  {
    address: Address("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), // Router
    storageKeys: []
  },
  {
    address: Address("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), // WETH
    storageKeys: [
      Hash("0x..."), // balanceOf[sender]
      Hash("0x..."), // balanceOf[pair]
    ]
  },
  {
    address: Address("0x..."), // Token
    storageKeys: [
      Hash("0x..."), // balanceOf[sender]
      Hash("0x..."), // balanceOf[pair]
      Hash("0x..."), // allowance[sender][router]
    ]
  }
]);

// Verify savings before including
if (AccessList.hasSavings(accessList)) {
  const tx = Transaction.create({
    type: 1, // EIP-2930
    accessList,
    // ... other fields
  });
}

Merging Access Lists from Simulation

import * as AccessList from '@tevm/voltaire/AccessList';

// Simulate multiple calls and collect access lists
const swap1Access = await simulateSwap(tokenA, tokenB, amount1);
const swap2Access = await simulateSwap(tokenB, tokenC, amount2);

// Merge and deduplicate
const combined = AccessList.merge(swap1Access, swap2Access);

console.log(`Combined: ${AccessList.addressCount(combined)} addresses`);
console.log(`Storage keys: ${AccessList.storageKeyCount(combined)}`);
console.log(`Net savings: ${AccessList.gasSavings(combined)} gas`);

Finding Accessed Storage

import * as AccessList from '@tevm/voltaire/AccessList';

const list = AccessList.from(simulatedAccessList);

// Check if specific contract is accessed
if (AccessList.includesAddress(list, proxyContract)) {
  const keys = AccessList.keysFor(list, proxyContract);
  console.log(`Proxy accesses ${keys?.length ?? 0} storage slots`);
}

// Check for specific storage slot access
const balanceSlot = Hash("0x...");
if (AccessList.includesStorageKey(list, tokenContract, balanceSlot)) {
  console.log("Transaction reads token balance");
}

When to Use Access Lists

Access lists provide net gas savings when:
  1. Multiple cold accesses: Accessing the same address/slot multiple times
  2. Complex DeFi operations: Swaps, lending, multi-hop transactions
  3. Batch operations: Multiple transfers or contract interactions
  4. Contract calls with many SLOAD: Storage-heavy operations
The break-even point:
  • Address: Saves 200 gas per address (2600 - 2400)
  • Storage key: Saves 200 gas per key (2100 - 1900)
Use hasSavings() to verify before including in transactions.

RLP Encoding

Access lists encode as RLP for transaction serialization:
// Format: [[address, [storageKey1, storageKey2, ...]], ...]
const bytes = AccessList.toBytes(list);
const decoded = AccessList.fromBytes(bytes);