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 pushes updates as the validator processes them. 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:
| Filter | Description | Page |
|---|---|---|
| Accounts | Stream account state changes by pubkey or owner | Account Subscriptions |
| Transactions | Stream full transaction data with account filters | Transaction Subscriptions |
| TransactionStatus | Lightweight transaction status updates | Transaction Status Subscriptions |
| Blocks | Stream complete block data | Block Subscriptions |
| BlockMeta | Lightweight block metadata | Block Meta Subscriptions |
| Slots | Slot progression and status changes | Slot Subscriptions |
| Entries | Ledger entry data | Entry Subscriptions |
| Programs | Monitor program deployments and upgrades | Program Subscriptions |
Commitment Levels
All filters share the top-level commitment field:
| Value | Name | Description |
|---|---|---|
| 0 | Processed | Earliest. Transaction has been processed by the current node but may be rolled back. |
| 1 | Confirmed | Transaction has been confirmed by supermajority of the cluster. Very unlikely to roll back. |
| 2 | Finalized | Transaction has been finalized. Cannot be rolled back. |
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
accounts | map<string, SubscribeRequestFilterAccounts> | No | Account subscription filters keyed by a label you choose |
slots | map<string, SubscribeRequestFilterSlots> | No | Slot subscription filters |
transactions | map<string, SubscribeRequestFilterTransactions> | No | Transaction subscription filters |
transactions_status | map<string, SubscribeRequestFilterTransactions> | No | Transaction status subscription filters (lightweight) |
blocks | map<string, SubscribeRequestFilterBlocks> | No | Block subscription filters |
blocks_meta | map<string, SubscribeRequestFilterBlocksMeta> | No | Block metadata subscription filters |
entry | map<string, SubscribeRequestFilterEntry> | No | Ledger entry subscription filters |
commitment | CommitmentLevel | No | Commitment level: Processed (0), Confirmed (1), or Finalized (2) |
accounts_data_slice | SubscribeRequestAccountsDataSlice[] | No | Request only specific byte ranges of account data |
ping | SubscribeRequestPing | No | Pong response to server ping (set id to match ping) |
from_slot | uint64 | No | Replay 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:
| Field | Type | Description |
|---|---|---|
account | SubscribeUpdateAccount | Account state change |
slot | SubscribeUpdateSlot | Slot progression |
transaction | SubscribeUpdateTransaction | Full transaction data |
transaction_status | SubscribeUpdateTransactionStatus | Lightweight transaction status |
block | SubscribeUpdateBlock | Complete block |
block_meta | SubscribeUpdateBlockMeta | Block metadata only |
entry | SubscribeUpdateEntry | Ledger entry |
ping | SubscribeUpdatePing | Keep-alive ping from server |
pong | SubscribeUpdatePong | Acknowledgement 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
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDef = protoLoader.loadSync('geyser.proto', {
keepCase: true, longs: String, enums: String, defaults: true, oneofs: true
});
const proto = grpc.loadPackageDefinition(packageDef).geyser;
const client = new proto.Geyser('88.216.36.108:10001', grpc.credentials.createInsecure());
const stream = client.subscribe();
// Handle incoming updates
stream.on('data', (update) => {
if (update.account) {
console.log('[Account]', Buffer.from(update.account.account.pubkey).toString('hex'));
} else if (update.transaction) {
console.log('[Tx]', Buffer.from(update.transaction.transaction.signature).toString('hex'));
} else if (update.slot) {
console.log('[Slot]', update.slot.slot, update.slot.status);
} else if (update.ping) {
// Respond to ping with pong
stream.write({ ping: { id: update.ping.id } });
}
});
stream.on('error', (err) => {
console.error('Stream error:', err);
// Implement reconnection logic here
});
// Send subscription request -- monitor USDC account + all slots
stream.write({
accounts: {
usdc: {
account: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
owner: [],
filters: []
}
},
slots: {
allSlots: { filter_by_commitment: false }
},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
commitment: 1,
entry: {},
accountsDataSlice: [],
ping: null
});Rust
use yellowstone_grpc_client::GeyserGrpcClient;
use yellowstone_grpc_proto::prelude::*;
use std::collections::HashMap;
use futures::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut client = GeyserGrpcClient::build_from_uri("http://88.216.36.108:10001")
.connect()
.await?;
// Build subscription: accounts (USDC) + all slots
let mut accounts = HashMap::new();
accounts.insert("usdc".to_string(), SubscribeRequestFilterAccounts {
account: vec!["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string()],
owner: vec![],
filters: vec![],
nonempty_txn_signature: None,
});
let mut slots = HashMap::new();
slots.insert("allSlots".to_string(), SubscribeRequestFilterSlots {
filter_by_commitment: Some(false),
interleave: None,
});
let request = SubscribeRequest {
accounts,
slots,
transactions: HashMap::new(),
transactions_status: HashMap::new(),
blocks: HashMap::new(),
blocks_meta: HashMap::new(),
commitment: Some(CommitmentLevel::Confirmed as i32),
entry: HashMap::new(),
accounts_data_slice: vec![],
ping: None,
from_slot: None,
};
let (_, mut stream) = client.subscribe_with_request(Some(request)).await?;
while let Some(msg) = stream.next().await {
match msg?.update_oneof {
Some(UpdateOneof::Account(acct)) => {
println!("Account update: {:?}", acct.account.map(|a| a.pubkey));
}
Some(UpdateOneof::Slot(slot)) => {
println!("Slot: {} status: {:?}", slot.slot, slot.status);
}
Some(UpdateOneof::Ping(_)) => {
println!("Received ping -- connection alive");
}
_ => {}
}
}
Ok(())
}Example Response
{
"filters": ["usdc"],
"account": {
"account": {
"pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"lamports": "2039280",
"owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"data": "...",
"write_version": "12345678",
"slot": "250000042"
},
"slot": "250000042",
"is_startup": false
}
}{
"filters": ["allSlots"],
"slot": {
"slot": "250000043",
"parent": "250000042",
"status": "CONFIRMED"
}
}