Subscribe

The Subscribe method is the heart of the Yellowstone Geyser gRPC interface. It establishes a bidirectional streaming connection that multiplexes all real-time subscriptions into a single gRPC stream. Clients send SubscribeRequest messages with filter configurations, and the server pushes SubscribeUpdate messages as matching on-chain events occur.

This is the most powerful and commonly used Geyser method. It replaces JSON-RPC polling and WebSocket subscriptions with a single, persistent connection that delivers sub-millisecond latency updates. Every HFT bot, MEV searcher, and real-time indexer on Solana should use this method.

Type: Bidirectional Streaming

Subscription Filter Types

Each filter type is documented in detail on its own page:

FilterDescriptionPage
AccountsStream account state changes by pubkey or ownerAccount Subscriptions
TransactionsStream full transaction data with account filtersTransaction Subscriptions
TransactionStatusLightweight transaction status updatesTransaction Status Subscriptions
BlocksStream complete block dataBlock Subscriptions
BlockMetaLightweight block metadataBlock Meta Subscriptions
SlotsSlot progression and status changesSlot Subscriptions
EntriesLedger entry dataEntry Subscriptions
ProgramsMonitor program deployments and upgradesProgram Subscriptions

Commitment Levels

All filters share the top-level commitment field:

ValueNameDescription
0ProcessedFastest. Transaction has been processed by the current node but may be rolled back.
1ConfirmedTransaction has been confirmed by supermajority of the cluster. Very unlikely to roll back.
2FinalizedTransaction has been finalized. Cannot be rolled back.

Parameters

ParameterTypeRequiredDescription
accountsmap<string, SubscribeRequestFilterAccounts>NoAccount subscription filters keyed by a label you choose
slotsmap<string, SubscribeRequestFilterSlots>NoSlot subscription filters
transactionsmap<string, SubscribeRequestFilterTransactions>NoTransaction subscription filters
transactions_statusmap<string, SubscribeRequestFilterTransactions>NoTransaction status subscription filters (lightweight)
blocksmap<string, SubscribeRequestFilterBlocks>NoBlock subscription filters
blocks_metamap<string, SubscribeRequestFilterBlocksMeta>NoBlock metadata subscription filters
entrymap<string, SubscribeRequestFilterEntry>NoLedger entry subscription filters
commitmentCommitmentLevelNoCommitment level: Processed (0), Confirmed (1), or Finalized (2)
accounts_data_sliceSubscribeRequestAccountsDataSlice[]NoRequest only specific byte ranges of account data
pingSubscribeRequestPingNoPong response to server ping (set id to match ping)
from_slotuint64NoReplay updates from this slot (if the node supports historical replay)

Response

Each SubscribeUpdate message contains a filters array (your label strings) and exactly one of:

FieldTypeDescription
accountSubscribeUpdateAccountAccount state change
slotSubscribeUpdateSlotSlot progression
transactionSubscribeUpdateTransactionFull transaction data
transaction_statusSubscribeUpdateTransactionStatusLightweight transaction status
blockSubscribeUpdateBlockComplete block
block_metaSubscribeUpdateBlockMetaBlock metadata only
entrySubscribeUpdateEntryLedger entry
pingSubscribeUpdatePingKeep-alive ping from server
pongSubscribeUpdatePongAcknowledgement of your ping

Ping/Pong Keep-Alive

The server periodically sends SubscribeUpdatePing messages to keep the connection alive. Your client should respond with a SubscribeRequest containing a ping field to acknowledge. If the server does not receive a pong within the timeout window, it may close the connection.

Reconnection Strategy

gRPC streams can drop due to network issues, server restarts, or idle timeouts. Implement exponential backoff reconnection logic in your client. When reconnecting, re-send your full SubscribeRequest -- the server does not persist subscription state across connections.

Code Examples

Node.js

