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.

Events

The events interface provides EventStream instances for robust event streaming with dynamic chunking, retry logic, and block context metadata.
This uses the Contract pattern - a copyable implementation you add to your codebase. EventStream itself is a library primitive.

Basic Usage

import { Contract } from './Contract.js';  // Your local copy

const usdc = Contract({
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  abi: erc20Abi,
  provider
});

// Get EventStream for Transfer events
const stream = usdc.events.Transfer({});

// Backfill historical events
for await (const { log, metadata } of stream.backfill({
  fromBlock: 18000000n,
  toBlock: 19000000n
})) {
  console.log(`Chain head ${metadata.chainHead}: ${log.args.value}`);
}

// Watch for new events
for await (const { log } of stream.watch()) {
  console.log('New transfer:', log.args);
}

Filtering Events

Filter by indexed parameters when creating the stream:
// Transfers from specific address
const stream = usdc.events.Transfer({ from: '0x742d35...' });

// Transfers to specific address
const stream = usdc.events.Transfer({ to: '0x742d35...' });

// Transfers between specific addresses
const stream = usdc.events.Transfer({
  from: '0xsender...',
  to: '0xreceiver...'
});

Backfill Historical Events

Use backfill() to fetch events from a specific block range:
const stream = usdc.events.Transfer({});

for await (const { log, metadata } of stream.backfill({
  fromBlock: 18000000n,
  toBlock: 18001000n
})) {
  console.log(`Found transfer at block ${log.blockNumber}`);
}
// Loop ends after processing historical events

Dynamic Chunking

EventStream automatically handles large block ranges by chunking requests:
  • Starts with 100 blocks per request
  • Reduces chunk size by 50% on “block range too large” errors
  • Increases chunk size by 25% after 5 consecutive successes
  • Never goes below 10 blocks minimum
// Handle million-block range efficiently
for await (const { log } of stream.backfill({
  fromBlock: 0n,
  toBlock: 19000000n,
  chunkSize: 200,       // Initial chunk size (default: 100)
  minChunkSize: 50      // Minimum after reduction (default: 10)
})) {
  processEvent(log);
}

Watch for New Events

Use watch() to poll for new events:
const stream = usdc.events.Transfer({});

for await (const { log, metadata } of stream.watch({
  pollingInterval: 1000  // Poll every second (default)
})) {
  console.log('New transfer:', log.eventName);
}

Watch from Specific Block

for await (const { log } of stream.watch({
  fromBlock: 19000000n  // Start watching from this block
})) {
  console.log(log);
}

Cancellation with AbortSignal

Use AbortSignal to cleanly stop streaming:
const controller = new AbortController();

// Stop after 10 seconds
setTimeout(() => controller.abort(), 10000);

try {
  for await (const { log } of stream.watch({
    signal: controller.signal
  })) {
    console.log(log);
  }
} catch (error) {
  if (error instanceof EventStreamAbortedError) {
    console.log('Stream stopped');
  }
}

Backfill Then Watch

Combine backfill and watch for complete event history:
const stream = usdc.events.Transfer({});

// First: get historical events
for await (const { log } of stream.backfill({
  fromBlock: 0n,
  toBlock: currentBlock
})) {
  processEvent(log);
}

// Then: watch for new events
for await (const { log } of stream.watch({
  fromBlock: currentBlock
})) {
  processEvent(log);
}

EventStreamResult Structure

Each yielded result contains the log and metadata:
type EventStreamResult = {
  log: {
    eventName: string;
    args: {
      from: AddressType;
      to: AddressType;
      value: bigint;
    };
    blockNumber: BlockNumberType;
    blockHash: HashType;
    transactionHash: TransactionHashType;
    logIndex: number;
  };
  metadata: {
    chainHead: bigint;      // Current chain head block number
    fromBlock: bigint;      // Chunk start block
    toBlock: bigint;        // Chunk end block
  };
};

Standalone EventStream

EventStream is a library primitive you can use directly:
import { EventStream } from './EventStream.js';

const stream = EventStream({
  provider,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  event: {
    type: 'event',
    name: 'Transfer',
    inputs: [
      { type: 'address', name: 'from', indexed: true },
      { type: 'address', name: 'to', indexed: true },
      { type: 'uint256', name: 'value', indexed: false }
    ]
  },
  filter: { from: userAddress }
});

Retry Configuration

Configure retry behavior for transient errors:
for await (const { log } of stream.backfill({
  fromBlock: 0n,
  toBlock: 1000000n,
  retry: {
    maxRetries: 5,         // Max retry attempts (default: 3)
    initialDelay: 1000,    // Initial delay ms (default: 1000)
    maxDelay: 30000        // Max delay ms (default: 30000)
  }
})) {
  processEvent(log);
}

Error Handling

import { BlockRangeTooLargeError, EventStreamAbortedError } from './EventStream.js';

try {
  for await (const { log } of stream.backfill({ fromBlock: 0n, toBlock: 1000000n })) {
    console.log(log);
  }
} catch (error) {
  if (error instanceof EventStreamAbortedError) {
    console.log('Stream was cancelled');
  } else if (error instanceof BlockRangeTooLargeError) {
    console.log('Block range exceeded limit');
  } else {
    console.error('Unexpected error:', error);
  }
}