Documentation index

gRPC Streaming

Account and transaction streaming over Yellowstone Geyser gRPC, served from a Solana validator's Geyser plugin.

Features

  • Streams, not polling: updates are pushed to you as the validator processes them
  • 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 to our gRPC endpoints utilizing IP Whitelisting instead of API keys. Once your server IP is whitelisted, you can access the endpoint directly.

The endpoint is plaintext HTTP/2 (h2c) on port 10001, in two regions. The examples below use Frankfurt.

RegionEndpoint
Frankfurt88.216.36.108:10001
Amsterdam84.32.104.38:10001
Text
http://88.216.36.108:10001

A 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

Terminal
npm install @grpc/grpc-js @grpc/proto-loader

Rust

Add to Cargo.toml:

TOML
[dependencies]
tonic = "0.10"
prost = "0.12"
tokio = { version = "1.0", features = ["full"] }
yellowstone-grpc-proto = "1.4.0"

Usage

Node.js Example

JavaScript
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 (Not needed if IP is whitelisted)
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

Rust
use yellowstone_grpc_proto::geyser::geyser_client::GeyserClient;
use yellowstone_grpc_proto::geyser::{SubscribeRequest, SubscribeRequestFilterAccounts};
use tonic::transport::{Channel, ClientTlsConfig};
use tonic::Request;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Note: Use http for whitelisted connections
    let endpoint = Channel::from_static("http://88.216.36.108:10001");

    let channel = endpoint.connect().await?;
    let mut client = GeyserClient::new(channel);

    let request = SubscribeRequest {
        accounts: [("client".to_string(), SubscribeRequestFilterAccounts {
            account: vec![],
            owner: vec![],
            filters: vec![],
        })].into_iter().collect(),
        slots: Default::default(),
        transactions: Default::default(),
        blocks: Default::default(),
        blocks_meta: Default::default(),
        commitment: Some(0), // Processed (Confirmed = 1, Finalized = 2)
        entry: Default::default(),
    };

    let mut stream = client.subscribe(std::iter::once(request)).await?.into_inner();

    while let Some(message) = stream.message().await? {
        println!("Update received: {:?}", message);
    }

    Ok(())
}

Performance Tips

  1. Use Filters: Reduce bandwidth with specific filters
  2. Batch Processing: Process updates in batches
  3. One stream, many filters: each connection you buy is one open Subscribe stream, on one IP or split across several. An IP that opens more than its share gets RESOURCE_EXHAUSTED, so put every filter in one request
  4. Backpressure Handling: Implement flow control

Error Handling

TypeScript
stream.on('error', (error) => {
  console.error('Stream error:', error);
  // Implement reconnection logic
});

stream.on('end', () => {
  console.log('Stream ended, reconnecting...');
  // Reconnect
});