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

# gRPC 客户端（Yellowstone）

> Yellowstone Geyser 流式传输，支持自动重连、Ping/Pong 存活检测与 YAML 配置。

## 安装

```bash theme={null}
npm install @orbitflare/sdk @grpc/grpc-js yaml
```

`@grpc/grpc-js` 与 `yaml` 为可选对等依赖。仅在使用 gRPC 客户端（或 YAML 配置）时才需要安装。

## 构建客户端

以下示例启用全部选项：

```ts theme={null}
import { GeyserClientBuilder } from '@orbitflare/sdk/grpc';
import type { RetryPolicy } from '@orbitflare/sdk';

const client = new GeyserClientBuilder()
  .url('http://ny.rpc.orbitflare.com:10000')
  .fallbackUrl('http://fra.rpc.orbitflare.com:10000')
  .retry({
    initialDelayMs: 100,
    maxDelayMs: 30_000,
    multiplier: 2.0,
    maxAttempts: 0,
  })
  .timeoutSecs(30)
  .keepaliveSecs(60)
  .pingIntervalSecs(10)
  .maxMissedPongs(3)
  .channelCapacity(4096)
  .build();
```

最简配置：

```ts theme={null}
const client = new GeyserClientBuilder()
  .url('http://ny.rpc.orbitflare.com:10000')
  .build();
```

### 构建器方法

**`.url(url)`** - 主 gRPC 端点。回退读取环境变量 `ORBITFLARE_GRPC_URL`。

```ts theme={null}
.url('http://ny.rpc.orbitflare.com:10000')
```

**`.urls([...])`** - 一次设置主端点与备用端点。第一个元素为主端点。

```ts theme={null}
.urls(['http://ny.rpc.orbitflare.com:10000', 'http://fra.rpc.orbitflare.com:10000'])
```

**`.fallbackUrl(url)` / `.fallbackUrls([...])`** - 添加备用端点。连接失败时 SDK 会轮换尝试。

```ts theme={null}
.fallbackUrl('http://fra.rpc.orbitflare.com:10000')
```

**`.retry(policy)`** - 控制重连退避。连接断开时，SDK 先等待 `initialDelayMs`，随后每次尝试加倍（上限 `maxDelayMs`）。将 `maxAttempts` 设为 0 表示无限重试。默认：初始 100ms、最大 30s、2 倍乘数、无限次。

```ts theme={null}
.retry({
  initialDelayMs: 200,
  maxDelayMs: 15_000,
  multiplier: 2.0,
  maxAttempts: 0,
})
```

**`.timeoutSecs(n)`** - 单次 gRPC 请求超时。默认：30。

```ts theme={null}
.timeoutSecs(15)
```

**`.keepaliveSecs(n)`** - TCP keepalive 间隔。操作系统按此间隔发送探测以在 TCP 层检测死亡连接。默认：60。

```ts theme={null}
.keepaliveSecs(30)
```

**`.pingIntervalSecs(n)`** - SDK 发送协议层 `Ping` 的频率。服务端应以 `Pong` 回应。默认：10。

```ts theme={null}
.pingIntervalSecs(15)
```

**`.maxMissedPongs(n)`** - 连续多少次 Ping 无应答后，SDK 认为连接已死并重连。默认：3。在默认参数下，约 30 秒内可检测到死亡连接。

```ts theme={null}
.maxMissedPongs(5)
```

**`.channelCapacity(n)`** - 后台任务与你的代码之间的有界通道容量。若消费事件较慢，后台任务在填满时会暂停，而非无限占用内存。默认：4096。

```ts theme={null}
.channelCapacity(8192)
```

## 编写 YAML 配置

创建包含所需过滤器的 YAML 文件：

```yaml theme={null}
# grpc.yml
transactions:
  pumpfun:
    vote: false
    failed: false
    account_include:
      - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
  jupiter:
    vote: false
    failed: false
    account_include:
      - "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"

accounts:
  usdc_mint:
    account:
      - "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
  token_program:
    owner:
      - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"

slots:
  all:
    filter_by_commitment: true

commitment: confirmed
```

### YAML 过滤器参考

**`transactions`** - 每个条目是一个命名过滤器。若交易涉及任一 `account_include` 地址则匹配。`account_exclude` 排除匹配。`account_required` 表示所列地址必须全部出现。`vote` 与 `failed` 按交易类型过滤。

**`accounts`** - 按地址监视特定账户，或通过 `owner` 监视某程序拥有的全部账户。

**`slots`** - 订阅槽位更新。`filter_by_commitment` 仅在你设定的承诺级别发送更新。

**`commitment`** - `'processed'`、`'confirmed'` 或 `'finalized'`。默认为 `'confirmed'`。

YAML 支持 `${ENV_VAR}` 展开：

```yaml theme={null}
transactions:
  target:
    account_include:
      - "${TARGET_PROGRAM}"
```

## 订阅与读取事件

### 自 YAML

```ts theme={null}
const stream = client.subscribeYaml('grpc.yml');
```

### 编程方式

运行时动态构建过滤器：

```ts theme={null}
import { proto } from '@orbitflare/sdk/proto';

const request: proto.geyser.SubscribeRequest = {
  transactions: {
    target: {
      vote: false,
      failed: false,
      signature: undefined,
      accountInclude: [someAddress],
      accountExclude: [],
      accountRequired: [],
    },
  },
  accounts: {},
  slots: {},
  transactionsStatus: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  accountsDataSlice: [],
  commitment: 1,
  ping: { id: 1 },
};

const stream = client.subscribe(request);
```

### 读取流

`subscribeYaml` 与 `subscribe` 均返回 `GeyserStream`。它是异步可迭代对象 - 使用 `for await`：

```ts theme={null}
for await (const update of stream) {
  if (update.transaction) {
    // update.transaction.slot - the slot this transaction was in
    // update.transaction.transaction - the transaction info (signature, accounts, instructions, meta)
  } else if (update.account) {
    // update.account.slot - the slot
    // update.account.account - account info (pubkey, lamports, owner, data)
    // update.account.isStartup - true during initial account snapshot
  } else if (update.slot) {
    // update.slot.slot - the slot number
    // update.slot.status - processed, confirmed, finalized, etc.
  } else if (update.blockMeta) {
    // update.blockMeta.slot, update.blockMeta.blockhash, update.blockMeta.parentSlot
    // update.blockMeta.executedTransactionCount
  }
}
```

或直接调用 `await stream.next()` 逐个拉取事件。流关闭时返回 `undefined`。若已用尽全部重试，下一次迭代会抛出错误。

Pong 消息在内部消费，不会出现在你的流中。

### 关闭

```ts theme={null}
stream.close();
```

立即停止后台任务。

## 多路流

一个客户端可同时运行多条流。每条流使用独立的后台连接：

```ts theme={null}
const pumpfun = client.subscribeYaml('config/pumpfun.yml');
const raydium = client.subscribeYaml('config/raydium.yml');
const slots = client.subscribeYaml('config/slots.yml');
```

它们共享端点健康状态 - 若某条流将失败端点隔离，其他流在下次重连时会跳过该端点。但各流的连接与生命周期完全独立。

可随时新建流，包括根据已有流的数据动态创建。

## 完整示例

监听 pump.fun 交易、解码签名并打印每条摘要的流：

```ts theme={null}
import bs58 from 'bs58';
import { GeyserClientBuilder } from '@orbitflare/sdk/grpc';

async function main() {
  const client = new GeyserClientBuilder()
    .url('http://ny.rpc.orbitflare.com:10000')
    .fallbackUrl('http://fra.rpc.orbitflare.com:10000')
    .build();

  const stream = client.subscribeYaml('grpc.yml');
  let txCount = 0;

  console.log('streaming...');

  for await (const update of stream) {
    if (update.transaction) {
      txCount += 1;
      const info = update.transaction.transaction;
      if (info?.signature) {
        const sig = bs58.encode(info.signature);
        const fee = info.meta?.fee ?? 0n;
        const ixCount = info.transaction?.message?.instructions.length ?? 0;
        console.log(
          `#${txCount} slot=${update.transaction.slot} sig=${sig.slice(0, 16)}... fee=${fee} instructions=${ixCount}`,
        );
      }
    } else if (update.slot) {
      console.log(`slot ${update.slot.slot} (${update.slot.status})`);
    }
  }
}

void main();
```

配合以下 `grpc.yml`：

```yaml theme={null}
transactions:
  pumpfun:
    vote: false
    failed: false
    account_include:
      - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"

slots:
  all:
    filter_by_commitment: true

commitment: confirmed
```
