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

# 代币账户 (ATA) 过滤

> 通过 tokenAccounts (ATA) 扩展在 LaserStream 的 gRPC 和 WebSocket 流中捕获钱包的传入 SPL 代币转账 — 基于所有者的匹配，普通的 accountInclude 过滤器无法做到。

LaserStream 的 `tokenAccounts` 过滤器让交易订阅匹配钱包拥有的 **关联代币账户 (ATA)** 上的活动，而不仅仅是直接出现钱包公钥的交易。在 [LaserStream gRPC](/zh/laserstream/grpc) 和 [LaserStream WSS](/zh/rpc/websocket/transaction-subscribe) 中，它的工作方式相同。

## 问题：普通账户过滤器错过传入的代币转账

当你使用 `accountInclude: [wallet]` 观察钱包时，你只匹配该钱包公钥出现在交易账户键中的交易。一个常见的情况被忽略：当有人发送 SPL 代币（例如 USDC）到钱包时，转账触及的是钱包的 **关联代币账户 (ATA)** — 一个单独的程序派生地址 — 而不是钱包公钥本身。

因此，普通的 `accountInclude: [wallet]` 订阅无法看到传入的代币转账。你需要预先枚举钱包拥有的每个 ATA 并将其添加到过滤器中 — 但 ATAs 是按需创建的（每个铸币一个），所以你无法提前知道完整的集合。

## `tokenAccounts` 扩展的工作原理

在交易过滤器上设置 `tokenAccounts` 以扩展匹配，以便一个 `accountInclude` 钱包**还**匹配触及其拥有的代币账户的交易。匹配是**基于所有者**的：LaserStream 在匹配时解析你的 `accountInclude` 地址拥有的代币账户，因此可以抓住钱包拥有的任何代币账户 — 包括非规范的，而不仅仅是派生的 ATA 地址。你不需要自己列出 ATAs。

如省略 `tokenAccounts` 的订阅行为与之前完全相同，因此可以安全地添加到现有过滤器中。

## 扩展模式

`tokenAccounts` 取三个字符串值之一：

| 值                  | 匹配                        | 体积          | 用途                           |
| ------------------ | ------------------------- | ----------- | ---------------------------- |
| `"balanceChanged"` | 拥有的代币余额实际变化的交易（或其代币账户已关闭） | 较低 — 推荐的默认值 | “告诉我钱实际移动时” — 存款、取款、结算到钱包的兑换 |
| `"all"`            | 任何引用钱包拥有的代币账户的交易，即使余额没有变化 | 较高          | 完全了解任何触及钱包代币账户的情况            |
| `"none"`           | 无扩展 — 与省略该字段相同            | —           | 默认值                          |

从 `"balanceChanged"` 开始。它捕获实际资金移动，其体积仅为 `"all"` 的一小部分。

## 在 LaserStream gRPC 中使用

在你的 `SubscribeRequest` 中将 `tokenAccounts` 添加到交易过滤器中。 [Helius LaserStream SDK](/zh/laserstream/clients) 将字符串转换为线级别的 `TokenAccountExpansionControlFlag` 枚举（是 `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);
});
```

有关 pre- 和 post- 平衡的完整示例，请参阅 [事务监控指南](/zh/laserstream/guides/transaction-monitoring)，以及每个交易过滤字段的 [订阅请求参考](/zh/laserstream/grpc)。

## 在 LaserStream WebSocket 中使用

同一字段可用于 [`transactionSubscribe`](/zh/rpc/websocket/transaction-subscribe) WebSocket 方法 — 这是标准 Solana WebSocket API 的 Helius 扩展。无效值会返回 JSON-RPC 错误 `-32602`: `Invalid tokenAccounts value '<x>', expected one of: none, balanceChanged, all`。

```javascript theme={"system"}
const ws = new WebSocket('wss://mainnet.helius-rpc.com/?api-key=<API_KEY>');

ws.on('open', () => {
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'transactionSubscribe',
    params: [
      {
        accountInclude: ['<WALLET_PUBKEY>'],
        tokenAccounts: 'balanceChanged' // also match the wallet's ATAs
      },
      { commitment: 'confirmed', encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 }
    ]
  }));
  setInterval(() => ws.ping(), 30_000);
});

ws.on('message', (data) => {
  const msg = JSON.parse(data.toString());
  const result = msg.params?.result;
  if (!result) return;
  // Token balances this wallet owns that changed in the tx
  const owned = (result.transaction.meta.postTokenBalances || [])
    .filter((b) => b.owner === '<WALLET_PUBKEY>');
  console.log(result.signature, owned);
});
```

## 读取匹配结果

一旦通过 ATA 扩展匹配交易，钱包的代币移动会体现在交易的 `meta.postTokenBalances` 和 `meta.preTokenBalances` 中。通过 `owner` 过滤这些条目，以隔离钱包实际拥有的余额，然后在相同的 `accountIndex` 上将 `preTokenBalances` 与 `postTokenBalances` 进行比较，以查看每个铸造的移动量。上述示例展示了过滤步骤；[事务监控指南](/zh/laserstream/guides/transaction-monitoring#example-4-watch-a-wallet-incl-token-transfers) 展示了完整差异。

## 相关内容

<CardGroup cols={2}>
  <Card title="交易监控" icon="receipt" href="/zh/laserstream/guides/transaction-monitoring">
    全面的过滤策略以及一个可运行的基于gRPC的钱包观察示例。
  </Card>

  <Card title="transactionSubscribe (WebSocket)" icon="bolt" href="/zh/rpc/websocket/transaction-subscribe">
    相同的`tokenAccounts`字段在WebSocket `transactionSubscribe`方法中。
  </Card>

  <Card title="订阅请求参考" icon="filter" href="/zh/laserstream/grpc">
    每个交易过滤器字段，包括`tokenAccounts`。
  </Card>

  <Card title="压缩过滤器" icon="layer-group" href="/zh/laserstream/cuckoo-filters">
    在一个流中跟踪数十万个账户。
  </Card>
</CardGroup>
