Solana gRPC Streaming API
AllenHark provides Solana gRPC streaming powered by the Yellowstone Geyser plugin. Stream real-time account updates, transactions, blocks, and slot notifications directly from the validator's Geyser plugin, pushed to you instead of polled.
Why gRPC Over WebSocket
| Feature | AllenHark gRPC | Standard WebSocket |
|---|---|---|
| Protocol | HTTP/2 multiplexed streams | Single TCP connection |
| Delivery | Pushed from the validator's Geyser plugin | Pushed by the RPC layer, or polled |
| Filtering | Server-side (only matching data sent) | Client-side (all data sent, filter locally) |
| Streams | One Subscribe stream per connection you buy, on one IP or split across several; each carries every filter you set | One subscription per method call |
| Backpressure | Built-in flow control | Manual handling |
| Data Source | Direct from validator (Geyser) | Processed via RPC layer |
gRPC suits HFT bots, MEV searchers, arbitrage systems, snipers and real-time analytics: anything that needs account and transaction changes as the validator processes them.
Getting Started
1. Get Your Endpoint
Buy a gRPC plan at checkout (see pricing) and enter your server's IP address. That address is added to the allowlist — no API keys needed.
The endpoint is plaintext HTTP/2 on port 10001: Frankfurt 88.216.36.108:10001 or Amsterdam 84.32.104.38:10001. The examples below use Frankfurt.
2. Install Dependencies
Node.js
npm install @grpc/grpc-js @grpc/proto-loaderRust
Add to Cargo.toml:
[dependencies]
yellowstone-grpc-client = "6.0.0"
yellowstone-grpc-proto = "6.0.0"
tokio = { version = "1.0", features = ["full"] }
tonic = "0.12"
futures = "0.3"3. Get the Proto File
Download the Yellowstone Geyser proto definition:
curl -o geyser.proto https://raw.githubusercontent.com/rpcpool/yellowstone-grpc/master/yellowstone-grpc-proto/proto/geyser.proto4. Connect and Subscribe
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(new grpc.Metadata());
stream.on('data', (update) => {
if (update.account) {
console.log('Account update:', update.account.account.pubkey);
} else if (update.transaction) {
console.log('Transaction:', update.transaction.transaction.signature);
} else if (update.slot) {
console.log('Slot:', update.slot.slot);
}
});
stream.on('error', (err) => {
console.error('Stream error:', err.message);
// Implement reconnection logic here
});
// Subscribe to all slot updates and Raydium program accounts
stream.write({
accounts: {
raydium: {
account: [],
owner: ['675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'],
filters: []
}
},
slots: { slots: {} },
transactions: {},
blocks: {},
blocksMeta: {},
commitment: 1, // Processed
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?;
// Subscribe to Raydium program accounts and all slots
let mut accounts = HashMap::new();
accounts.insert("raydium".to_string(), SubscribeRequestFilterAccounts {
account: vec![],
owner: vec!["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8".to_string()],
filters: vec![],
nonempty_txn_signature: None,
});
let mut slots = HashMap::new();
slots.insert("slots".to_string(), SubscribeRequestFilterSlots {
filter_by_commitment: None,
interleave_updates: None,
});
let request = SubscribeRequest {
accounts,
slots,
transactions: HashMap::new(),
blocks: HashMap::new(),
blocks_meta: HashMap::new(),
commitment: Some(CommitmentLevel::Processed as i32),
entry: HashMap::new(),
accounts_data_slice: vec![],
ping: None,
from_slot: None,
transactions_status: HashMap::new(),
};
let (_, mut stream) = client.subscribe_with_request(Some(request)).await?;
while let Some(msg) = stream.next().await {
match msg?.update_oneof {
Some(UpdateOneof::Account(account)) => {
println!("Account update: {:?}", account);
}
Some(UpdateOneof::Slot(slot)) => {
println!("Slot: {}", slot.slot);
}
_ => {}
}
}
Ok(())
}API Reference
Unary Methods (Request/Response)
Standard request-response methods available alongside streaming:
| Method | Description |
|---|---|
| GetLatestBlockhash | Returns the latest blockhash for transaction signing |
| GetBlockHeight | Returns the current block height |
| GetSlot | Returns the current slot |
| GetBlock | Returns full block data for a given slot |
| GetTransaction | Returns transaction details by signature |
| GetVersion | Returns the Geyser plugin version |
| GetGenesisHash | Returns the genesis hash |
| GetFeeForMessage | Returns the fee for a given message |
| IsBlockhashValid | Checks if a blockhash is still valid |
| Subscribe | Opens a bidirectional streaming subscription |
Subscription Filters
The Subscribe method accepts filters for different data types. Each filter type streams specific on-chain data in real-time:
| Subscription | Description | Common Use Case |
|---|---|---|
| Accounts | Real-time account state changes | Token balances, program state, wallet monitoring |
| Transactions | Individual transactions as they're processed | DEX trade monitoring, copy trading, MEV |
| TransactionStatus | Transaction confirmation status updates | Confirmation tracking without full tx data |
| Blocks | Complete block data including all transactions | Block explorers, full indexing |
| BlockMeta | Block metadata without transaction details | Slot monitoring, block timing |
| Slots | Slot progression notifications | Leader tracking, timing synchronization |
| Entries | Ledger entry data | Advanced analytics, validator monitoring |
| Programs | Program deployment and upgrade events | Protocol monitoring, security alerts |
Common Patterns
Monitor a DEX Pool (e.g., Raydium AMM)
stream.write({
accounts: {
pool: {
account: ['POOL_ACCOUNT_PUBKEY'],
owner: [],
filters: []
}
},
slots: {},
transactions: {},
blocks: {},
blocksMeta: {},
commitment: 1,
entry: {}
});Track All Transactions for a Program
stream.write({
accounts: {},
slots: {},
transactions: {
pumpfun: {
vote: false,
failed: false,
account_include: ['6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'],
account_exclude: [],
account_required: []
}
},
blocks: {},
blocksMeta: {},
commitment: 1,
entry: {}
});Watch Multiple Wallets
stream.write({
accounts: {
wallets: {
account: [
'WALLET_PUBKEY_1',
'WALLET_PUBKEY_2',
'WALLET_PUBKEY_3'
],
owner: [],
filters: []
}
},
slots: {},
transactions: {},
blocks: {},
blocksMeta: {},
commitment: 1,
entry: {}
});Performance Tips
- Use server-side filters — only subscribe to accounts and programs you need. Every filter reduces bandwidth and processing on your end.
- Process updates asynchronously — use worker threads or async channels to avoid blocking the stream reader.
- Implement reconnection logic — gRPC streams can disconnect. Always handle errors and reconnect automatically.
- Use
processedcommitment — for the earliest updates, subscribe atprocessedlevel. Useconfirmedonly if you need higher certainty. - Batch state updates — if you receive many account updates per second, batch them before writing to storage.
Error Handling and Reconnection
function connectWithRetry() {
const stream = client.subscribe(new grpc.Metadata());
stream.on('data', handleUpdate);
stream.on('error', (err) => {
console.error('Stream error:', err.message);
setTimeout(connectWithRetry, 1000); // Reconnect after 1 second
});
stream.on('end', () => {
console.log('Stream ended, reconnecting...');
setTimeout(connectWithRetry, 1000);
});
// Send subscription request
stream.write(subscriptionRequest);
}
connectWithRetry();Support
- Discord: discord.gg/JpzS72MAKG
- Website: allenhark.com
- Yellowstone gRPC Repo: github.com/rpcpool/yellowstone-grpc
