# Event Filtering

Consumers connected to the hub WebSocket (`/hub/ws`) receive only events matching their subscription criteria. mx-notifier evaluates a `SubscribeEvent` protobuf message — declared at subscribe time — against every normalized event before delivery.

***

## Match levels

Five match levels are available, from broadest (receive everything) to most specific (match on address, identifier, and topic prefix):

| Level               | Matches when                                                                                                           |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `All`               | Always — the subscriber receives every event regardless of address, identifier, or topics                              |
| `Address`           | The event's `address` field equals the subscription address                                                            |
| `Identifier`        | The event's `identifier` field equals the subscription identifier (any address)                                        |
| `AddressIdentifier` | Both `address` AND `identifier` match the subscription values                                                          |
| `Topics`            | `address` and `identifier` match, AND the subscription topics are a prefix of the event topics (with wildcard support) |

{% hint style="info" %}
Match levels are evaluated in the order listed. `All` short-circuits all other checks. More specific levels require progressively more fields in the subscription.
{% endhint %}

***

## SubscribeEvent structure

```protobuf
message SubscribeEvent {
  string event_type   = 1;  // Match level: "All", "Address", "Identifier",
                            //   "AddressIdentifier", or "Topics"
  string address      = 2;  // Required for Address, AddressIdentifier, Topics
  string identifier   = 3;  // Required for Identifier, AddressIdentifier, Topics
  repeated string topics = 4;  // Required for Topics match level (base64-encoded bytes)
}
```

