Skip to main content

Overview

BlockFilter represents a filter created by eth_newBlockFilter that notifies of new block hashes. Used for monitoring blockchain progress and detecting reorgs without polling full blocks.

Type Definition

type BlockFilterType = {
  readonly filterId: FilterIdType;
  readonly type: "block";
} & {
  readonly [brand]: "BlockFilter";
};

Creating BlockFilter

from

import * as BlockFilter from './primitives/BlockFilter/index.js';
import * as FilterId from './primitives/FilterId/index.js';

// Create filter on node
const filterIdStr = await rpc.eth_newBlockFilter();
const filterId = FilterId.from(filterIdStr);

// Wrap in BlockFilter type
const filter = BlockFilter.from(filterId);
Parameters:
  • filterId: FilterIdType - Filter identifier from eth_newBlockFilter
Returns: BlockFilterType

JSON-RPC Usage

Create Filter

// Returns filter ID
const filterIdStr = await rpc.eth_newBlockFilter();
const filterId = FilterId.from(filterIdStr);
const filter = BlockFilter.from(filterId);

Poll for Changes

// Returns array of new block hashes since last poll
const blockHashes = await rpc.eth_getFilterChanges(filter.filterId);

for (const hash of blockHashes) {
  console.log(`New block: ${hash}`);
}

Uninstall Filter

const success = await rpc.eth_uninstallFilter(filter.filterId);

Example: Block Monitor

import * as BlockFilter from './primitives/BlockFilter/index.js';
import * as FilterId from './primitives/FilterId/index.js';
import { Hash } from './primitives/Hash/index.js';

// Install block filter
const filterIdStr = await rpc.eth_newBlockFilter();
const filterId = FilterId.from(filterIdStr);
const filter = BlockFilter.from(filterId);

console.log(`Block filter installed: ${filterId.toString()}`);

// Poll every 15 seconds
const interval = setInterval(async () => {
  try {
    const hashes = await rpc.eth_getFilterChanges(filter.filterId);

    if (hashes.length > 0) {
      console.log(`New blocks: ${hashes.length}`);

      for (const hashStr of hashes) {
        const hash = Hash.from(hashStr);
        const block = await rpc.eth_getBlockByHash(hash, false);
        console.log(`Block ${block.number}: ${hashes.length} txs`);
      }
    }
  } catch (error) {
    console.error('Filter error:', error);
    clearInterval(interval);
  }
}, 15000);

// Cleanup on exit
process.on('SIGINT', async () => {
  await rpc.eth_uninstallFilter(filter.filterId);
  clearInterval(interval);
  process.exit();
});

Example: Reorg Detector

import * as BlockFilter from './primitives/BlockFilter/index.js';
import * as FilterId from './primitives/FilterId/index.js';

// Track block chain
const blockChain = new Map(); // hash -> block number
let lastBlockNumber = 0n;

// Install filter
const filterId = FilterId.from(await rpc.eth_newBlockFilter());
const filter = BlockFilter.from(filterId);

setInterval(async () => {
  const hashes = await rpc.eth_getFilterChanges(filter.filterId);

  for (const hashStr of hashes) {
    const hash = Hash.from(hashStr);
    const block = await rpc.eth_getBlockByHash(hash, false);

    // Check for reorg
    if (block.number <= lastBlockNumber) {
      console.warn(`REORG DETECTED at block ${block.number}`);
      console.warn(`Old hash: ${blockChain.get(block.number)}`);
      console.warn(`New hash: ${hash.toHex()}`);
    }

    blockChain.set(block.number, hash);
    lastBlockNumber = block.number;
  }
}, 15000);

Comparison with eth_subscribe

BlockFilter (eth_newBlockFilter)

Pros:
  • HTTP compatible (no WebSocket required)
  • Simple request-response pattern
  • Works with all RPC providers
Cons:
  • Polling-based (less efficient)
  • Delayed notifications (poll interval)
  • Filter expiration if not polled
