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

# 通过解析流跟踪 Jupiter 交换

> 使用 describeProgram 构建和订阅一个针对 Jupiter 路由指令的解析流过滤器。

本指南逐步构建一个实际的过滤器：使用[程序发现](/docs/zh/parsed-streams/quickstart#discovery)来监控钱包的 Jupiter 交换，以便在您打开订阅之前确保过滤器是正确的。

<Steps>
  <Step title="查找程序">
    猜测的指令名称是过滤器静默匹配不到内容的最常见方式。因此，首先调用 `describeProgram` 以获取匹配器对比的确切名称。

    ```json Request theme={"system"}
    { "jsonrpc": "2.0", "id": 1, "method": "describeProgram", "params": [{ "program": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" }] }
    ```

    ```json Response theme={"system"}
    {
      "jsonrpc": "2.0", "id": 1,
      "result": {
        "id": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
        "name": "jupiter",
        "instructions": ["route", "shared_accounts_route", "exact_out_route"],
        "events": ["SwapEvent"],
        "roles": ["user_transfer_authority", "destination_token_account"]
      }
    }
    ```

    优先使用程序的**地址**而不是目录名称 —— 因为多个目录条目可以共享同一个名称，并且名称查找可能解析为程序的旧版本。`route` 和 `shared_accounts_route` 是涵盖大多数 Jupiter v6 交换的两个指令，因此需要对此进行过滤。
  </Step>

  <Step title="构建过滤器">
    结合程序 ID、前一步的指令名称以及您要监控的钱包。字段通过 AND 组合，因此这将匹配与钱包的 SOL 账户相关的路由指令：

    ```json theme={"system"}
    {
      "programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
      "instructionNames": ["route", "shared_accounts_route"],
      "accounts": {
        "include": ["So11111111111111111111111111111111111111112"],
        "roles": { "user_transfer_authority": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }
      },
      "includeFailed": false,
      "includeCpi": true
    }
    ```

    `accounts.roles` 将 `user_transfer_authority` 固定在指令中钱包的精确位置，这比单独使用 `accounts.include` 更严格：简单的地址匹配也可能捕捉到钱包作为指令中不相关账户出现的情况。角色名称完全匹配，因此它们是从 `describeProgram` 的 `roles` 列表中复制的，而不是猜测的。保留 `includeCpi: true`（默认值） —— 交换的实际代币运动发生在内层指令中。
  </Step>

  <Step title="订阅并处理通知">
    使用过滤器打开订阅，然后读取每个匹配指令的解码参数：

    ```typescript theme={"system"}
    import WebSocket from "ws";

    const ws = new WebSocket("wss://<ENDPOINT>/?api-key=<API_KEY>");

    const filter = {
      programs: ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
      instructionNames: ["route", "shared_accounts_route"],
      accounts: {
        include: ["So11111111111111111111111111111111111111112"],
        roles: { user_transfer_authority: "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" },
      },
      includeFailed: false,
      includeCpi: true,
    };

    ws.on("open", () => {
      ws.send(JSON.stringify({
        jsonrpc: "2.0",
        id: 1,
        method: "parsedTransactionSubscribe",
        params: [filter, { commitment: "confirmed", details: "full" }],
      }));
    });

    ws.on("message", (data) => {
      const msg = JSON.parse(data.toString());
      if (msg.method === "parsedTransactionNotification") {
        const { transaction, instructions, matchedIndexes } = msg.params.result.value;
        for (const i of matchedIndexes) {
          const ix = instructions[i];
          if (!ix.decoded) continue;
          console.log(transaction.signature, ix.decoded.args.in_amount, ix.decoded.args.slippage_bps);
        }
      }
    });
    ```

    `matchedIndexes` 仅指向过滤器命中的指令 — 跳过事务中的其他部分。在读取之前，请检查 `decoded` 是否存在：来自未索引程序版本的路线指令到达时带有 `decoded: null` 和原始字段。
  </Step>
</Steps>

## 接下来的步骤

<CardGroup cols={2}>
  <Card title="过滤字段参考" icon="filter" href="/docs/zh/parsed-streams/quickstart#filter-fields">
    所有过滤字段、选项和限制。
  </Card>

  <Card title="处理重新连接" icon="rotate" href="/docs/zh/parsed-streams/guides/handling-reconnects">
    使此订阅在断开连接和部署时保持活动状态。
  </Card>
</CardGroup>
