> ## 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 Account (ATA) Filtering

> Catch a wallet's incoming SPL token transfers in LaserStream gRPC streams with tokenAccounts (ATA) expansion — owner-based matching that a plain accountInclude filter misses.

LaserStream's `tokenAccounts` filter lets a gRPC transaction subscription match activity on the **associated token accounts (ATAs) a wallet owns**, not just transactions where the wallet's pubkey appears directly. The same filter is available over WebSocket — see [Token Account (ATA) Filtering over WebSocket](/docs/rpc/websocket/token-account-filtering).

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

When you watch a wallet with `accountInclude: [wallet]`, you only match transactions where that wallet pubkey appears in the transaction's account keys. A common case slips through: when someone sends the wallet an SPL token (USDC, for example), the transfer touches the wallet's **associated token account (ATA)** — a separate program-derived address — not the wallet pubkey itself.

So a plain `accountInclude: [wallet]` subscription never sees incoming token transfers. You would have to enumerate every ATA the wallet owns up front and add each one to the filter — but ATAs are created on demand (one per mint), so you can't know the full set in advance.

## How `tokenAccounts` expansion works

Set `tokenAccounts` on a transaction filter to expand matching so an `accountInclude` wallet **also** matches transactions that touch a token account it owns. Matching is **owner-based**: LaserStream resolves the token accounts owned by your `accountInclude` addresses at match time, so it catches any token account the wallet owns — including non-canonical ones — not just the derived ATA address. You never have to list the ATAs yourself.

Subscriptions that omit `tokenAccounts` behave exactly as before, so it's safe to add to an existing filter.

## Expansion modes

`tokenAccounts` takes one of three string values:

| Value              | Matches                                                                                            | Volume                          | Use it for                                                                                   |
| ------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- |
| `"balanceChanged"` | Transactions where an owned token balance actually changed (or its token account was closed)       | Lower — the recommended default | "Tell me when money actually moved" — deposits, withdrawals, swaps that settle to the wallet |
| `"all"`            | Any transaction that references a token account the wallet owns, even if the balance didn't change | Higher                          | Full visibility into anything that so much as touches the wallet's token accounts            |
| `"none"`           | No expansion — identical to omitting the field                                                     | —                               | The default                                                                                  |

Start with `"balanceChanged"`. It captures real fund movement at a fraction of the volume of `"all"`.

## Use it in LaserStream gRPC

Add `tokenAccounts` to a transaction filter in your `SubscribeRequest`. The [Helius LaserStream SDK](/docs/laserstream/clients) converts the string to the wire-level `TokenAccountExpansionControlFlag` enum for you (part of `yellowstone-grpc-proto` 12.5.0+).

```typescript theme={"system"}
import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';
import bs58 from 'bs58';

const wallet = '<WALLET_PUBKEY>';

const subscriptionRequest: SubscribeRequest = {
  transactions: {
    "wallet-activity": {
      accountInclude: [wallet],
      accountExclude: [],
      accountRequired: [],
      vote: false,
      failed: false,
      tokenAccounts: "balanceChanged" // also match the wallet's ATAs
    }
  },
  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) => {
  if (!data.transaction?.transaction) return;
  const tx = data.transaction.transaction;
  // Token balances this wallet owns that changed in the tx
  const owned = (tx.meta?.postTokenBalances || []).filter((b: any) => b.owner === wallet);
  console.log(bs58.encode(tx.signature), owned);
}, async (error) => {
  console.error('Stream error:', error);
});
```

See the [Transaction Monitoring guide](/docs/laserstream/guides/transaction-monitoring) for a fuller example that diffs pre- and post-balances, and the [Subscribe Request reference](/docs/laserstream/grpc) for every transaction filter field.

## Reading what matched

Once a transaction matches via ATA expansion, the wallet's token movement lives in the transaction's `meta.postTokenBalances` and `meta.preTokenBalances`. Filter those entries by `owner` to isolate the balances your wallet actually owns, then diff `preTokenBalances` against `postTokenBalances` on the same `accountIndex` to see how much each mint moved. The example above shows the filtering step; the [Transaction Monitoring guide](/docs/laserstream/guides/transaction-monitoring#example-4-watch-a-wallet-incl-token-transfers) shows the full diff.

## Related

<CardGroup cols={2}>
  <Card title="Transaction Monitoring" icon="receipt" href="/docs/laserstream/guides/transaction-monitoring">
    Full filtering strategies and a runnable wallet-watch example over gRPC.
  </Card>

  <Card title="Token Account Filtering (WebSocket)" icon="bolt" href="/docs/rpc/websocket/token-account-filtering">
    The same `tokenAccounts` field on the WebSocket `transactionSubscribe` method.
  </Card>

  <Card title="Subscribe Request Reference" icon="filter" href="/docs/laserstream/grpc">
    Every transaction filter field, including `tokenAccounts`.
  </Card>

  <Card title="Compressed Filters" icon="layer-group" href="/docs/laserstream/cuckoo-filters">
    Track hundreds of thousands of accounts in one stream.
  </Card>
</CardGroup>
