gRPC Streaming
Real-time account and transaction streaming via high-performance gRPC. Powered by custom Geyser plugins.
Features
- Sub-millisecond Latency: Faster than WebSocket
- Account Streaming: Real-time account updates
- Transaction Streaming: Pre-confirmation transaction data
- Block Streaming: Full block data as it's produced
- Filtered Streams: Subscribe to specific accounts/programs
Endpoint
You connect from an IP on your allowlist, or with an API key. Once your server IP is on the allowlist, you can access the endpoint directly with no token. New subscriptions also come with an API key: send it as an x-token header to connect from any address.
The endpoint is plaintext HTTP/2 (h2c) on port 10001, in two regions. The examples below use Frankfurt.
| Region | Endpoint |
|---|---|
| Frankfurt | 88.216.36.108:10001 |
| Amsterdam | 84.32.104.38:10001 |
http://88.216.36.108:10001A connection from an address that is not on your allowlist completes the handshake, then every call returns PERMISSION_DENIED (Unauthorized IP).
Proto Definition
You will need the standard Solana Geyser proto file. Save this as geyser.proto. The endpoint serves eight RPCs: Subscribe, SubscribeReplayInfo, Ping, GetLatestBlockhash, GetBlockHeight, GetSlot, IsBlockhashValid and GetVersion. Newer upstream protos also define SubscribeDeshred and SubscribeGossip, which this endpoint does not serve. Server reflection is off, so tools like grpcurl need -proto geyser.proto.
Download Standard Geyser Proto
Installation
Node.js
npm install @grpc/grpc-js @grpc/proto-loaderRust
Add to Cargo.toml (checked against these versions, October 2026):
[dependencies]
yellowstone-grpc-client = "16.0.1"
yellowstone-grpc-proto = "14.0.1"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
anyhow = "1"Usage
Node.js Example
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Load the protobuf definition
const packageDefinition = protoLoader.loadSync('geyser.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const proto = grpc.loadPackageDefinition(packageDefinition).geyser;
// Create client (Note: insecure credentials since traffic is direct to IP)
const client = new proto.Geyser('88.216.36.108:10001', grpc.credentials.createInsecure());
// Metadata: empty from a whitelisted IP. With an API key (new subscriptions),
// send it as x-token: metadata.set('x-token', process.env.ALLENHARK_KEY);
const metadata = new grpc.Metadata();
// Create stream
const stream = client.subscribe(metadata);
stream.on('data', (data) => {
console.log('Update received:', data);
});
stream.on('error', (error) => {
console.error('Stream error:', error);
});
// Send subscription request
const request = {
accounts: {
'client': {
account: [],
owner: [],
filters: []
}
},
slots: {},
transactions: {},
blocks: {},
blocksMeta: {},
commitment: 'processed',
entry: {}
};
stream.write(request);Rust Example
use std::collections::HashMap;
use futures::StreamExt;
use yellowstone_grpc_client::GeyserGrpcClient;
use yellowstone_grpc_proto::geyser::{CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Use http://: the endpoint is plaintext HTTP/2. From an allowlisted IP no
// token is needed; with an API key, set ALLENHARK_KEY and it is sent as x-token.
let mut client = GeyserGrpcClient::build_from_shared("http://88.216.36.108:10001")?
.x_token(std::env::var("ALLENHARK_KEY").ok())?
.connect()
.await?;
// Accounts owned by the Pump.fun program
let mut accounts = HashMap::new();
accounts.insert("client".to_string(), SubscribeRequestFilterAccounts {
owner: vec!["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P".to_string()],
..Default::default()
});
let request = SubscribeRequest {
accounts,
commitment: Some(CommitmentLevel::Processed as i32),
..Default::default()
};
let (_, mut stream) = client.subscribe_with_request(Some(request)).await?;
while let Some(message) = stream.next().await {
println!("Update received: {:?}", message?);
}
Ok(())
}Performance Tips
- Use Filters: Reduce bandwidth with specific filters
- Batch Processing: Process updates in batches
- One stream, many filters: each connection you buy is one open
Subscribestream, on one IP or split across several. An IP that opens more than its share getsRESOURCE_EXHAUSTED, so put every filter in one request - Backpressure Handling: Implement flow control
Error Handling
stream.on('error', (error) => {
console.error('Stream error:', error);
// Implement reconnection logic
});
stream.on('end', () => {
console.log('Stream ended, reconnecting...');
// Reconnect
});