Skip to main content

Try it Live

Run EventLog examples in the interactive playground

    Chain Reorganizations

    Chain reorganizations occur when a different chain becomes canonical, invalidating blocks and their logs:
    import { EventLog, Address, Hash } from 'tevm';
    
    // Original log (in canonical chain)
    const log = EventLog({
      address: tokenAddress,
      topics: [TRANSFER_SIG, fromHash, toHash],
      data: valueData,
      blockNumber: 18000000n,
      blockHash: Hash('0xabc...'),
      removed: false,
    });
    
    // After reorg, RPC marks log as removed
    const updatedLog = EventLog({
      ...log,
      removed: true,
    });
    
    console.log(log.isRemoved()); // false
    console.log(updatedLog.isRemoved()); // true
    

    Usage Patterns

    Filtering Active Logs

    import { EventLog } from 'tevm';
    
    // Remove invalidated logs
    const activeLogs = allLogs.filter(log => !log.isRemoved());
    
    console.log(`Active: ${activeLogs.length} of ${allLogs.length}`);
    

    Detecting Reorgs

    import { EventLog } from 'tevm';
    
    function detectReorg(newLogs: EventLog[]): void {
      const removed = newLogs.filter(log => log.isRemoved());
    
      if (removed.length > 0) {
        console.log(`⚠️  Chain reorg detected: ${removed.length} logs invalidated`);
    
        for (const log of removed) {
          console.log(`  Block ${log.blockNumber}, Log ${log.logIndex}`);
          console.log(`  Address: ${Address.toHex(log.address)}`);
        }
    
        // Reprocess affected blocks
        const affectedBlocks = new Set(
          removed.map(log => log.blockNumber).filter(n => n !== undefined)
        );
    
        for (const blockNum of affectedBlocks) {
          console.log(`  Reprocessing block ${blockNum}`);
          // Re-fetch and reprocess
        }
      }
    }
    
    detectReorg(latestLogs);
    

    Comparing Log Sets

    import { EventLog } from 'tevm';
    
    function compareLogSets(oldLogs: EventLog[], newLogs: EventLog[]): void {
      const oldActive = oldLogs.filter(log => !log.isRemoved());
      const newActive = newLogs.filter(log => !log.isRemoved());
      const newRemoved = newLogs.filter(log => log.isRemoved());
    
      console.log(`Old active: ${oldActive.length}`);
      console.log(`New active: ${newActive.length}`);
      console.log(`Newly removed: ${newRemoved.length}`);
    
      if (newRemoved.length > 0) {
        console.log('⚠️  Reorg detected!');
      }
    }
    

    Processing with Reorg Handling

    import { EventLog } from 'tevm';
    
    function processLogs(logs: EventLog[]): void {
      for (const log of logs) {
        if (log.isRemoved()) {
          console.log(`Skipping removed log at block ${log.blockNumber}`);
          continue;
        }
    
        // Process active log
        const sig = log.getTopic0();
        // ... decode and handle event
      }
    }
    
    processLogs(allLogs);
    

    Reorg-Aware Database

    import { EventLog } from 'tevm';
    
    class LogDatabase {
      private logs: Map<string, EventLog> = new Map();
    
      insert(log: EventLog): void {
        if (log.isRemoved()) {
          console.log('Refusing to insert removed log');
          return;
        }
    
        const key = this.makeKey(log);
        this.logs.set(key, log);
      }
    
      update(log: EventLog): void {
        const key = this.makeKey(log);
    
        if (log.isRemoved()) {
          console.log(`Removing invalidated log: ${key}`);
          this.logs.delete(key);
        } else {
          this.logs.set(key, log);
        }
      }
    
      private makeKey(log: EventLog): string {
        return `${log.blockNumber}-${log.logIndex}`;
      }
    }
    
    const db = new LogDatabase();
    
    // Initial insert
    db.insert(log);
    
    // After reorg
    const updatedLog = { ...log, removed: true };
    db.update(updatedLog); // Removes from database
    

    Transaction Receipt Processing

    import { EventLog } from 'tevm';
    
    function processReceipt(receipt: TransactionReceipt): void {
      const logs = receipt.logs.map(EventLog.from);
    
      const activeLogs = logs.filter(log => !log.isRemoved());
    
      if (activeLogs.length < logs.length) {
        console.log(`⚠️  ${logs.length - activeLogs.length} logs removed by reorg`);
      }
    
      // Process only active logs
      for (const log of activeLogs) {
        // ... handle event
      }
    }
    

    Default Behavior

    Logs without removed field default to active (not removed):
    import { EventLog, Address, Hash } from 'tevm';
    
    const log = EventLog({
      address: Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2'),
      topics: [Hash('0xddf252ad...')],
      data: Bytes(),
      // No removed field
    });
    
    console.log(log.removed); // undefined
    console.log(log.isRemoved()); // false (treats undefined as false)
    

    RPC Integration

    eth_getLogs and receipt endpoints include removed flag:
    import { EventLog, Address, Hash, Hex } from 'tevm';
    
    // RPC response includes removed flag
    const rpcLog = {
      address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2',
      topics: ['0xddf252ad...'],
      data: '0x0000...0001',
      blockNumber: '0x112a880',
      removed: true, // Set by RPC after reorg
    };
    
    const log = EventLog({
      address: Address(rpcLog.address),
      topics: rpcLog.topics.map(Hash.fromHex),
      data: Hex.toBytes(rpcLog.data),
      blockNumber: BigInt(rpcLog.blockNumber),
      removed: rpcLog.removed,
    });
    
    if (log.isRemoved()) {
      console.log('Log invalidated by chain reorganization');
    }
    

    See Also