A Yellowstone subscription can stay healthy for hours, fail for seconds, and leave a trading bot with a local state that looks current but is not. The socket came back. One pool update did not. That is enough to make the next decision against the wrong reserves.

This guide defines the recovery contract around the stream. It complements the Yellowstone filter cookbook, which covers what to request. Here the question is what happens after the request stops receiving data.

Separate connection liveness from data continuity

Track these as different states:

StateWhat it provesWhat it does not prove
Transport connectedThe client has an open gRPC channelThe server is sending current data
Updates arrivingSome messages are reaching the processEvery required slot was observed
Checkpoint advancingThe consumer completed known slot boundariesEvery side effect was applied once
ReconciledLocal state matches an authoritative readFuture updates cannot be missed

An end, close, or recoverable transport error should move the consumer out of healthy. So should a stale-data timer, even when the channel still reports connected.

Use an explicit state machine:

stream-health.txt
STARTING -> LIVE -> STALE -> RECONNECTING -> REPLAYING -> RECONCILING -> LIVE
                         \-> GAP_TOO_OLD -> PAUSED -> REBUILDING ---^

Do not let the execution worker read a model in STALE, REPLAYING, GAP_TOO_OLD, or REBUILDING. Recovery is part of the risk gate, not a background log message.

Checkpoint complete slots, not the last object received

A transport can fail halfway through a slot. Saving the slot number from the last account update can therefore overstate what the process has fully consumed.

The built-in Yellowstone reconnect design checkpoints completed slots from block metadata. On a recoverable error it requests a few slots before that checkpoint, then removes duplicates from the overlap. Triton documents this behavior for the Rust client added in v13.1. See the auto-reconnect release note and the Yellowstone repository.

The current Rust client setup is small:

yellowstone-reconnect.rs
use yellowstone_grpc_client::{
    GeyserGrpcClient,
    ReconnectConfig,
};
 
let mut client = GeyserGrpcClient::build_from_shared(endpoint)?
    .x_token(Some(token))?
    .set_reconnect_config(ReconnectConfig::default())
    .connect()
    .await?;
 
let mut stream = client.subscribe_once(request).await?;

Treat this as a client capability, not a universal endpoint guarantee. The endpoint must run a compatible server and retain the requested history. Ask for the first available replay slot before you depend on backfill.

Use overlap, then deduplicate before side effects

Exact resume boundaries are brittle. A short overlap is safer because it covers a failure that arrived between the last update and the durable checkpoint write.

Overlap creates duplicate delivery by design. Build the consumer so the same update converges:

UpdateCandidate identity
Transactionsignature + update type + commitment
Accountpubkey + slot + write version
Slot statusslot + status
Block metadataslot + blockhash

Deduplication in memory protects one process lifetime. Trading side effects need durable idempotency. Write the observed transaction signature as a unique key before a worker submits a copy, rebalance, alert, or webhook.

idempotency.ts
type SeenTransaction = {
  signature: string;
  observedSlot: bigint;
  actionSignature: string | null;
};
 
async function claimCandidate(signature: string, slot: bigint): Promise<boolean> {
  const inserted = await store.insertIfAbsent<SeenTransaction>({
    signature,
    observedSlot: slot,
    actionSignature: null,
  });
 
  return inserted;
}

insertIfAbsent must be backed by a unique constraint or an equivalent atomic operation. A check-then-insert sequence can race when two workers receive the same replayed event.

Know the replay boundary before the incident

The Yellowstone protobuf includes from_slot and a SubscribeReplayInfo method that reports the first available replay slot. Those fields are part of the published protocol.

The provider decides how much history is retained. Triton's May 2026 note states that Dragon's Mouth currently keeps about 1,000 slots. That number describes that service at that date, not every Yellowstone endpoint and not a permanent contract.

At reconnect time, compare three values:

replay-check.txt
requested_from = last_complete_slot - overlap_slots
first_available = endpoint replay boundary
 
if requested_from < first_available:
  recovery = GAP_TOO_OLD
else:
  recovery = REPLAYING

When the gap is too old, do not jump to the live head and continue silently. Record the missing range, stop dependent execution, reload the accounts or application state through JSON-RPC, and enable the strategy only after reconciliation passes.

Keep the channel alive without hiding a stale stream

The official Yellowstone repository notes that some load balancers close bidirectional streams when the client sends nothing for a period. The protocol includes a ping request for this case. The server can send a ping and the client can answer without replacing its filters.

A keepalive policy needs two timers:

  1. Transport timer: respond to the protocol ping and reconnect on channel failure.
  2. Data timer: alert when the last relevant slot or block update is older than the workload's allowed staleness.

The second timer catches a connection that is technically open but operationally useless. Choose its threshold from observed slot cadence and the workload, then test it during a controlled network pause.

Reconcile after every uncertain interval

Replay restores events. It does not establish that your reducer, decoder, queue, and side effects all reached the same state as the chain.

After reconnect:

  • fetch the authoritative accounts that drive the next decision;
  • compare their slot and decoded values with the local model;
  • verify pending transaction signatures independently;
  • clear or rebuild derived caches if the comparison fails;
  • return to LIVE only after the check passes.

For a copy bot, reconcile the watched and mirror signatures. For a DLMM worker, reload the pair, active bin, relevant bin arrays, and inventory accounts. The recovery procedure should name the state it verifies rather than run one generic getSlot call.

Metrics that make a stream operable

Log recovery as a bounded event with these fields:

MetricWhy it matters
Last update ageDetects an open but stale stream
Last complete slotDefines the recovery checkpoint
Requested replay slotShows the intended overlap
First available replay slotProves whether the gap can be covered
Reconnect count and delayExposes transport instability
Replayed and deduplicated eventsConfirms overlap behavior
Reconciliation resultGates the return to live execution
Queue age and depthDetects a consumer that cannot catch up

Test the path by cutting the connection for a short interval, then for longer than the documented replay window in a non-production environment. Both cases need a deterministic result.

Need a filtered stream with a clear recovery contract?

Tell us the programs, account set, region, and acceptable gap window. We can map the Yellowstone subscription and the RPC reconciliation path before you move production traffic.

Review RPCEdge plans →

The production rule

Recovery is complete when the consumer can account for the interval, remove duplicates, reconcile state, and re-enable actions deliberately. A green connection icon covers only the first step.

Frequently asked questions

Does Yellowstone gRPC reconnect automatically?
The Rust yellowstone-grpc-client added built-in auto-reconnect in v13.1. It can resume with from_slot and deduplicate the overlap when the endpoint supports replay. Other clients still need an explicit reconnect state machine, so verify the behavior of the package and provider version you run.
What does from_slot do in a Yellowstone subscription?
from_slot asks the server to replay updates beginning at a prior slot. Replay depends on the server retaining that slot. Request a small overlap before your last complete checkpoint, then deduplicate every replayed update before applying side effects.
How should a Solana bot deduplicate replayed events?
Use a stable event identity. Transactions can use signature plus commitment or event type. Account updates can use pubkey, slot, and write version. Store side-effect idempotency keys durably so a process restart cannot submit the same action twice.
Can replay guarantee that no Solana updates are missed?
No. Coverage ends when the disconnect is longer than the provider's retained replay window, the checkpoint is wrong, or the requested data was never retained. Detect that condition, pause dependent actions, and rebuild authoritative state through RPC or another backfill source.
Why does a Yellowstone stream need ping messages?
Some load balancers close quiet bidirectional gRPC connections. Yellowstone defines a ping field so a client can respond to server pings without rewriting the full subscription. A healthy connection still needs a stale-data alarm because an open socket does not prove updates are current.