> ## 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.

# TopicFilter

> Event topic filter for matching indexed parameters

## 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

```typescript theme={null}
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)

```solidity theme={null}
event Transfer(address indexed from, address indexed to, uint256 value);
//              topic1             topic2           (not indexed, in data)
```

## Creating TopicFilter

### from

```typescript theme={null}
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

```typescript theme={null}
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:**

```typescript theme={null}
// 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

```typescript theme={null}
const empty = TopicFilter.isEmpty(filter);
```

Returns `true` if all positions are `null` or `undefined` (no filtering).

## Matching Logic

### Position-based AND

```typescript theme={null}
// 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

```typescript theme={null}
// ANY hash in array matches
const filter = TopicFilter.from([[hash1, hash2]]);

// Matches: [hash1, ...] OR [hash2, ...]
// Does NOT match: [hash3, ...]
```

### Wildcards

```typescript theme={null}
// null matches anything
const filter = TopicFilter.from([hash1, null, hash3]);

// Matches: [hash1, ANYTHING, hash3]
```

## Common Patterns

### Specific Event

```typescript theme={null}
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

```typescript theme={null}
const senderHash = Address.from("0x...").toBytes32();
const filter = TopicFilter.from([transferSig, senderHash]);
```

### Event with Specific Recipient

```typescript theme={null}
const recipientHash = Address.from("0x...").toBytes32();
const filter = TopicFilter.from([transferSig, null, recipientHash]);
```

### Multiple Event Types

```typescript theme={null}
// Match Transfer OR Approval
const filter = TopicFilter.from([[transferSig, approvalSig]]);
```

### Transactions Between Two Addresses

```typescript theme={null}
// 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:

```typescript theme={null}
// 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

```typescript theme={null}
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);
```

## Performance Considerations

### Bloom Filter Optimization

More specific filters = faster queries:

```typescript theme={null}
// 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

## Related Types

* [LogFilter](/primitives/log-filter) - Complete log filter with address and block range
* [FilterId](/primitives/filter-id) - Filter identifier for polling
* [EventLog](/primitives/event-log) - Log entry structure
* [EventSignature](/primitives/event-signature) - Event signature hash

## See Also

* [TopicFilter (Effect)](https://voltaire-effect.tevm.sh/primitives/topic-filter) - Effect.ts integration with Schema validation
* [Ethereum JSON-RPC eth\_getLogs](https://ethereum.github.io/execution-apis/api-documentation/)
* [Solidity Events Documentation](https://docs.soliditylang.org/en/latest/contracts.html#events)
* [Bloom Filters in Ethereum](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/#bloom-filters)