// HTTP polling
const filterId = FilterId.from(await rpc.eth_newBlockFilter());
setInterval(async () => {
  const hashes = await rpc.eth_getFilterChanges(filterId);
  // Process hashes...
}, 15000);

eth_subscribe

Pros:
  • Real-time push notifications
  • More efficient (no polling)
  • No filter expiration
Cons:
  • Requires WebSocket connection
  • Not supported by all providers
  • More complex error handling
// WebSocket subscription
const subscription = await ws.eth_subscribe('newHeads');
subscription.on('data', (block) => {
  console.log(`New block: ${block.hash}`);
});

Filter Expiration

Block filters expire after inactivity (typically 5 minutes). Best practices:
async function pollBlockFilter(filterId: FilterIdType) {
  try {
    const hashes = await rpc.eth_getFilterChanges(filterId);
    return hashes;
  } catch (error) {
    if (error.message.includes('filter not found')) {
      // Recreate filter
      const newFilterId = FilterId.from(await rpc.eth_newBlockFilter());
      return pollBlockFilter(newFilterId);
    }
    throw error;
  }
}

Performance Considerations

Polling Frequency

Choose poll interval based on:
  • Block time (12s for Ethereum mainnet)
  • Filter expiration timeout
  • Application latency requirements
// Conservative: Poll every 15s (safe from expiration)
const interval = 15000;

// Aggressive: Poll every 6s (faster notifications, more requests)
const interval = 6000;

// Aligned: Poll on expected block time
const interval = 12000;

Batch Processing

Process multiple blocks efficiently:
const hashes = await rpc.eth_getFilterChanges(filterId);

// Fetch all blocks in parallel
const blocks = await Promise.all(
  hashes.map(hash => rpc.eth_getBlockByHash(hash, false))
);

// Process blocks
for (const block of blocks) {
  await processBlock(block);
}

Use Cases

Block Height Tracker

let currentBlock = 0n;

setInterval(async () => {
  const hashes = await rpc.eth_getFilterChanges(filterId);

  for (const hash of hashes) {
    const block = await rpc.eth_getBlockByHash(hash, false);
    currentBlock = block.number;
    console.log(`Current block: ${currentBlock}`);
  }
}, 15000);

Transaction Confirmation Monitor

async function waitForConfirmations(txHash, confirmations = 12) {
  const receipt = await rpc.eth_getTransactionReceipt(txHash);
  let currentBlock = receipt.blockNumber;

  const filterId = FilterId.from(await rpc.eth_newBlockFilter());

  return new Promise((resolve) => {
    const interval = setInterval(async () => {
      const hashes = await rpc.eth_getFilterChanges(filterId);

      if (hashes.length > 0) {
        const block = await rpc.eth_getBlockByHash(hashes[0], false);
        currentBlock = block.number;

        const confs = currentBlock - receipt.blockNumber;
        if (confs >= confirmations) {
          await rpc.eth_uninstallFilter(filterId);
          clearInterval(interval);
          resolve(confs);
        }
      }
    }, 15000);
  });
}

Network Activity Monitor

const filterId = FilterId.from(await rpc.eth_newBlockFilter());

setInterval(async () => {
  const hashes = await rpc.eth_getFilterChanges(filterId);

  if (hashes.length === 0) {
    console.log('No new blocks (network stalled?)');
    return;
  }

  const blocks = await Promise.all(
    hashes.map(hash => rpc.eth_getBlockByHash(hash, false))
  );

  const totalTxs = blocks.reduce((sum, b) => sum + b.transactions.length, 0);
  const avgGasUsed = blocks.reduce((sum, b) => sum + b.gasUsed, 0n) / BigInt(blocks.length);

  console.log(`Blocks: ${blocks.length}, Txs: ${totalTxs}, Avg Gas: ${avgGasUsed}`);
}, 15000);

JSON-RPC Methods

  • eth_newBlockFilter - Create block hash filter
  • eth_getFilterChanges - Poll for new blocks
  • eth_uninstallFilter - Remove filter
  • eth_getBlockByHash - Fetch full block

See Also