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.
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;
}
}
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