> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Token Mint Filtering

> Subscribe to every transaction that touches a token mint in LaserStream gRPC with matchMints. Catches SPL transfers that accountInclude misses because the mint is not in the account keys.

LaserStream's `matchMints` flag lets a gRPC transaction subscription match on the **token mints in a transaction's pre/post token balances** in addition to its account keys.

Put a mint in `accountInclude`, set `matchMints: true`, and you receive every transaction that touches that token: transfers, swaps, mint-to, burns, and account closes.

> [!NOTE]
>
> `matchMints` is available on LaserStream gRPC only. It is not available on LaserStream WebSocket yet.

## The problem: plain account filters miss most token transfers

When you watch a token with `accountInclude: [mint]`, you only match transactions where the mint pubkey appears in the transaction's account keys.

A classic SPL `Transfer` instruction never references the mint. It only names the source token account, the destination token account, and the owner, so a plain account filter misses the most common operation on any token.

Only instructions that pass the mint directly match, such as `MintTo`, `Burn`, `TransferChecked`, and swaps whose program accounts include the mint. The only workaround was to stream all transactions and inspect each one's token balances yourself.

## How `matchMints` works

Set `matchMints: true` on a transaction filter and LaserStream builds a set of mints from the transaction's `preTokenBalances` and `postTokenBalances`.

Your `accountInclude`, `accountExclude`, and `accountRequired` lists are then matched against **both** the account keys and that mint set. A mint qualifies if any token account for it appears in either balance list, regardless of whether the balance changed.

The flag is opt-in, and filters that omit it behave exactly as before, so you can add it to an existing subscription without changing what that subscription already receives. SPL and Token-2022 mints work because both programs populate pre/post token balances.

## Semantics

| Predicate | Without `matchMints` | With `matchMints: true` |
| - | - | - |
| `accountInclude` | Matches if any listed key is in the account keys | Matches if any listed key is in the account keys **or** in the mint set |
| `accountExclude` | Rejects if any listed key is in the account keys | Rejects if any listed key is in the account keys **or** in the mint set |
| `accountRequired` | Every listed key must be in the account keys | Every listed key must be in the account keys **or** in the mint set (each key can be satisfied by either) |

The rest of the filter logic is unchanged:

* Predicates inside one named filter are still AND-combined (`vote`, `failed`, `signature`, and the account lists).
* Multiple named filters are still OR-combined.
* Values inside a list are OR (except `accountRequired`, where all must match).
* A transaction with no token balances falls back to keys-only matching. `matchMints` never adds transactions that have no token activity.
* `matchMints` on its own does not restrict the stream. You still need at least one key or mint in an account list (or another restricting predicate) for the filter to be accepted.

> [!NOTE]
>
> LaserStream matches mints by exact pubkey. There is no "balance changed only" mode for mints, unlike `tokenAccounts: "balanceChanged"`.

## Use it in LaserStream gRPC

Add `matchMints: true` to a transaction filter in your `SubscribeRequest` and put the mint in `accountInclude`. This example streams every USDC transaction on mainnet:

> [tab] TypeScript
>
> Requires `helius-laserstream` 0.8.5 or later. The field is also accepted as `match_mints`.
>
> ```typescript theme={"system"}
> import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';
> import bs58 from 'bs58';
>
> const USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
>
> const subscriptionRequest: SubscribeRequest = {
>   transactions: {
>     'usdc-txs': {
>       accountInclude: [USDC],
>       accountExclude: [],
>       accountRequired: [],
>       vote: false,
>       failed: false,
>       matchMints: true, // match USDC via pre/post token-balance mints
>     },
>   },
>   commitment: CommitmentLevel.CONFIRMED,
>   accounts: {}, slots: {}, transactionsStatus: {},
>   blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
> };
>
> const config: LaserstreamConfig = {
>   apiKey: 'YOUR_API_KEY',
>   endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
> };
>
> await subscribe(config, subscriptionRequest, async (data) => {
>   const tx = data.transaction?.transaction;
>   if (!tx) return;
>   // USDC balances touched by this transaction
>   const usdcBalances = (tx.meta?.postTokenBalances || []).filter((b: any) => b.mint === USDC);
>   console.log(bs58.encode(tx.signature), usdcBalances);
> }, async (error) => {
>   console.error('Stream error:', error);
> });
> ```

