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

# 解析流快速入门

> 连接到解析流，发送您的第一个过滤器并读取解码通知。还有完整的JSON-RPC 2.0协议参考。

<Tip>
  刚接触解析流？首先阅读[心理模型](/docs/zh/parsed-streams#the-mental-model) - 它解释了过滤器为何如此设计。
</Tip>

## 快速入门

<Steps>
  <Step title="获取访问权限">
    Parsed Streams 目前处于封闭测试阶段。Helius 团队会将您的项目 ID 加入白名单，并与您共享连接端点。要加入封闭测试，请[在此申请](https://form.typeform.com/to/BlFWKbC9)。

    使用作为`api-key`查询参数（或`x-api-key`头）的项目API密钥进行身份验证。
  </Step>

  <Step title="连接">
    ```bash wscat theme={"system"}
    wscat -c "wss://<ENDPOINT>/?api-key=YOUR_API_KEY"
    ```

    缺失、无效或非白名单的密钥将被HTTP 401拒绝。达到连接上限的项目将获得HTTP 429。
  </Step>

  <Step title="使用过滤器订阅">
    发送`parsedTransactionSubscribe`，包含过滤器和可选选项：

    ```json theme={"system"}
    {"jsonrpc":"2.0","id":1,"method":"parsedTransactionSubscribe","params":[{"programs":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}]}
    ```

    响应`result`是一个整数**订阅ID**：

    ```json theme={"system"}
    { "jsonrpc": "2.0", "id": 1, "result": 23 }
    ```
  </Step>

  <Step title="读取通知">
    每个匹配的交易作为已解码的`parsedTransactionNotification`到达，具有指向您过滤器命中的指令的`matchedIndexes`。有关完整格式，请参见[通知](#notifications)。
  </Step>

  <Step title="取消订阅">
    ```json theme={"system"}
    { "jsonrpc": "2.0", "id": 2, "method": "parsedTransactionUnsubscribe", "params": [23] }
    ```

    或者只需关闭连接——它将删除所有订阅。
  </Step>
</Steps>

## 指南

<CardGroup cols={2}>
  <Card title="跟踪Jupiter交换" icon="arrow-right-arrow-left" href="/docs/zh/parsed-streams/guides/track-jupiter-swaps">
    在订阅之前，使用`describeProgram`构建您可以信任的过滤器。
  </Card>

  <Card title="跟踪Pump.fun铸造" icon="rocket" href="/docs/zh/parsed-streams/guides/track-pumpfun-mints">
    一个安全的重连监听器记录每一个新的Pump.fun代币部署。
  </Card>

  <Card title="处理重连" icon="rotate" href="/docs/zh/parsed-streams/guides/handling-reconnects">
    经受住空闲超时和部署，然后精准回填您错过的内容。
  </Card>
</CardGroup>

## 协议参考

解析流通过单个WebSocket连接使用**JSON-RPC 2.0**。每个请求都会收到具有相同`id`的响应。订阅然后推送`parsedTransactionNotification`消息，直到您取消订阅或断开连接。

| 方法                             | 目的              |
| ------------------------------ | --------------- |
| `parsedTransactionSubscribe`   | 使用过滤器开始订阅       |
| `parsedTransactionUnsubscribe` | 停止订阅            |
| `ping`                         | 活性检查；返回当前槽位     |
| `describeProgram`              | 列出程序的指令、事件和账户角色 |

### 订阅

发送 `parsedTransactionSubscribe` 带有过滤器和可选选项。响应 `result` 是一个整数**订阅ID**。

```json Request theme={"system"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "parsedTransactionSubscribe",
  "params": [
    {
      "programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
      "instructionNames": ["route", "shared_accounts_route"],
      "accounts": {
        "include": ["So11111111111111111111111111111111111111112"],
        "roles": { "user_transfer_authority": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }
      },
      "includeFailed": false,
      "includeCpi": true
    },
    { "commitment": "confirmed", "details": "full" }
  ]
}
```

```json Response theme={"system"}
{ "jsonrpc": "2.0", "id": 1, "result": 23 }
```

#### 过滤字段

至少需要 `programs` 或 `accounts.include` 之一。您设置的字段与 **AND** 组合：指令必须满足所有条件才能匹配。

<ParamField body="programs" type="string[]">
  要匹配的程序 ID（base58 地址，而不是名称）。如果其程序在此列表中，则指令匹配。或在列表中。
</ParamField>

<ParamField body="instructionNames" type="string[]">
  解码的指令名称，如 `route`。首先精确匹配，然后不区分大小写和分隔符进行回退匹配，因此 `sharedAccountsRoute` 也匹配线路名称 `shared_accounts_route`。或在列表中。只有目录可以识别其名称的指令才能匹配，因此请从 `describeProgram` 获取名称。
</ParamField>

<ParamField body="accounts.include" type="string[]">
  账户地址。如果在其账户列表中出现任何这些，则指令匹配。或在列表中。适用于每个指令，无论是否解码。程序 ID 本身在此不算作账户。
</ParamField>

<ParamField body="accounts.roles" type="object">
  解码账户角色名称到地址的映射，例如 `{ "user_transfer_authority": "<pubkey>" }`。每个条目都必须成立（条目之间为且关系），并且指令必须解码才能适用。角色名称完全匹配，无大小写折叠，因此请从 `describeProgram` 复制而不是推测。
</ParamField>

<ParamField body="includeFailed" type="boolean" default="false">
  包含失败交易的指令。
</ParamField>

<ParamField body="includeCpi" type="boolean" default="true">
  内部（CPI）指令有资格匹配。设置 `false` 仅匹配顶级指令。
</ParamField>

过滤器或选项中的任何位置的未知字段都将被拒绝，而不是被静默忽略，因此拼写错误会响亮地失败，而不是不匹配任何内容。

#### 选项

第二个参数是可选的。

<ParamField body="commitment" type="string" default="confirmed">
  仅支持 `confirmed`。
</ParamField>

<ParamField body="details" type="string" default="full">
  每个通知携带的内容。`full`：完整交易，每个指令，以及指向过滤命中项的 `matchedIndexes`。`matched`：仅匹配的指令，无索引列表。`raw`：仅匹配的指令，每个指令简化为其位置，`programId`，以及 base58 `data` blob，无解码字段和无需 `accountKeys` 数组。当带宽比上下文更重要时，使用 `matched`（完整负载的平均大小大约是三个大小），当您自己解码指令数据且只需要字节时使用 `raw`。
</ParamField>

一个项目可以持有最多**100个并发连接**，在其所有API密钥中共享。

### 通知

每个匹配的交易每个订阅一个通知。默认情况下`details: "full"`：

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "method": "parsedTransactionNotification",
  "params": {
    "subscription": 23,
    "result": {
      "context": { "slot": 430172053 },
      "value": {
        "transaction": {
          "signature": "3riSYL4HTRxgQjLayt6L2JPaDR3oaEQg1H4v3fnjUxNU...",
          "slot": 430172053,
          "blockTime": null,
          "feePayer": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX",
          "fee": 5000,
          "accountKeys": ["6jduWNCT...", "..."],
          "status": "ok",
          "error": null,
          "summary": {
            "type": "swap",
            "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
            "parsedData": {
              "type": "swap",
              "protocol": "jupiter",
              "kind": "swap",
              "in_amount": "1000000",
              "actual_out_amount": "183985",
              "input_mint": "So11111111111111111111111111111111111111112",
              "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          },
          "nativeTransfers": [
            { "fromUserAccount": "6jduWNCT...", "toUserAccount": "DfXygSm4...", "amount": 1000000 }
          ],
          "tokenTransfers": [
            {
              "fromUserAccount": "6jduWNCT...",
              "toUserAccount": "AeUfFU6L...",
              "fromTokenAccount": "HLaEoW1s...",
              "toTokenAccount": "G13P9kSY...",
              "rawTokenAmount": 183985,
              "decimals": 6,
              "tokenStandard": "Fungible",
              "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          ]
        },
        "instructions": [
          {
            "topIndex": 4,
            "innerIndex": null,
            "stackHeight": 1,
            "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
            "programName": "jupiter",
            "instructionName": "route",
            "summary": {
              "type": "swap",
              "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
              "parsedData": {
                "type": "swap",
                "protocol": "jupiter",
                "kind": "swap",
                "in_amount": "1000000",
                "actual_out_amount": "183985",
                "input_mint": "So11111111111111111111111111111111111111112",
                "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
              }
            },
            "decoded": {
              "args": { "in_amount": "1000000", "slippage_bps": 50 },
              "accounts": [
                { "name": "user_transfer_authority", "pubkey": "9xQe...", "isSigner": true, "isWritable": false }
              ]
            }
          }
        ],
        "matchedIndexes": [8, 13]
      }
    }
  }
}
```

阅读它：

* **`transaction`** 是完整的上下文。`fee` 是以 lamports 计。`accountKeys` 是完整的密钥列表，包括从地址查找表中加载的密钥，顺序与链报告的相同。`feePayer` 始终为 `accountKeys[0]`。`error` 以结构化 JSON 承载交易错误，例如 `{"InstructionError": [2, {"Custom": 6001}]}`，当 `status` 是 `"error"` 时。
* **`summary`** 在其出现的每个地方都有一个形状：`type`（例如 `swap` 或 `transfer`），一个人类可读的 `description`，以及一个结构化的 `parsedData` 负载，当解析器识别操作时——对于交换：协议、数量和铸币。`transaction.summary` 标记了交易的标题操作；每个识别的指令都有其自己的 `summary`，形状相同。要收集交易中的每个交换，请迭代 `instructions` 并读取 `summary.parsedData`，在 `summary.type` 是 `"swap"` 时。
* **`nativeTransfers`** 和 **`tokenTransfers`** 列出了解析器从整个交易中提取的 SOL 和代币移动，形状与 Parsed Events API 返回的一样，因此流和 API 用户可以共享处理代码。两者始终存在，可能为空。
* **`instructions`** 是每个执行顺序的交易指令：每个顶级指令后跟其内部指令。每个条目都有其自己的位置：`topIndex` 属于哪个顶级指令（从 0 开始），`innerIndex` 是其在该指令内部调用中的位置（`null` 表示它是顶级指令本身），`stackHeight` 是调用深度（1 为顶级）。使用这些，而不是数组位置。
* **`matchedIndexes`** 是 `instructions` 中告诉您哪些是实际命中的索引。其余的用于上下文。使用 `details: "matched"`，数组仅包含命中项，`matchedIndexes` 缺失。
* **`decoded` 名称是 snake\_case**（`in_amount`，`user_transfer_authority`），如程序的 IDL 中发布。整数参数通常是字符串（`"1000000"`），因为 u64 值不能适应 JavaScript 数字。
* **`blockTime`** 目前始终为 `null`。不要在此基础上构建。
* 预期一个**混合解码和未解码指令**在一个交易中：一个完全解码的交换可以与一个无法识别的备忘录并排。分支于 `decoded`：当它是 `null` 时，指令承载 `rawData`（base58 字节）和 `rawAccounts`（普通公钥列表），因此您总会有东西可以使用。

使用 `details: "raw"`，`value` 缩减为交易元信息和 blob。`accountKeys`、`nativeTransfers`、`tokenTransfers`、`matchedIndexes` 以及所有解码字段都消失了（交易 `summary` 仍被包含）；每个匹配的指令是其位置，其程序及其 `data` 字节在 base58 中，完全如链上所示（即使是目录可能已经解码的指令也是如此）：

```json theme={"system"}
"value": {
  "transaction": {
    "signature": "3riSYL4HTRxgQjLayt6L2JPaDR3oaEQg1H4v3fnjUxNU...",
    "slot": 430172053,
    "blockTime": null,
    "feePayer": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX",
    "fee": 5000,
    "status": "ok",
    "error": null,
    "summary": {
      "type": "swap",
      "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
      "parsedData": {
        "type": "swap",
        "protocol": "jupiter",
        "kind": "swap",
        "in_amount": "1000000",
        "actual_out_amount": "183985",
        "input_mint": "So11111111111111111111111111111111111111112",
        "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
      }
    }
  },
  "instructions": [
    { "topIndex": 4, "innerIndex": null, "stackHeight": 1, "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", "data": "3Bxs4h24hBtQy9rw" }
  ]
}
```

### 取消订阅

```json theme={"system"}
{ "jsonrpc": "2.0", "id": 2, "method": "parsedTransactionUnsubscribe", "params": [23] }
```

如果订阅存在并且是您的，则返回 `true`。通知立即停止。关闭连接会移除其所有订阅。

### 发现

这种 API 最常见的失败是过滤器有效但没有匹配内容，通常是猜测的指令或角色名称。`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"]
  }
}
```

您可以传递程序地址或目录名称，但**优先使用地址**：名称在不同版本的程序中可能会有歧义（多个目录项被命名为 `jupiter`，并且名字查找可能解析到旧版本）。如果按名称查找，请确保 `result.id` 是您打算订阅的程序。

推荐流程：使用 `describeProgram` 获取确切的指令和角色名称，使用这些名称构建过滤器，然后订阅。[Track Jupiter Swaps](/docs/zh/parsed-streams/guides/track-jupiter-swaps) 指南从头到尾进行演示。

### 限制

| 限制                        | 值                |
| ------------------------- | ---------------- |
| 每个项目的并发连接                 | 100              |
| 每个连接的订阅                   | 25               |
| 客户端消息                     | 每秒 10 条，突发 20 条  |
| 客户端消息大小                   | 64 KiB           |
| 每个过滤器的 `programs`         | 10               |
| 每个过滤器的 `instructionNames` | 50，每个最多 64 个字符   |
| 每个过滤器的 `accounts.include` | 100              |
| 每个过滤器的 `accounts.roles`   | 20，每个名称最多 64 个字符 |
| 每个连接的出站缓冲区                | 2048 个通知，然后连接关闭  |

### 错误

错误遵循 JSON-RPC 2.0: `{ "error": { "code": <int>, "message": "<text>" }, "id": <id> }`。消息准确指出了问题所在。

| Code     | 意义                         |
| -------- | -------------------------- |
| `-32700` | 解析错误（无效JSON）               |
| `-32600` | 无效请求                       |
| `-32601` | 方法未找到                      |
| `-32602` | 无效参数：错误的公钥，未知字段，不支持的承诺或详细值 |
| `-32000` | 超出过滤器限制                    |
| `-32001` | 服务器未准备好；使用回退重试             |
| `-32002` | 速率限制（每秒10条消息）              |
| `-32006` | 订阅过多（每个连接25个）              |

连接也可以使用 WebSocket 关闭代码关闭——查看[处理重连](/docs/zh/parsed-streams/guides/handling-reconnects)了解其含义和如何恢复。

## 客户端示例

<CodeGroup>
  ```bash wscat theme={"system"}
  wscat -c "wss://<ENDPOINT>/?api-key=<API_KEY>"
  # then send:
  {"jsonrpc":"2.0","id":1,"method":"parsedTransactionSubscribe","params":[{"programs":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}]}
  ```

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

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

  ws.on("open", () => {
    ws.send(JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "parsedTransactionSubscribe",
      params: [{ programs: ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"] }],
    }));
  });

  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 ?? instructions.keys()) {
        const ix = instructions[i];
        console.log(transaction.signature, ix.programName, ix.instructionName, ix.decoded?.args);
      }
    }
  });
  ```

  ```python Python theme={"system"}
  import asyncio, json, websockets

  URL = "wss://<ENDPOINT>/?api-key=<API_KEY>"

  async def main():
      async with websockets.connect(URL) as ws:
          await ws.send(json.dumps({
              "jsonrpc": "2.0", "id": 1, "method": "parsedTransactionSubscribe",
              "params": [{"programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}],
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("method") == "parsedTransactionNotification":
                  value = msg["params"]["result"]["value"]
                  for i in value.get("matchedIndexes") or range(len(value["instructions"])):
                      ix = value["instructions"][i]
                      print(ix.get("programName"), ix.get("instructionName"), (ix.get("decoded") or {}).get("args"))

  asyncio.run(main())
  ```
</CodeGroup>
