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

# Track Jupiter Swaps with Parsed Streams

> Build and subscribe a Parsed Streams filter for Jupiter route instructions using describeProgram.

This guide builds a real filter step by step: watch a wallet's Jupiter swaps, using [program discovery](/docs/parsed-streams/quickstart#discovery) so the filter is correct before you ever open a subscription.

<Steps>
  <Step title="Look Up the Program">
    Guessed instruction names are the most common way a filter silently matches nothing. Call `describeProgram` first to get the exact names the matcher compares against.

    ```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"]
      }
    }
    ```

    Prefer the program **address** over a catalog name — more than one catalog entry can share a name, and a name lookup can resolve to an older version of the program. `route` and `shared_accounts_route` are the two instructions that cover most Jupiter v6 swaps, so those are what you'll filter on.
  </Step>

  <Step title="Build the Filter">
    Combine the program id, the instruction names from the previous step, and the wallet you're watching. Fields combine with AND, so this matches route instructions that touch the wallet's SOL account:

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

    `accounts.roles` pins `user_transfer_authority` to the wallet's exact position in the instruction, which is stricter than `accounts.include` alone: a plain address match would also catch the wallet showing up as an unrelated account elsewhere in the instruction. Role names match exactly, so they're copied from `describeProgram`'s `roles` list, not guessed. Leave `includeCpi: true` (the default) — a swap's actual token movements happen in inner instructions.
  </Step>

  <Step title="Subscribe and Handle Notifications">
    Open the subscription with the filter, then read each matched instruction's decoded arguments:

    ```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` points only at the instructions your filter hit — skip everything else in the transaction. Check `decoded` is present before reading it: a route instruction from an unindexed program version arrives with `decoded: null` and raw fields instead.
  </Step>
</Steps>

## Next Steps

<CardGroup cols={2}>
  <Card title="Filter Fields Reference" icon="filter" href="/docs/parsed-streams/quickstart#filter-fields">
    All filter fields, options, and limits.
  </Card>

  <Card title="Handling Reconnects" icon="rotate" href="/docs/parsed-streams/guides/handling-reconnects">
    Keep this subscription alive across disconnects and deploys.
  </Card>
</CardGroup>
