Skip to main content

Install

cargo add orbitflare-sdk --features grpc

Building the client

Here’s a client with every option set:
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:
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.
.url("http://ny.rpc.orbitflare.com:10000")
.urls(&[...]) - Primary + fallbacks in one call. First element is primary.
.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.
.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.
.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.
.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.
.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.
.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.
.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.
.channel_capacity(8192)

Writing a YAML config

Create a YAML file with the filters you want:
# 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:
transactions:
  target:
    account_include:
      - "${TARGET_PROGRAM}"

Subscribing and reading events

From YAML

let mut stream = client.subscribe_yaml("grpc.yml")?;

Programmatically

For dynamic filters built at runtime:
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:
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

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:
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.
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:
transactions:
  pumpfun:
    vote: false
    failed: false
    account_include:
      - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"

slots:
  all:
    filter_by_commitment: true

commitment: confirmed