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
TopicFilter is an array-based filter for matching event topics (indexed parameters) in Ethereum logs. Topics use AND logic across positions and OR logic within arrays, enabling precise event filtering for applications like DEX trackers and wallet monitors.
Type Definition
type TopicEntry = HashType | readonly HashType[] | null;
type TopicFilterType = readonly [
TopicEntry?,
TopicEntry?,
TopicEntry?,
TopicEntry?
] & {
readonly [brand]: "TopicFilter";
};
Topic Positions
Events can have up to 4 topics (0-3):
- topic0: Event signature hash (required for non-anonymous events)
- topic1-3: Indexed parameters (if declared with
indexed in Solidity)
event Transfer(address indexed from, address indexed to, uint256 value);
// topic1 topic2 (not indexed, in data)
Creating TopicFilter
from
import * as TopicFilter from './primitives/TopicFilter/index.js';
import { Hash } from './primitives/Hash/index.js';
// Match specific event
const filter = TopicFilter.from([eventSigHash]);
// Match with wildcards
const filter2 = TopicFilter.from([eventSigHash, null, recipientHash]);
// Match any of multiple values (OR)
const filter3 = TopicFilter.from([[eventSig1, eventSig2]]);
Parameters:
topics: Array of up to 4 entries, each being:
HashType - Match this specific hash
HashType[] - Match any of these hashes (OR)
null - Wildcard, matches anything
Returns: TopicFilterType
Throws: InvalidTopicFilterError if array has >4 entries or invalid hashes
Operations
matches
const isMatch = TopicFilter.matches(filter, log.topics);
Checks if a log’s topics match the filter criteria. Uses:
- AND logic across positions: All non-null positions must match
- OR logic within arrays: Any hash in array matches
Examples:
// Filter: [eventSig, null, recipientHash]
TopicFilter.matches(filter, [eventSig, senderHash, recipientHash]); // true
TopicFilter.matches(filter, [eventSig, anyHash, recipientHash]); // true
TopicFilter.matches(filter, [eventSig, anyHash, wrongHash]); // false
// Filter: [[eventSig1, eventSig2], null]
TopicFilter.matches(filter, [eventSig1, anyHash]); // true
TopicFilter.matches(filter, [eventSig2, anyHash]); // true
TopicFilter.matches(filter, [eventSig3, anyHash]); // false
isEmpty
const empty = TopicFilter.isEmpty(filter);
Returns true if all positions are null or undefined (no filtering).
Matching Logic
Position-based AND
// ALL non-null positions must match
const filter = TopicFilter.from([hash1, hash2, hash3]);
// Matches: topics must be [hash1, hash2, hash3]
// Does NOT match: [hash1, hash2, wrongHash]
Array-based OR
// ANY hash in array matches
const filter = TopicFilter.from([[hash1, hash2]]);
// Matches: [hash1, ...] OR [hash2, ...]
// Does NOT match: [hash3, ...]
Wildcards
// null matches anything
const filter = TopicFilter.from([hash1, null, hash3]);
// Matches: [hash1, ANYTHING, hash3]
Common Patterns
Specific Event
import { EventSignature } from './primitives/EventSignature/index.js';
// Transfer(address,address,uint256)
const transferSig = EventSignature.fromSignature(
"Transfer(address,address,uint256)"
);
const filter = TopicFilter.from([transferSig]);
Event with Specific Sender
const senderHash = Address.from("0x...").toBytes32();
const filter = TopicFilter.from([transferSig, senderHash]);
Event with Specific Recipient
const recipientHash = Address.from("0x...").toBytes32();
const filter = TopicFilter.from([transferSig, null, recipientHash]);
Multiple Event Types
// Match Transfer OR Approval
const filter = TopicFilter.from([[transferSig, approvalSig]]);
Transactions Between Two Addresses
// From addr1 OR to addr1
const addr1Hash = Address.from("0x...").toBytes32();
const filter = TopicFilter.from([
transferSig,
[addr1Hash], // from addr1
[addr1Hash] // OR to addr1
]);
Bloom Filters
Ethereum blocks contain bloom filters that enable efficient topic matching without scanning all logs:
// Node first checks bloom filter
if (block.logsBloom.mightContainTopic(topic)) {
// Only then scan actual logs
const logs = block.logs.filter(log =>
TopicFilter.matches(filter, log.topics)
);
}
Bloom filter properties:
- Probabilistic data structure (false positives possible, no false negatives)
- 2048-bit (256-byte) bit array
- 3 hash functions per topic
- Enables fast block scanning for eth_getLogs
Example: DEX Trade Monitor
import * as TopicFilter from './primitives/TopicFilter/index.js';
import * as LogFilter from './primitives/LogFilter/index.js';
import { EventSignature } from './primitives/EventSignature/index.js';
import { Address } from './primitives/Address/index.js';
// Uniswap V2 Swap event
const swapSig = EventSignature.fromSignature(
"Swap(address,uint256,uint256,uint256,uint256,address)"
);
// WETH address as topic (if indexed)
const wethAddr = Address.from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
const wethHash = wethAddr.toBytes32();
// Match swaps involving WETH
const topics = TopicFilter.from([
swapSig,
null, // any sender
[wethHash, null] // WETH as token0 OR token1
]);
const filter = LogFilter.from({
fromBlock: "latest",
topics
});
// Poll for new swaps
const logs = await rpc.eth_getLogs(filter);
Bloom Filter Optimization
More specific filters = faster queries:
// FAST: Specific event + address
const filter1 = TopicFilter.from([eventSig, addrHash]);
// SLOWER: Event only (many addresses to check)
const filter2 = TopicFilter.from([eventSig]);
// SLOWEST: Wildcard (bloom filter can't help)
const filter3 = TopicFilter.from([null]);
Node Resource Limits
Nodes may limit:
- Query block range (e.g., 10,000 blocks max)
- Number of results (e.g., 10,000 logs max)
- Query complexity (multiple OR conditions)
Best practices:
- Use smallest block ranges possible
- Filter by contract address when possible
- Add topic filters to reduce result set
- Paginate large queries by block range
See Also