To subscribe, serialize a `SubscribeEvent` into a `WsMessage` PAYLOAD frame with `topic = "subscribe"` and send it over the hub WebSocket. See [connection.md](https://docs.xoxno.com/developers/notifier/connection) for frame encoding.

***

## Topic prefix matching

The `Topics` match level compares the subscription's `topics` list against the event's `topics` array using a **prefix match with wildcard support**.

1. Subscription topics are compared left-to-right against event topics.
2. The subscription list is a prefix — it must not be longer than the event topics array.
3. An **empty string** subscription topic acts as a wildcard and matches any value at that position.
4. All non-empty subscription topics must exactly match the corresponding event topic (base64-encoded bytes comparison).

### Matching algorithm

```
function topicsMatch(subscriptionTopics, eventTopics):
    if len(subscriptionTopics) > len(eventTopics):
        return false                       // prefix cannot exceed event length

    for i in 0..len(subscriptionTopics):
        if subscriptionTopics[i] == "":
            continue                       // wildcard: skip this position
        if subscriptionTopics[i] != eventTopics[i]:
            return false                   // mismatch at position i

    return true
```

### Base64 encoding

Event topics are raw bytes (token identifiers, nonces, amounts, addresses). In the `topics` field of `SubscribeEvent`, they are represented as **standard base64-encoded strings**.

For example, the token identifier `WEGLD-bd4d79` would be encoded as:

```
WEGLD-bd4d79  →  base64("WEGLD-bd4d79")  =  "V0VHTEQtYmQ0ZDc5"
```

{% hint style="warning" %}
Topics are compared as base64 strings, not decoded bytes. Encode topic values to base64 before placing them in the `topics` field. Sending raw UTF-8 strings without encoding produces no matches.
{% endhint %}

***

## Code examples

The following JavaScript examples show how to build `SubscribeEvent` messages for each match level. All examples assume a `WsMessage` protobuf type is loaded and a `sendFrame(msg)` helper serializes and sends binary WebSocket frames.

### All — receive every event

```javascript
const sub = SubscribeEvent.create({
  eventType: 'All',
});

sendSubscribeFrame(ws, sub);
```

### Address — monitor a specific contract

```javascript
// Receive all events emitted by a given contract address
const sub = SubscribeEvent.create({
  eventType: 'Address',
  address: 'erd1qqqqqqqqqqqqqpgqx7wfpk0qcxnyfq6xsxe6n3ypz3v5vz2d...',
});

sendSubscribeFrame(ws, sub);
```

### Identifier — watch any ESDTTransfer across all contracts

```javascript
// Receive every ESDTTransfer event regardless of which contract emitted it
const sub = SubscribeEvent.create({
  eventType: 'Identifier',
  identifier: Buffer.from('ESDTTransfer').toString('base64'),
});

sendSubscribeFrame(ws, sub);
```

### AddressIdentifier — watch a specific event on a specific contract

```javascript
// Receive only swapTokensFixedInput events from one DEX pool
const sub = SubscribeEvent.create({
  eventType: 'AddressIdentifier',
  address: 'erd1qqqqqqqqqqqqqpgqx7wfpk0qcxnyfq6xsxe6n3ypz3v5vz2d...',
  identifier: Buffer.from('swapTokensFixedInput').toString('base64'),
});

sendSubscribeFrame(ws, sub);
```

### Topics — filter by token identifier with wildcard nonce

```javascript
// Receive ESDTTransfer events for WEGLD-bd4d79, any nonce
// Event topics layout for ESDTTransfer: [tokenId, nonce, amount, receiver]
// Wildcard ("") at position 1 matches any nonce value

const tokenId = Buffer.from('WEGLD-bd4d79').toString('base64');  // "V0VHTEQtYmQ0ZDc5"

const sub = SubscribeEvent.create({
  eventType: 'Topics',
  address: 'erd1qqqqqqqqqqqqqpgqx7wfpk0qcxnyfq6xsxe6n3ypz3v5vz2d...',
  identifier: Buffer.from('ESDTTransfer').toString('base64'),
  topics: [tokenId, ''],   // match WEGLD-bd4d79 at pos 0, wildcard at pos 1
});

sendSubscribeFrame(ws, sub);
```

### Topics — match a specific token and nonce

```javascript
// Receive events for a specific NFT (token + nonce)
// Nonce is a big-endian uint64 encoded as bytes, then base64

const nonce = 42n;
const nonceBytes = Buffer.alloc(8);
nonceBytes.writeBigUInt64BE(nonce);

const sub = SubscribeEvent.create({
  eventType: 'Topics',
  address: 'erd1qqqqqqqqqqqqqpgqx7wfpk0qcxnyfq6xsxe6n3ypz3v5vz2d...',
  identifier: Buffer.from('ESDTNFTTransfer').toString('base64'),
  topics: [
    Buffer.from('XOXNO-abcdef').toString('base64'),  // token identifier
    nonceBytes.toString('base64'),                   // exact nonce
  ],
});

sendSubscribeFrame(ws, sub);
```

***

## Subscription helper

```javascript
function sendSubscribeFrame(ws, subscribeEvent) {
  const payload = SubscribeEvent.encode(subscribeEvent).finish();

  const frame = WsMessage.create({
    version: 1,
    withAcknowledge: false,
    counter: nextCounter(),
    type: 2,              // PAYLOAD
    topic: 'subscribe',
    payload,
  });

  ws.send(WsMessage.encode(frame).finish());
}
```

***

## Common patterns

### Monitor all events from a contract

Use `Address` match level. You receive every event the contract emits regardless of event type.

### Watch a specific DEX event type globally

Use `Identifier` match level with the event identifier (e.g., `swapTokensFixedInput`). Useful for aggregating activity across all pools of the same DEX protocol.

### Track transfers of a specific fungible token

Use `Topics` match level with `identifier = ESDTTransfer`, `topics[0] = base64(tokenId)`, and `topics[1] = ""` (wildcard for the fungible nonce, which is always zero, but a wildcard is cleaner).

### Track transfers of all tokens on a specific contract

Use `AddressIdentifier` match level. You receive only the event type you care about from one contract, reducing noise relative to `Address`.

### Detect any NFT mint on a contract

Use `AddressIdentifier` with `identifier = ESDTNFTCreate`. Use `Topics` only to filter to a specific collection or nonce range.
