Documentation index

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.

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 (checked against these versions, October 2026):

TOML
[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

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: 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

Rust
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

  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
});