Skip to main content

Overview

The Subscribe method establishes a bidirectional streaming connection that allows you to receive real-time updates from the Solana blockchain. You can subscribe to various update types including accounts, transactions, slots, blocks, and entries.
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeUpdate) {}

How It Works

Subscribe uses bidirectional streaming, which means:
  1. Client sends initial request: You send a SubscribeRequest with filters defining what data you want to receive
  2. Server streams updates: The server continuously sends SubscribeUpdate messages matching your filters
  3. Dynamic updates: You can send additional SubscribeRequest messages at any time to add, modify, or remove filters
  4. Ping/Pong keepalive: The connection supports ping/pong messages to keep load balancers and firewalls happy

SubscribeRequest

The request message defines filters for the types of updates you want to receive.
accounts
map<string, SubscribeRequestFilterAccounts>
Map of account filters. Key is a user-defined filter name, value contains filter criteria.
slots
map<string, SubscribeRequestFilterSlots>
Map of slot filters. Subscribe to slot status updates (processed, confirmed, finalized, etc.).
transactions
map<string, SubscribeRequestFilterTransactions>
Map of transaction filters. Receive transaction updates with full execution details and metadata.
transactions_status
map<string, SubscribeRequestFilterTransactions>
Map of transaction status filters. Receive lightweight transaction status updates (success/failure) without full transaction data.
blocks
map<string, SubscribeRequestFilterBlocks>
Map of block filters. Receive complete block data including transactions, accounts, and entries.
blocks_meta
map<string, SubscribeRequestFilterBlocksMeta>
Map of block metadata filters. Receive block metadata without full transaction/account data.
entry
map<string, SubscribeRequestFilterEntry>
Map of entry filters. Subscribe to ledger entry updates.
commitment
CommitmentLevel
Commitment level for updates: PROCESSED (0), CONFIRMED (1), or FINALIZED (2). If not specified, defaults to PROCESSED.
accounts_data_slice
SubscribeRequestAccountsDataSlice[]
Array of data slices to receive only portions of account data. Useful for large accounts when you only need specific data ranges.
ping
SubscribeRequestPing
Send a ping request. Server will respond with a pong message containing the same ID.
from_slot
uint64
Start receiving updates from a specific slot number. Useful for replaying historical data.

Account Filters

account
string[]
List of account public keys to subscribe to (base58 encoded).
owner
string[]
List of program owner public keys. Receives updates for all accounts owned by these programs.
filters
SubscribeRequestFilterAccountsFilter[]
Additional filters: memcmp (memory comparison), datasize (account data size), token_account_state (valid token accounts), lamports (balance filters).
nonempty_txn_signature
bool
Only receive account updates that include a transaction signature.

Transaction Filters

vote
bool
Filter vote transactions. Set to false to exclude vote transactions.
failed
bool
Filter failed transactions. Set to true to include failed transactions.
signature
string
Subscribe to a specific transaction signature.
account_include
string[]
List of accounts. Receive transactions that include any of these accounts.
account_exclude
string[]
List of accounts. Exclude transactions that include any of these accounts.
account_required
string[]
List of accounts. Receive transactions that include all of these accounts.

SubscribeUpdate

The server responds with a stream of update messages.
filters
string[]
Names of the filters that matched this update.
update_oneof
oneof
One of the following update types:
  • account: Account update with balance, data, and ownership changes
  • slot: Slot status update (processed, confirmed, finalized, rooted, dead, etc.)
  • transaction: Full transaction with metadata and execution details
  • transaction_status: Lightweight transaction status (success/error)
  • block: Complete block data
  • block_meta: Block metadata without full transaction data
  • entry: Ledger entry update
  • ping: Server-initiated ping for keepalive
  • pong: Response to client ping
created_at
google.protobuf.Timestamp
Timestamp when the update was created on the server.

Account Update