1const grpc = require('@grpc/grpc-js');
2const protoLoader = require('@grpc/proto-loader');
3
4const packageDef = protoLoader.loadSync('geyser.proto', {
5  keepCase: true, longs: String, enums: String, defaults: true, oneofs: true
6});
7const proto = grpc.loadPackageDefinition(packageDef).geyser;
8const client = new proto.Geyser('[IP_ADDRESS]:[PORT]', grpc.credentials.createInsecure());
9
10const stream = client.subscribe();
11
12// Handle incoming updates
13stream.on('data', (update) => {
14  if (update.account) {
15    console.log('[Account]', Buffer.from(update.account.account.pubkey).toString('hex'));
16  } else if (update.transaction) {
17    console.log('[Tx]', Buffer.from(update.transaction.transaction.signature).toString('hex'));
18  } else if (update.slot) {
19    console.log('[Slot]', update.slot.slot, update.slot.status);
20  } else if (update.ping) {
21    // Respond to ping with pong
22    stream.write({ ping: { id: update.ping.id } });
23  }
24});
25
26stream.on('error', (err) => {
27  console.error('Stream error:', err);
28  // Implement reconnection logic here
29});
30
31// Send subscription request -- monitor USDC account + all slots
32stream.write({
33  accounts: {
34    usdc: {
35      account: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
36      owner: [],
37      filters: []
38    }
39  },
40  slots: {
41    allSlots: { filter_by_commitment: false }
42  },
43  transactions: {},
44  transactionsStatus: {},
45  blocks: {},
46  blocksMeta: {},
47  commitment: 1,
48  entry: {},
49  accountsDataSlice: [],
50  ping: null
51});

Rust

1use yellowstone_grpc_client::GeyserGrpcClient;
2use yellowstone_grpc_proto::prelude::*;
3use std::collections::HashMap;
4use futures::StreamExt;
5
6#[tokio::main]
7async fn main() -> anyhow::Result<()> {
8    let mut client = GeyserGrpcClient::build_from_uri("http://[IP_ADDRESS]:[PORT]")
9        .connect()
10        .await?;
11
12    // Build subscription: accounts (USDC) + all slots
13    let mut accounts = HashMap::new();
14    accounts.insert("usdc".to_string(), SubscribeRequestFilterAccounts {
15        account: vec!["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string()],
16        owner: vec![],
17        filters: vec![],
18        nonempty_txn_signature: None,
19    });
20
21    let mut slots = HashMap::new();
22    slots.insert("allSlots".to_string(), SubscribeRequestFilterSlots {
23        filter_by_commitment: Some(false),
24        interleave: None,
25    });
26
27    let request = SubscribeRequest {
28        accounts,
29        slots,
30        transactions: HashMap::new(),
31        transactions_status: HashMap::new(),
32        blocks: HashMap::new(),
33        blocks_meta: HashMap::new(),
34        commitment: Some(CommitmentLevel::Confirmed as i32),
35        entry: HashMap::new(),
36        accounts_data_slice: vec![],
37        ping: None,
38        from_slot: None,
39    };
40
41    let (_, mut stream) = client.subscribe_with_request(Some(request)).await?;
42
43    while let Some(msg) = stream.next().await {
44        match msg?.update_oneof {
45            Some(UpdateOneof::Account(acct)) => {
46                println!("Account update: {:?}", acct.account.map(|a| a.pubkey));
47            }
48            Some(UpdateOneof::Slot(slot)) => {
49                println!("Slot: {} status: {:?}", slot.slot, slot.status);
50            }
51            Some(UpdateOneof::Ping(_)) => {
52                println!("Received ping -- connection alive");
53            }
54            _ => {}
55        }
56    }
57
58    Ok(())
59}

Example Response

1{
2  "filters": ["usdc"],
3  "account": {
4    "account": {
5      "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
6      "lamports": "2039280",
7      "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
8      "data": "...",
9      "write_version": "12345678",
10      "slot": "250000042"
11    },
12    "slot": "250000042",
13    "is_startup": false
14  }
15}
1{
2  "filters": ["allSlots"],
3  "slot": {
4    "slot": "250000043",
5    "parent": "250000042",
6    "status": "CONFIRMED"
7  }
8}