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

# WebSocket 客户端

> 账户变更、日志、槽位与签名的实时订阅，支持自动重连。

## 安装

```bash theme={null}
cargo add orbitflare-sdk --features ws
```

## 构建客户端

```rust theme={null}
use orbitflare_sdk::{WsClientBuilder, RetryPolicy, Result};
use std::time::Duration;

let client = WsClientBuilder::new()
    .url("ws://ny.rpc.orbitflare.com")
    .fallback_url("ws://fra.rpc.orbitflare.com")
    .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
    .retry(RetryPolicy {
        initial_delay: Duration::from_millis(100),
        max_delay: Duration::from_secs(30),
        multiplier: 2.0,
        max_attempts: 0,
    })
    .ping_interval_secs(10)
    .max_missed_pongs(3)
    .build()
    .await?;
```

最简配置：

```rust theme={null}
let client = WsClientBuilder::new()
    .url("ws://ny.rpc.orbitflare.com")
    .build()
    .await?;
```

注意 `.build()` 为异步 — 在返回前会建立 WebSocket 连接。若连接失败，将立即得到错误。

### 构建器方法

**`.url(url)`** — 主 WebSocket 端点。回退读取环境变量 `ORBITFLARE_WS_URL`。

```rust theme={null}
.url("ws://ny.rpc.orbitflare.com")
```

**`.urls(&[...])`** — 一次设置主端点与备用端点。

```rust theme={null}
.urls(&["ws://ny.rpc.orbitflare.com", "ws://fra.rpc.orbitflare.com"])
```

**`.fallback_url(url)` / `.fallback_urls(&[...])`** — 添加故障转移端点。重连时 SDK 会轮换尝试。

```rust theme={null}
.fallback_url("ws://fra.rpc.orbitflare.com")
```

**`.api_key(key)`** — 许可证密钥。回退读取环境变量 `ORBITFLARE_LICENSE_KEY`。

```rust theme={null}
.api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
```

**`.retry(policy)`** — 重连退避。默认：初始 100ms、最大 30s、2 倍乘数、无限次尝试。

```rust theme={null}
.retry(RetryPolicy {
    initial_delay: Duration::from_millis(200),
    max_delay: Duration::from_secs(15),
    multiplier: 2.0,
    max_attempts: 0,
})
```

**`.ping_interval_secs(n)`** — SDK 发送 WebSocket `Ping` 帧的频率。默认：10。

```rust theme={null}
.ping_interval_secs(15)
```

**`.max_missed_pongs(n)`** — 多少次 Ping 无应答后终止连接。默认：3。

```rust theme={null}
.max_missed_pongs(5)
```

## 可用订阅

### `slot_subscribe()`

在槽位被处理、确认或最终确定时触发。

```rust theme={null}
let mut sub = client.slot_subscribe().await?;
```

每条事件为含 `slot`、`parent`、`root` 字段的 JSON 值：

```json theme={null}
{"slot": 413014740, "parent": 413014739, "root": 413014708}
```

### `account_subscribe(address, commitment)`

指定账户数据变更时触发。

```rust theme={null}
let mut sub = client.account_subscribe(
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "confirmed",
).await?;
```

返回含 lamports、owner 与可执行标志的 base64 编码账户数据。

### `logs_subscribe(mentions, commitment)`

提及给定地址的交易产生日志时触发。传入空切片表示所有交易。

```rust theme={null}
let mut sub = client.logs_subscribe(
    &["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
    "confirmed",
).await?;
```

全部日志：

```rust theme={null}
let mut sub = client.logs_subscribe(&[], "confirmed").await?;
```

### `signature_subscribe(signature, commitment)`

交易达到给定承诺级别时触发一次。适合确认刚发送的交易。

```rust theme={null}
let mut sub = client.signature_subscribe("5K8F2j...", "confirmed").await?;
```

## 读取事件

所有订阅返回 `WsSubscription`。调用 `.next()` 获取下一条事件：

```rust theme={null}
while let Some(event) = sub.next().await {
    println!("{event}");
}
```

`.next()` 返回 `Option<serde_json::Value>`。`None` 表示订阅已关闭。

## 取消订阅

```rust theme={null}
sub.unsubscribe().await;
```

向服务器发送取消订阅消息。若丢弃订阅而未调用，SDK 会检测孤立订阅并自动发送取消。

## 多路订阅

所有订阅共用单一 WebSocket 连接。SDK 在内部将通知路由到正确订阅。

```rust theme={null}
let mut slots = client.slot_subscribe().await?;
let mut usdc = client.account_subscribe("EPjFWdd5...", "confirmed").await?;
let mut logs = client.logs_subscribe(&["6EF8r..."], "confirmed").await?;
```

可在已有订阅运行时随时添加新订阅。

## 重连

若连接断开，后台任务会以指数退避重连并自动重新订阅所有内容。`.next()` 调用会持续工作 — 连接恢复后事件会继续到达。

## 完整示例

监控脚本：订阅槽位更新与 USDC 账户变更，打印实时流。

```rust theme={null}
use orbitflare_sdk::{WsClientBuilder, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let client = WsClientBuilder::new()
        .url("ws://ny.rpc.orbitflare.com")
        .build()
        .await?;

    let mut slots = client.slot_subscribe().await?;
    let mut usdc = client.account_subscribe(
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        "confirmed",
    ).await?;

    println!("watching slots and USDC mint account...");

    loop {
        tokio::select! {
            Some(slot) = slots.next() => {
                let s = slot["slot"].as_u64().unwrap_or(0);
                let parent = slot["parent"].as_u64().unwrap_or(0);
                println!("slot {s} (parent {parent})");
            }
            Some(acct) = usdc.next() => {
                let lamports = acct["lamports"].as_u64().unwrap_or(0);
                let data_len = acct["data"][0]
                    .as_str()
                    .map(|s| s.len())
                    .unwrap_or(0);
                println!("USDC mint updated: {lamports} lamports, {data_len} bytes");
            }
        }
    }
}
```
