TL;DR

  • Dragon's Mouth streams Solana updates as the node processes them, with no memory of individual sessions.
  • Network instability and server restarts can drop the stream, requiring custom reconnect and backfill logic.
  • yellowstone-grpc-client now ships this as a built-in feature, handling reconnects, backfills, and deduplication for you.
  • To enable it: set set_reconnect_config on the builder. No other code changes required.

Challenge of stateless streams

A Dragon's Mouth stream is a persistent gRPC connection between your application and a Solana node, with no session state on either side. When a network blip, node restart, or connection reset drops it, there's no record of where you left off.

If your application accumulates state across slots (reconstructing a block, maintaining a real-time view of account changes), a reconnect gap means lost context that your application was actively building when the stream dropped.

For use cases where 100% completeness is non-negotiable, we built Fumarole: a persistent storage layer on top of Dragon's Mouth. Latency-sensitive workloads, however, need something different -- the fastest stream, with reliable reconnect and short-window gap recovery.

With no built-in solution available, teams have always had to build, debug, and maintain their own versions of it on top of Yellowstone gRPC. Until now.

Introducing auto-reconnect

The Yellowstone gRPC client ships built-in auto-reconnect in v13.1. One config call to enable it, and it handles:

  • Automatic reconnects with configurable exponential backoff.
  • Backfilling the data that arrived during the drop.
  • Deduplication of events before they reach your application.

Any application that requires continuous, accurate Solana state benefits from this.

How it works

  • You call subscribe_once, and the stream begins.
  • In the background, AutoReconnect maintains a slot checkpoint that updates each time a slot completes via BlockMeta. If the stream drops mid-slot, the checkpoint holds at the last fully completed slot.
  • When the stream encounters a recoverable error (network timeout, server unavailable, connection reset), AutoReconnect waits with exponential backoff and then resubscribes using from_slot, starting a few slots before the stored checkpoint to ensure overlap with the gap.
  • DedupStream then tracks which messages have already been delivered and filters out any that come through again during replay. Your application receives only the events it actually missed.

The reconnect logic is built as composable Stream layers:

AutoReconnect    -- reconnects on failure, resumes from checkpoint
    ↓
DedupStream      -- filters duplicates during replay window
    ↓
Streaming        -- raw gRPC transport

Each layer implements Rust's Stream trait and composes without spawned tasks or channels between components.

Reconnect window

The server's replay buffer determines how long you can be offline before auto-reconnect loses coverage. On Dragon's Mouth, that window is currently ~1,000 slots.

If you reconnect within that window, the client cleanly recovers the gap. Otherwise, the requested slot is no longer in the buffer, and the client reconnects from the current position.

Get started

The new feature is live on all Triton endpoints. Since yellowstone-grpc-client is open-source, once your provider ships v13.1, you're good to go.

If you want to build with the team shipping the future of Solana's read layer and don't have an endpoint yet, it takes ~2 minutes to sign up.

Once you have your endpoint and secret token, follow the steps below.

1. Update your dependency:

[dependencies]
yellowstone-grpc-client = "13.1.0" # or higher

2. Enable auto-reconnect on the client builder:

use yellowstone_grpc_client::GeyserGrpcClient;

let mut client = GeyserGrpcClient::build_from_shared("https://your-endpoint")?
    .x_token(Some("your-token"))?
    .set_reconnect_config(ReconnectConfig::default())
    .connect()
    .await?;

let mut stream = client.subscribe_once(request).await?;

while let Some(msg) = stream.next().await {
    match msg {
        Ok(update) => handle(update),
        Err(e) => {
            // Only unrecoverable errors reach here.
            // Transient failures are handled internally.
            eprintln!("fatal: {e}");
            break;
        }
    }
}

Backoff and slot retention are both configurable:

Parameter Default Description
backoff.initial_interval 10ms First retry delay
backoff.multiplier 2.0 Exponential growth factor
backoff.max_retries 3 Retries per reconnect attempt
slot_retention 250 Slots tracked for dedup (~100 seconds)
let config = ReconnectConfig {
    backoff: Backoff::new(
        Duration::from_millis(100),  // initial interval
        2.0,                          // multiplier
        10,                           // max retries
    ),
    slot_retention: 250,
    ..Default::default()
};

If you don't call set_reconnect_config, the client behaves exactly as before. Existing subscriptions work without modification.

Auto-reconnect covers the standard subscribe API. Deshred streams (subscribe_deshred) support is coming in a future release.