Rust SDK for OrbitFlare - RPC, gRPC (Yellowstone Geyser), JetStream, and WebSocket clients for Solana.
cargo add orbitflare-sdkOnly the RPC client is enabled by default. Enable what you need:
cargo add orbitflare-sdk --features grpc # gRPC streaming (Yellowstone Geyser)
cargo add orbitflare-sdk --features jetstream # JetStream streaming
cargo add orbitflare-sdk --features ws # WebSocket subscriptions
cargo add orbitflare-sdk --features all # EverythingOr in your Cargo.toml:
[dependencies]
orbitflare-sdk = "0.3.0" # RPC only (default)
orbitflare-sdk = { version = "0.3.0", features = ["grpc"] } # gRPC
orbitflare-sdk = { version = "0.3.0", features = ["all"] } # Everythinguse orbitflare_sdk::{RpcClientBuilder, Result};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<()> {
let client = RpcClientBuilder::new()
.url("http://ny.rpc.orbitflare.com")
.commitment("confirmed")
.build()?;
let slot = client.get_slot().await?;
let balance = client.get_balance("CKs1E69a2e9TmH4mKKLrXFF8kD3ZnwKjoEuXa6sz9WqX").await?;
let (blockhash, last_valid) = client.get_latest_blockhash().await?;
let inflation = client.request("getInflationRate", json!([])).await?;
let raw = client
.request_raw(r#"{"jsonrpc":"2.0","id":1,"method":"getHealth","params":[]}"#)
.await?;
Ok(())
}| Method | Returns |
|---|---|
get_slot() |
Current slot (u64) |
get_balance(address) |
Lamports (u64) |
get_account_info(address) |
Account data or None |
get_multiple_accounts(addresses) |
Vec of accounts (auto-chunks to 100) |
get_latest_blockhash() |
(blockhash, last_valid_block_height) |
get_transaction(signature) |
Full transaction with metadata |
get_signatures_for_address(address, limit) |
Recent signatures |
get_program_accounts(program_id) |
All accounts owned by a program |
get_recent_prioritization_fees(addresses) |
Recent priority fees |
send_transaction(tx_base64) |
Signature string |
simulate_transaction(tx_base64) |
Simulation result |
get_token_accounts_by_owner(owner, mint, program) |
Token accounts |
get_transactions_for_address(address, options) |
Full transaction history with filters and pagination (OrbitFlare-specific) |
request(method, params) |
Any RPC method by name |
request_raw(body) |
Raw JSON-RPC body string |
Subscribe using a YAML config file:
# grpc.yml
transactions:
pumpfun:
vote: false
failed: false
account_include:
- "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
commitment: confirmeduse 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")
.build()?;
let mut stream = client.subscribe_yaml("grpc.yml")?;
while let Some(update) = stream.next().await {
let update = update?;
if let Some(UpdateOneof::Transaction(tx)) = update.update_oneof {
println!("slot={}", tx.slot);
}
}
Ok(())
}The YAML format supports ${ENV_VAR} expansion.
Build the request programmatically with the typed builder instead of YAML:
use orbitflare_sdk::grpc::{
AccountFilter, Commitment, Lamports, Memcmp, SlotFilter, SubscribeRequestBuilder,
TransactionFilter,
};
let request = SubscribeRequestBuilder::new()
.transactions(
"pumpfun",
TransactionFilter::new()
.vote(false)
.failed(false)
.account_include(["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"])
.account_required(["So11111111111111111111111111111111111111112"]),
)
.accounts(
"wsol-token-accounts",
AccountFilter::new()
.owner(["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"])
.datasize(165)
.memcmp(Memcmp::base58(0, "So11111111111111111111111111111111111111112"))
.lamports(Lamports::Gt(0)),
)
.slots("slots", SlotFilter::new().filter_by_commitment(true))
.commitment(Commitment::Confirmed)
.build();
let mut stream = client.subscribe(request);client.subscribe(request) also accepts a raw proto SubscribeRequest if you'd rather construct it by hand.
# jetstream.yml
transactions:
raydium:
account_include:
- "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"use orbitflare_sdk::{JetstreamClientBuilder, Result};
use orbitflare_sdk::proto::jetstream::subscribe_update::UpdateOneof;
#[tokio::main]
async fn main() -> Result<()> {
let client = JetstreamClientBuilder::new()
.url("http://ny.jetstream.orbitflare.com")
.build()?;
let mut stream = client.subscribe_yaml("jetstream.yml")?;
while let Some(update) = stream.next().await {
let update = update?;
if let Some(UpdateOneof::Transaction(tx)) = update.update_oneof {
println!("slot={}", tx.slot);
}
}
Ok(())
}Or build the request with the typed builder:
use orbitflare_sdk::jetstream::{SubscribeRequestBuilder, TransactionFilter};
let request = SubscribeRequestBuilder::new()
.transactions(
"pumpfun",
TransactionFilter::new()
.account_include(["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"])
.account_required(["So11111111111111111111111111111111111111112"]),
)
.build();
let mut stream = client.subscribe(request);v2 (jetstream::v2) runs on the same endpoint and auth as v1 and is fully additive. Filters are managed at runtime over a bidirectional stream (no reconnect to change them), every response carries a monotonic sequence for gap detection, slot lifecycle events are a separate stream, and transactions can opt into enrichment (fee payer, program ids, compute-unit price, loaded address-table addresses, and more).
use orbitflare_sdk::jetstream::v2::{JetstreamClientBuilder, TransactionFilter};
use orbitflare_sdk::proto::jetstream::v2::subscribe_transactions_response::Payload;
use orbitflare_sdk::Result;
#[tokio::main]
async fn main() -> Result<()> {
let client = JetstreamClientBuilder::new()
.url("http://ny.jetstream.orbitflare.com")
.build()?;
println!("version={}", client.get_version().await?);
let filter = TransactionFilter::new()
.account_include(["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"])
.include_enrichment(true)
.with_id("pumpfun");
let mut stream = client.subscribe_transactions(vec![filter]);
while let Some(resp) = stream.next().await {
match resp?.payload {
Some(Payload::Transaction(ft)) => {
if let Some(tx) = &ft.transaction {
println!("slot={} cu_price={}", tx.slot, tx.compute_unit_price);
}
}
Some(Payload::FilterValidation(r)) => {
println!("filter {} accepted={}", r.filter_id, r.accepted);
}
_ => {}
}
}
Ok(())
}Add or remove filters on a live stream without reconnecting:
let handle = stream.handle();
handle.add_filters(vec![
TransactionFilter::new().account_include(["..."]).with_id("raydium"),
])?;
handle.remove_filters(vec!["pumpfun".to_string()])?;Slot lifecycle events are a separate server stream:
let mut slots = client.subscribe_slots();
while let Some(event) = slots.next().await {
let event = event?;
println!("slot={} status={:?}", event.slot, event.status());
}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 sub = client.slot_subscribe().await?;
while let Some(slot) = sub.next().await {
println!("{slot}");
}
Ok(())
}Subscription types: account_subscribe, logs_subscribe, slot_subscribe, signature_subscribe. All auto-resubscribe on reconnect.
All clients support multiple endpoints with automatic failover and health tracking.
let client = RpcClientBuilder::new()
.urls(&[
"http://ny.rpc.orbitflare.com",
"http://fra.rpc.orbitflare.com",
"http://ams.rpc.orbitflare.com",
])
.build()?;Failing endpoints are quarantined with exponential cooldown (10s, 20s, 40s, max 60s) and automatically retried once the cooldown expires. Healthy endpoints are always preferred.
RPC calls retry on transient errors (5xx, 429, connection resets, Solana error codes -32005, -32007, -32014, -32015, -32016) with exponential backoff before failing over to the next endpoint. 429 responses with a Retry-After header are respected.
gRPC, JetStream, and WebSocket connections use active ping/pong to detect dead connections. Configurable via the builder:
let client = GeyserClientBuilder::new()
.url("http://ny.rpc.orbitflare.com:10000")
.ping_interval_secs(15) // send a ping every 15s (default: 10)
.max_missed_pongs(5) // kill connection after 5 missed pongs (default: 3)
.build()?;All three streaming clients reconnect automatically on disconnection. WebSocket also re-subscribes all active subscriptions after reconnecting.
Configure retry behavior:
use orbitflare_sdk::RetryPolicy;
use std::time::Duration;
let client = RpcClientBuilder::new()
.url("http://ny.rpc.orbitflare.com")
.retry(RetryPolicy {
initial_delay: Duration::from_millis(200),
max_delay: Duration::from_secs(15),
multiplier: 2.0,
max_attempts: 5,
})
.build()?;| Variable | Used by | Purpose |
|---|---|---|
ORBITFLARE_LICENSE_KEY |
RPC, WebSocket | API key appended to endpoint URLs |
ORBITFLARE_RPC_URL |
RPC | Default endpoint if .url() is not called |
ORBITFLARE_WS_URL |
WebSocket | Default endpoint if .url() is not called |
ORBITFLARE_GRPC_URL |
gRPC | Default endpoint if .url() is not called |
ORBITFLARE_JETSTREAM_URL |
JetStream | Default endpoint if .url() is not called |