> [tab] Rust
>
> Requires `helius-laserstream` 0.6.4 or later (which pulls `laserstream-core-proto` 11.3.0). The field comes straight from the proto crate.
>
> ```rust theme={"system"}
> use std::collections::HashMap;
> use helius_laserstream::grpc::{SubscribeRequest, SubscribeRequestFilterTransactions};
>
> let request = SubscribeRequest {
>     transactions: HashMap::from([(
>         "usdc-txs".to_string(),
>         SubscribeRequestFilterTransactions {
>             account_include: vec!["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string()],
>             vote: Some(false),
>             failed: Some(false),
>             match_mints: true,
>             ..Default::default()
>         },
>     )]),
>     ..Default::default()
> };
> ```

> [tab] Go
>
> Requires the Go module at tag `go/v0.3.0` or later.
>
> ```go theme={"system"}
> vote := false
> failed := false
> req := &laserstream.SubscribeRequest{
>     Transactions: map[string]*laserstream.SubscribeRequestFilterTransactions{
>         "usdc-txs": {
>             AccountInclude: []string{"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"},
>             Vote:           &vote,
>             Failed:         &failed,
>             MatchMints:     true,
>         },
>     },
>     Commitment: &commitmentLevel,
> }
> ```

If you use a raw gRPC or Yellowstone client instead of the SDK, regenerate from the Helius proto (`laserstream-core-proto` 11.3.0 or later, or the `.proto` bundled in the SDK repo).

`match_mints` is field 32 of `SubscribeRequestFilterTransactions`. Clients generated from the upstream Triton proto silently drop the unknown field, so the flag has no effect until you regenerate.

See the [Subscribe Request reference](https://www.helius.dev/docs/laserstream/grpc.md#subscribe-request) for every transaction filter field.

## Combine with `tokenAccounts` to watch one token for one wallet

`matchMints` composes with [`tokenAccounts` expansion](https://www.helius.dev/docs/laserstream/token-account-filtering), so one filter can match on wallet owners and mints at the same time.

This example streams every change to one wallet's USDC balance:

```typescript theme={"system"}
transactions: {
  'wallet-usdc': {
    accountInclude: [WALLET],
    accountRequired: [USDC],
    accountExclude: [],
    tokenAccounts: 'balanceChanged', // wallet matched via its token accounts
    matchMints: true,                // USDC matched via balance mints
    vote: false,
    failed: false,
  },
},
```

`accountInclude` plus `tokenAccounts` finds transactions where the wallet's token balances moved. `accountRequired` plus `matchMints` narrows those to the ones involving USDC.

## Reading what matched

Once a transaction matches via a mint, look for the mint in `meta.preTokenBalances[].mint` and `meta.postTokenBalances[].mint`. For plain transfers, the mint is usually absent from the account keys, so do not look for it there.

Diff `preTokenBalances` against `postTokenBalances` on the same `accountIndex` to see how much of the token moved and between which owners.

The [Transaction Monitoring guide](https://www.helius.dev/docs/laserstream/guides/transaction-monitoring.md#transaction-data-structure) covers the transaction structure in detail.

## Limits and notes

* Mints go in the same `accountInclude`, `accountExclude`, and `accountRequired` lists as account keys, so they count toward the same per-list plan limits. There is no separate mint limit.
* Matching cost does not scale with the number of mints you list. 100 mints and 100,000 mints perform the same, and subscribers that do not set the flag pay nothing.
* [Historical replay](https://www.helius.dev/docs/laserstream/historical-replay.md) honors `matchMints`, so a replay subscription returns the same transactions the live stream would have returned.
* If you attach a [compressed (cuckoo) filter](https://www.helius.dev/docs/laserstream/cuckoo-filters.md) to a transaction subscription, `matchMints` probes the mint set against it as well as the account keys.
* `matchMints` is live on all LaserStream gRPC regions, mainnet and devnet. It is not available on LaserStream WebSocket at this time.
* Minimum SDK versions: JavaScript/TypeScript `helius-laserstream` 0.8.5, Rust `helius-laserstream` 0.6.4, Go `go/v0.3.0`.

## Related

> [card] [Token Account (ATA) Filtering](https://www.helius.dev/docs/laserstream/token-account-filtering.md)
>
> Match transactions that touch the token accounts a wallet owns

> [card] [Transaction Monitoring](https://www.helius.dev/docs/laserstream/guides/transaction-monitoring.md)
>
> Full filtering strategies and runnable examples over gRPC

> [card] [Subscribe Request Reference](https://www.helius.dev/docs/laserstream/grpc.md)
>
> Every transaction filter field, including `matchMints`

> [card] [Historical Replay](https://www.helius.dev/docs/laserstream/historical-replay.md)
>
> Backfill up to 48 hours of token activity with the same filter


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.