in-solana by Supanode

Solana gRPC in Rust: Examples

TL;DR

  • - Use yellowstone-grpc-client crate for Rust gRPC connections
  • - Build filter requests with yellowstone-grpc-proto types
  • - Handle the bidirectional stream with tokio async
  • - Implement reconnection for production reliability

The yellowstone-grpc-client and yellowstone-grpc-proto crates provide a Rust client for Yellowstone gRPC. The client uses tonic for gRPC transport and tokio for async runtime.

Dependencies

[dependencies]
yellowstone-grpc-client = "2"
yellowstone-grpc-proto = "2"
tokio = { version = "1", features = ["full"] }
futures = "0.3"

Connecting

use yellowstone_grpc_client::GeyserGrpcClient;

let mut client = GeyserGrpcClient::build_from_uri("https://your-endpoint.example.com")
    .x_token(Some("your-auth-token".to_string()))
    .connect()
    .await?;

let (mut subscribe_tx, mut stream) = client.subscribe().await?;

Subscribing to accounts

use yellowstone_grpc_proto::prelude::*;
use std::collections::HashMap;

let mut accounts = HashMap::new();
accounts.insert(
    "token_accounts".to_string(),
    SubscribeRequestFilterAccounts {
        account: vec![],
        owner: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
        filters: vec![],
    },
);

subscribe_tx.send(SubscribeRequest {
    accounts,
    slots: HashMap::new(),
    transactions: HashMap::new(),
    blocks: HashMap::new(),
    blocks_meta: HashMap::new(),
    commitment: Some(CommitmentLevel::Confirmed as i32),
    ..Default::default()
}).await?;

Reading the stream

use futures::StreamExt;

while let Some(msg) = stream.next().await {
    match msg {
        Ok(update) => {
            if let Some(UpdateOneof::Account(account)) = update.update_oneof {
                let info = account.account.unwrap();
                println!("Account: {} ({} bytes)",
                    bs58::encode(&info.pubkey).into_string(),
                    info.data.len()
                );
            }
        }
        Err(e) => {
            eprintln!("Stream error: {e}");
            break;
        }
    }
}

Reconnection pattern

loop {
    match connect_and_subscribe().await {
        Ok(()) => break, // clean shutdown
        Err(e) => {
            eprintln!("Disconnected: {e}, reconnecting in 5s...");
            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
        }
    }
}

Wrap the connection and stream reading in a function and retry on error. Use exponential backoff for production deployments.

FAQ

Which Rust async runtime does it need? Tokio. The gRPC client uses tonic, which requires the tokio runtime.

Can I run multiple subscriptions? Send multiple named filters in a single SubscribeRequest. All matching data arrives on the same stream.

How do I parse transaction instructions? Use solana-sdk to deserialize the transaction bytes from the update, then match on program IDs and instruction data.

Updated: