> ## 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 Client (Yellowstone)

> Yellowstone Geyser streaming with automatic reconnection, ping/pong liveness detection, and YAML config.

## Install

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

## Building the client

Here's a client with every option set:

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

let client = GeyserClientBuilder::new()
    .url("http://ny.rpc.orbitflare.com:10000")
    .fallback_url("http://fra.rpc.orbitflare.com:10000")
    .retry(RetryPolicy {
        initial_delay: Duration::from_millis(100),
        max_delay: Duration::from_secs(30),
        multiplier: 2.0,
        max_attempts: 0,
    })
    .timeout_secs(30)
    .keepalive_secs(60)
    .ping_interval_secs(10)
    .max_missed_pongs(3)
    .channel_capacity(4096)
    .build()?;
```

Minimal setup:

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

### Builder methods

**`.url(url)`** - The primary gRPC endpoint. Falls back to `ORBITFLARE_GRPC_URL` env var.

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

**`.urls(&[...])`** - Primary + fallbacks in one call. First element is primary.

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

**`.fallback_url(url)` / `.fallback_urls(&[...])`** - Add fallback endpoints. On connection failure, the SDK rotates through them.

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

**`.retry(policy)`** - Controls reconnection backoff. When a connection drops, the SDK waits `initial_delay`, then doubles it each attempt (capped at `max_delay`). Set `max_attempts` to 0 for infinite retries. Default: 100ms initial, 30s max, 2x multiplier, infinite.

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

**`.timeout_secs(n)`** - Per-request gRPC timeout. Default: 30.

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

**`.keepalive_secs(n)`** - TCP keepalive interval. The OS sends probes at this interval to detect dead connections at the TCP level. Default: 60.

```rust theme={null}
.keepalive_secs(30)
```

**`.ping_interval_secs(n)`** - How often the SDK sends proto-level `Ping` messages to the server. The server should respond with a `Pong`. Default: 10.

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

**`.max_missed_pongs(n)`** - How many consecutive pings can go unanswered before the SDK considers the connection dead and reconnects. Default: 3. With the defaults, a dead connection is detected within 30 seconds.

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

**`.channel_capacity(n)`** - The bounded channel between the background task and your code. If your code is slow to consume events, the background task blocks when this fills up instead of eating unlimited memory. Default: 4096.

```rust theme={null}
.channel_capacity(8192)
```

## Writing a YAML config

Create a YAML file with the filters you want:

```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 filter reference

**`transactions`** - each entry is a named filter. A transaction matches if it involves any of the `account_include` addresses. `account_exclude` removes matches. `account_required` means all listed addresses must be present. `vote` and `failed` filter by transaction type.

**`accounts`** - watch specific accounts by address, or watch all accounts owned by a program via `owner`.

**`slots`** - subscribe to slot updates. `filter_by_commitment` only sends updates at your commitment level.

**`commitment`** - `"processed"`, `"confirmed"`, or `"finalized"`. Defaults to `"confirmed"`.

The YAML supports `${ENV_VAR}` expansion:

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

## Subscribing and reading events

### From YAML

```rust theme={null}
let mut stream = client.subscribe_yaml("grpc.yml")?;
```

### Programmatically

For dynamic filters built at runtime:

```rust theme={null}
use std::collections::HashMap;
use orbitflare_sdk::proto::geyser::*;

let mut filters = HashMap::new();
filters.insert("target".into(), SubscribeRequestFilterTransactions {
    vote: Some(false),
    failed: Some(false),
    signature: None,
    account_include: vec![some_address.to_string()],
    account_exclude: vec![],
    account_required: vec![],
});

let request = SubscribeRequest {
    transactions: filters,
    commitment: Some(1),
    ping: Some(SubscribeRequestPing { id: 1 }),
    ..Default::default()
};

let mut stream = client.subscribe(request);
```

### Reading the stream

Both `subscribe_yaml` and `subscribe` return a `GeyserStream`. Call `.next()` to get the next event:

```rust theme={null}
use orbitflare_sdk::proto::geyser::subscribe_update::UpdateOneof;

while let Some(update) = stream.next().await {
    let update = update?;
    match update.update_oneof {
        Some(UpdateOneof::Transaction(tx)) => {
            // tx.slot - the slot this transaction was in
            // tx.transaction - the transaction info (signature, accounts, instructions, meta)
        }
        Some(UpdateOneof::Account(acct)) => {
            // acct.slot - the slot
            // acct.account - account info (pubkey, lamports, owner, data)
            // acct.is_startup - true during initial account snapshot
        }
        Some(UpdateOneof::Slot(slot)) => {
            // slot.slot - the slot number
            // slot.status - processed, confirmed, finalized, etc.
        }
        Some(UpdateOneof::BlockMeta(meta)) => {
            // meta.slot, meta.blockhash, meta.parent_slot
            // meta.executed_transaction_count
        }
        _ => {}
    }
}
```

`stream.next()` blocks until an event arrives. Returns `None` when the stream is closed. Each event is a `Result` - an `Err` means all retries were exhausted and the connection is permanently gone.

Pong messages are consumed internally and never appear in your stream.

### Closing

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

Stops the background task immediately. Or just drop the stream.

## Multiple streams

One client can run many streams at once. Each gets its own background connection:

```rust theme={null}
let mut pumpfun = client.subscribe_yaml("config/pumpfun.yml")?;
let mut raydium = client.subscribe_yaml("config/raydium.yml")?;
let mut slots = client.subscribe_yaml("config/slots.yml")?;
```

They share endpoint health state - if one stream quarantines a failing endpoint, the others skip it on their next reconnect. But their connections and lifecycles are fully independent.

You can spin up new streams at any time, including dynamically based on data from an existing stream.

## Full example

A stream that watches pump.fun transactions, decodes the signature, and prints a summary for each one.

```rust theme={null}
use orbitflare_sdk::{GeyserClientBuilder, Result};
use orbitflare_sdk::proto::geyser::subscribe_update::UpdateOneof;

#[tokio::main]
async fn main() -> Result<()> {
    let client = GeyserClientBuilder::new()
        .url("http://ny.rpc.orbitflare.com:10000")
        .fallback_url("http://fra.rpc.orbitflare.com:10000")
        .build()?;

    let mut stream = client.subscribe_yaml("grpc.yml")?;
    let mut tx_count: u64 = 0;

    println!("streaming...");

    while let Some(update) = stream.next().await {
        let update = update?;

        match update.update_oneof {
            Some(UpdateOneof::Transaction(tx)) => {
                tx_count += 1;
                if let Some(info) = &tx.transaction {
                    let sig = bs58::encode(&info.signature).into_string();
                    let fee = info.meta.as_ref().map(|m| m.fee).unwrap_or(0);
                    let ix_count = info.transaction
                        .as_ref()
                        .and_then(|t| t.message.as_ref())
                        .map(|m| m.instructions.len())
                        .unwrap_or(0);

                    println!(
                        "#{tx_count} slot={} sig={}... fee={fee} instructions={ix_count}",
                        tx.slot,
                        &sig[..16],
                    );
                }
            }
            Some(UpdateOneof::Slot(slot)) => {
                println!("slot {} ({:?})", slot.slot, slot.status);
            }
            _ => {}
        }
    }

    Ok(())
}
```

With this `grpc.yml`:

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

slots:
  all:
    filter_by_commitment: true

commitment: confirmed
```
