> ## Documentation Index
> Fetch the complete documentation index at: https://docs.solanatracker.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Solana RPC WebSocket 订阅指南

> 订阅 Solana 账户变化，区分请求与订阅 ID，正确取消订阅，并在断线后恢复。

WebSocket 订阅推送连接建立后的变化。如果还需要当前状态，先通过 HTTP 获取快照；订阅并不是历史查询。

## 连接并订阅

使用 Node.js 20 或更新版本。安装 `ws`，设置 RPC API 密钥，并选择经常变化的账户：

```bash theme={null}
npm install ws
export SOLANA_RPC_API_KEY="YOUR_API_KEY"
export ACCOUNT_ADDRESS="ACCOUNT_PUBLIC_KEY"
```

保存为 `accounts.mjs`，运行 `node accounts.mjs`，按 Ctrl+C 停止。已有订阅 ID 时，示例会先取消订阅再关闭连接。

```javascript accounts.mjs theme={null}
import WebSocket from "ws";

const apiKey = process.env.SOLANA_RPC_API_KEY;
const account = process.env.ACCOUNT_ADDRESS;
if (!apiKey || !account) {
  throw new Error("Set SOLANA_RPC_API_KEY and ACCOUNT_ADDRESS");
}
const endpoint = new URL("wss://rpc-data.solanatracker.io/");
endpoint.searchParams.set("api_key", apiKey);
const socket = new WebSocket(endpoint);
let subscriptionId;
let stopping = false;
let closeTimer;

function unsubscribe() {
  socket.send(JSON.stringify({
    jsonrpc: "2.0", id: 2,
    method: "accountUnsubscribe", params: [subscriptionId],
  }));
}

socket.on("open", () => {
  socket.send(JSON.stringify({
    jsonrpc: "2.0", id: 1, method: "accountSubscribe",
    params: [account, { encoding: "base64", commitment: "confirmed" }],
  }));
});

socket.on("message", raw => {
  let message;
  try { message = JSON.parse(raw.toString()); }
  catch { console.error("Invalid JSON received"); return; }
  if (message.error) {
    console.error("RPC error", message.error);
    socket.close();
    return;
  }
  if (message.id === 1) {
    subscriptionId = message.result;
    console.log("Subscribed", subscriptionId);
    if (stopping) unsubscribe();
  } else if (message.id === 2) {
    console.log("Unsubscribed", message.result);
    socket.close();
  } else if (message.method === "accountNotification" &&
             message.params.subscription === subscriptionId) {
    const { context, value } = message.params.result;
    console.log({ slot: context.slot, lamports: value.lamports });
  }
});

socket.on("error", error => console.error("WebSocket error", error.message));
socket.on("close", () => {
  clearTimeout(closeTimer);
  console.log("Connection closed");
});
process.once("SIGINT", () => {
  stopping = true;
  closeTimer = setTimeout(() => socket.terminate(), 3_000);
  if (socket.readyState === WebSocket.OPEN && subscriptionId !== undefined) {
    unsubscribe();
  } else if (socket.readyState !== WebSocket.OPEN) {
    socket.terminate();
  }
});
```

订阅成功响应只确认订阅已建立。账户在指定确认级别发生变化后，才会收到通知。

## 请求 ID 与订阅 ID

发送的 `id` 用于匹配请求和响应。服务端在 `result` 中返回另一个订阅 ID。通知在 `params.subscription` 中携带该 ID；在同一连接上将它传给对应的取消订阅方法。

## 选择订阅

* [accountSubscribe](/cn/solana-rpc/websockets/accountsubscribe)：单个账户的 lamports 或数据变化。
* [programSubscribe](/cn/solana-rpc/websockets/programsubscribe)：某程序拥有的账户写入。
* [logsSubscribe](/cn/solana-rpc/websockets/logssubscribe)：符合筛选条件的交易日志。
* [signatureSubscribe](/cn/solana-rpc/websockets/signaturesubscribe)：签名达到指定确认级别；最终通知后订阅自动结束。
* [slotSubscribe](/cn/solana-rpc/websockets/slotsubscribe)：已处理 slot 的更新。

标准通知语义见 [Solana WebSocket 参考](https://solana.com/docs/rpc/websocket)。Shredstream 在执行元数据可用之前传递交易，应将其视为暂定数据，并通过 RPC 确认执行结果。

## 断线后恢复

使用带随机抖动和次数上限的退避重新连接，然后重新发送订阅请求。旧连接的订阅 ID 不能复用。通过 HTTP 重新获取账户状态，按上下文 slot 与缓冲的通知核对。如果不能遗漏交易，需要另行补查历史；重连不会重放错过的更新。

限制处理队列长度并监控积压。消费者跟不上时，缩小订阅范围或把耗时解码移出消息回调。查看套餐的[连接数限制](/cn/solana-rpc/credits-and-rate-limits)。
