← Back to guides
April 15, 2026·12 min read

Yellowstone gRPC Tutorial: Connect, Subscribe & Stream Solana Data (2026)

Yellowstone gRPC Tutorial: Connect, Subscribe & Stream Solana Data (2026)
YellowstonegRPCTutorialNode.jsRustSolana

Yellowstone gRPC — also called Dragon's Mouth — is the standard protocol for real-time Solana data streaming. Built and maintained by Triton One, it hooks directly into the validator via the Geyser plugin framework and pushes account updates, transactions, and slot notifications to connected clients via gRPC (HTTP/2).

This tutorial walks you through connecting, subscribing, and streaming data using the standard open-source libraries. By the end, you'll have a working client in both Node.js and Rust.

What You'll Need

  • A Yellowstone gRPC endpoint (from Subglow, QuickNode, Chainstack, or your own node)
  • Node.js 18+ or Rust 1.70+
  • An API key from your provider

How Yellowstone gRPC Works

When a Solana validator processes a new slot, the Geyser plugin intercepts account updates and confirmed transactions directly from validator memory. Yellowstone serializes this data into Protocol Buffers (protobuf) and streams it to connected clients over a persistent gRPC connection.

This is fundamentally different from RPC polling (you ask for data) or WebSocket (notification-based). With gRPC, data is pushed to you the instant it's confirmed — zero polling overhead, zero missed slots.

Architecture: From Validator to Your Bot

  1. Validator processes a slot — transactions confirm, accounts update
  2. Geyser plugin hooks fire — on_transaction, on_account_update callbacks capture data from validator memory
  3. Yellowstone serializes to protobuf — binary encoding for maximum throughput
  4. gRPC streams to your client — HTTP/2 multiplexed connection with built-in backpressure

Node.js: Connect with @triton-one/yellowstone-grpc

The official TypeScript/Node.js client is maintained by Triton One. Install it:

npm install @triton-one/yellowstone-grpc

Connect and subscribe to slot updates:

const Client = require("@triton-one/yellowstone-grpc").default;

const client = new Client("https://grpc.subglow.io", undefined, {
  "grpc.max_receive_message_length": 64 * 1024 * 1024,
});

const stream = await client.subscribe();
const request = {
  slots: { slot_sub: {} },
  commitment: 1, // CONFIRMED
};

stream.write(request);

stream.on("data", (data) => {
  if (data.slot) {
    console.log("Slot:", data.slot.slot, "Status:", data.slot.status);
  }
});

Subscribe to Transactions by Program

The real power is in filtering. Subscribe to only the transactions you care about — for example, all Pump.fun transactions:

const request = {
  transactions: {
    pump_filter: {
      accountInclude: ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
      vote: false,
      failed: false,
    },
  },
  commitment: 1,
};

stream.write(request);

stream.on("data", (data) => {
  if (data.transaction) {
    const sig = Buffer.from(data.transaction.transaction.signature).toString("base64");
    console.log("Pump.fun tx:", sig, "Slot:", data.transaction.slot);
  }
});

You can add multiple named filters — for example, pump_filter and raydium_filter — on the same connection. All matching transactions stream through a single subscription.

Rust: Connect with yellowstone-grpc-client

Add to your Cargo.toml:

[dependencies]
yellowstone-grpc-client = "2"
yellowstone-grpc-proto = "2"
tokio = { version = "1", features = ["full"] }
futures = "0.3"

Connect and subscribe:

use yellowstone_grpc_client::GeyserGrpcClient;
use yellowstone_grpc_proto::prelude::*;
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = GeyserGrpcClient::build_from_shared("https://grpc.subglow.io")?
        .x_token(Some("YOUR_API_KEY".into()))
        .connect()
        .await?;

    let (mut tx, mut stream) = client.subscribe().await?;

    // Subscribe to confirmed slots
    tx.send(SubscribeRequest {
        slots: [("slot_sub".into(), SubscribeRequestFilterSlots { ..Default::default() })].into(),
        commitment: Some(CommitmentLevel::Confirmed as i32),
        ..Default::default()
    }).await?;

    while let Some(msg) = stream.next().await {
        match msg?.update_oneof {
            Some(UpdateOneof::Slot(slot)) => println!("Slot: {} Status: {:?}", slot.slot, slot.status),
            _ => {}
        }
    }
    Ok(())
}

Handle Reconnection

gRPC streams can disconnect. Always wrap your subscription in a reconnection loop:

async function connectWithRetry() {
  let delay = 1000;
  while (true) {
    try {
      const client = new Client("https://grpc.subglow.io", undefined, {
        "grpc.max_receive_message_length": 64 * 1024 * 1024,
      });
      const stream = await client.subscribe();

      stream.write(subscriptionRequest);

      for await (const data of stream) {
        delay = 1000; // Reset backoff on success
        handleEvent(data);
      }
    } catch (err) {
      console.error("Stream error, reconnecting in", delay, "ms");
      await new Promise(r => setTimeout(r, delay));
      delay = Math.min(delay * 2, 30000);
    }
  }
}

From Raw Yellowstone to Pre-Parsed JSON

Raw Yellowstone gives you protobuf with Borsh-encoded instruction data. You must: decode protobuf, identify the program, parse the instruction discriminator, decode Borsh data, and map accounts to human-readable names.

With Subglow, the same Yellowstone endpoint delivers pre-parsed JSON with human-readable fields. Use the same @triton-one/yellowstone-grpc client — just point it at grpc.subglow.io.

Next Steps

Ready to try it?

Get your API key and start receiving filtered data in under 5 minutes. Free tier available.

Get started