account.pubkey
bytes
Account public key (32 bytes).
account.lamports
uint64
Account balance in lamports.
account.owner
bytes
Program that owns this account (32 bytes).
account.executable
bool
Whether this account contains executable program data.
account.rent_epoch
uint64
Epoch when rent is next due.
account.data
bytes
Account data.
account.write_version
uint64
Monotonically increasing version number.
account.txn_signature
bytes
Transaction signature that caused this account update.
slot
uint64
Slot number for this update.
is_startup
bool
True if this is a startup/snapshot account (not from a transaction).

Transaction Update

transaction.signature
bytes
Transaction signature (64 bytes).
transaction.is_vote
bool
Whether this is a vote transaction.
transaction.transaction
solana.storage.ConfirmedBlock.Transaction
Full transaction data including message and signatures.
transaction.meta
solana.storage.ConfirmedBlock.TransactionStatusMeta
Transaction metadata including status, fee, logs, compute units, and account changes.
transaction.index
uint64
Transaction index within the block.
slot
uint64
Slot number containing this transaction.

Examples

use yellowstone_grpc_client::GeyserGrpcClient;
use yellowstone_grpc_proto::prelude::{
    SubscribeRequest, SubscribeRequestFilterSlots,
    SubscribeRequestFilterTransactions, CommitmentLevel,
};
use futures::stream::StreamExt;
use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to the gRPC endpoint
    let mut client = GeyserGrpcClient::build_from_shared("http://127.0.0.1:10000")?
        .x_token(Some("your-token-here"))?
        .connect()
        .await?;

    // Create subscription request
    let mut slots = HashMap::new();
    slots.insert(
        "client".to_string(),
        SubscribeRequestFilterSlots {
            filter_by_commitment: Some(true),
            ..Default::default()
        },
    );

    let mut transactions = HashMap::new();
    transactions.insert(
        "client".to_string(),
        SubscribeRequestFilterTransactions {
            vote: Some(false),
            failed: Some(false),
            account_include: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
            ..Default::default()
        },
    );

    let request = SubscribeRequest {
        slots,
        transactions,
        commitment: Some(CommitmentLevel::Confirmed as i32),
        ..Default::default()
    };

    // Subscribe and receive updates
    let (mut subscribe_tx, mut stream) = client
        .subscribe_with_request(Some(request))
        .await?;

    println!("Subscription started, receiving updates...");
    
    while let Some(message) = stream.next().await {
        match message {
            Ok(msg) => {
                println!("Received update: filters={:?}", msg.filters);
                // Handle different update types
                if let Some(update) = msg.update_oneof {
                    // Process the update
                }
            }
            Err(error) => {
                eprintln!("Error: {:?}", error);
                break;
            }
        }
    }

    Ok(())
}

Dynamic Filter Updates

You can update your subscription filters at any time by sending a new SubscribeRequest on the existing stream. This allows you to add new filters, remove existing ones, or modify filter parameters without reconnecting.
// Later, update the subscription to add account filters
let mut accounts = HashMap::new();
accounts.insert(
    "token_accounts".to_string(),
    SubscribeRequestFilterAccounts {
        owner: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
        ..Default::default()
    },
);

let update_request = SubscribeRequest {
    accounts,
    ..Default::default()
};

subscribe_tx.send(update_request).await?;

Keepalive with Ping/Pong

For long-running connections, especially behind load balancers, use ping/pong messages:
// Respond to server pings
if let Some(UpdateOneof::Ping(_)) = msg.update_oneof {
    subscribe_tx.send(SubscribeRequest {
        ping: Some(SubscribeRequestPing { id: 1 }),
        ..Default::default()
    }).await?;
}

Best Practices

  • Use specific filters: Subscribe only to the data you need to reduce bandwidth and processing overhead
  • Handle backpressure: Process updates quickly or use buffering to avoid blocking the stream
  • Set appropriate commitment: Use PROCESSED for real-time data, CONFIRMED for reduced reorg risk, or FINALIZED for immutability
  • Implement reconnection logic: Networks are unreliable; implement exponential backoff and reconnection
  • Monitor memory: Account and transaction data can be large; use data slices for accounts when possible
  • Filter votes: Most applications should set vote: false to exclude vote transactions

Build docs developers (and LLMs) love