> ## 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` - опциональные peer-зависимости. Устанавливайте их, только если используете 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)`** - управление backoff при переподключении. Когда соединение падает, SDK ждёт `initialDelayMs`, затем удваивает задержку на каждой попытке (ограничение `maxDelayMs`). `maxAttempts: 0` - бесконечные повторы. По умолчанию: 100 мс начальная, 30 с максимум, множитель 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 отправляет на уровне proto сообщения `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.

**`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
```
