Team Shyft
· January 24, 2026
In this article you will learn how to implement a reconnect logic for your Solana gRPC streams with replay functionality to make sure you don’t miss any slots.

Yellowstone gRPC is a powerful, production-ready and battle-tested tool for streaming real-time Solana data. But in real-world conditions, network hiccups or server restarts can cause dropped connections. Without a proper reconnect strategy, your application risks missing critical updates from the blockchain. To prevent this, it’s important to build a system that not only reconnects automatically, but also resumes data streaming from a specific slot — ensuring consistency, reliability, and zero missed events.
To get started, we will need a few things.
**Authentication: gRPC endpoint and gRPC token
**Shyft’s gRPC nodes are available in various locations all across EU and the US region. To access, we would require a region-specific gRPC endpoint and an access token, which is available to purchase on your Shyft Dashboard.
Join Medium for free to get updates from this writer.
**A server-side backend (like NodeJS) to receive gRPC data
**As gRPC services are unsupported in web-browsers, you would need a backend application such as C#, Go, Java, Python etc. to receive gRPC data.
To ensure the stream automatically recovers from temporary disconnections, we implement a simple reconnection loop using gRPC streaming. If the connection drops due to an error, the application waits for a short delay and then restarts the gRPC stream using the same subscription request. This ensures continuous data flow without manual intervention, even in unstable network conditions.
async function subscribeCommand(client: Client, args: SubscribeRequest) { while (true) { try { await handleStream(client, args); } catch (error) { console.error("Stream error, retrying in 1 second...", error); await new Promise((resolve) => setTimeout(resolve, 1000)); } }}
The code illustrates a while loop in which the handleStream() function is called. The handleStream() function is responsible for subscribing and receiving the gRPC stream. As soon as the stream is interrupted, an error is thrown from the handle stream function, which is handled inside the loop. The loop then waits for a given timeout and iterates, resending the subscribe request.
You can checkout our docs, or simply run the Repl code here for the full gRPC code of the example above.
To avoid missing any Solana transactions during a disconnection, the stream tracks the last received slot from each transaction update. When the gRPC stream encounters an error, it attempts to reconnect and resumes from that exact slot using the fromSlot field in the SubscribeRequest. This logic ensures that no transaction updates are skipped across reconnections. A retry counter is used to prevent infinite attempts — after a limit is reached, the system falls back to streaming from the latest available slot instead.
require("dotenv").config();import Client, { CommitmentLevel } from "@triton-one/yellowstone-grpc";import { SubscribeRequest } from "@triton-one/yellowstone-grpc/dist/types/grpc/geyser";import * as bs58 from "bs58";const MAX_RETRY_WITH_LAST_SLOT = 30;const RETRY_DELAY_MS = 1000;const ADDRESS_TO_STREAM_FROM = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";type StreamResult = { lastSlot?: string; hasRcvdMSg: boolean;};async function handleStream( client: Client, args: SubscribeRequest, lastSlot?: string): Promise<StreamResult> { const stream = await client.subscribe(); let hasRcvdMSg = false; return new Promise((resolve, reject) => { stream.on("data", (data) => { const tx = data.transaction?.transaction?.transaction; if (tx?.signatures?.[0]) { const sig = bs58.encode(tx.signatures[0]); console.log("Got tx:", sig); lastSlot = data.transaction.slot; hasRcvdMSg = true; } }); stream.on("error", (err) => { stream.end(); reject({ error: err, lastSlot, hasRcvdMSg }); }); const finalize = () => resolve({ lastSlot, hasRcvdMSg }); stream.on("end", finalize); stream.on("close", finalize); stream.write(args, (err: any) => { if (err) reject({ error: err, lastSlot, hasRcvdMSg }); }); });}async function subscribeCommand(client: Client, args: SubscribeRequest) { let lastSlot: string | undefined; let retryCount = 0; while (true) { try { if (args.fromSlot) { console.log("Starting stream from slot", args.fromSlot); } const result = await handleStream(client, args, lastSlot); lastSlot = result.lastSlot; if (result.hasRcvdMSg) retryCount = 0; } catch (err: any) { console.error( `Stream error, retrying in ${RETRY_DELAY_MS / 1000} second...` ); await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); lastSlot = err.lastSlot; if (err.hasRcvdMSg) retryCount = 0; if (lastSlot && retryCount < MAX_RETRY_WITH_LAST_SLOT) { console.log( `#${retryCount} retrying with last slot ${lastSlot}, remaining retries ${ MAX_RETRY_WITH_LAST_SLOT - retryCount }` ); args.fromSlot = lastSlot; retryCount++; } else { console.log("Retrying from latest slot (no last slot available)"); delete args.fromSlot; retryCount = 0; lastSlot = undefined; } } }}const client = new Client(process.env.GRPC_URL!, process.env.X_TOKEN!, { "grpc.keepalive_permit_without_calls": 1, "grpc.keepalive_time_ms": 10000, "grpc.keepalive_timeout_ms": 1000, "grpc.default_compression_algorithm": 2,});const req: SubscribeRequest = { accounts: {}, slots: {}, transactions: { pumpFun: { vote: false, failed: false, accountInclude: [ADDRESS_TO_STREAM_FROM], accountExclude: [], accountRequired: [], }, }, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [], commitment: CommitmentLevel.CONFIRMED,};subscribeCommand(client, req);
Similar to the last approach, the infinite loop ensures the gRPC stream keeps reconnecting whenever it drops. We initialize two variables, one for storing the **lastSlot** i.e. the most recent slot received from the stream, and **retryCount** which limits the number of retries from a previous slot to avoid getting stuck on bad data or gaps.
if (args.fromSlot) { console.log("Starting stream from slot", args.fromSlot);}
Before starting the gRPC stream, the code checks if a fromSlot is set. If it is, the stream will resume from that specific slot instead of starting from the latest block. The handleStream function opens the gRPC stream, listens for incoming Solana transaction data, and keeps track of the most recent slot received. If any data is received, it marks the stream as successful (hasRcvdMSg = true) and resets the retry counter so the system can keep retrying from the last known slot if needed.
const result = await handleStream(client, args, lastSlot);lastSlot = result.lastSlot;if (result.hasRcvdMSg) retryCount = 0;
lastSlot was successfully recorded before the error, it will be reused in the next gRPC reconnection attempt.retryCount.Smart Fallback
if (lastSlot && retryCount < MAX_RETRY_WITH_LAST_SLOT) { args.fromSlot = lastSlot; retryCount++;} else { delete args.fromSlot; retryCount = 0; lastSlot = undefined;}
This is the core resilience logic:
lastSlot and haven’t exceeded the retry limit, we attempt to resume from it again via the gRPC endpoint.fromSlot and let the gRPC stream start from the tip of the blockchain.The complete code for this article is available in on GitHub — feel free to clone and test it out. We’ve also shared a collection of example use cases covering gRPC and DeFi on GitHub, which you can clone and experiment with.
Building a reconnect strategy with slot-based replay ensures your Solana application remains reliable and real-time — even through network hiccups. By tracking the last received slot and retrying intelligently, you can resume streaming from exactly where you left off, avoiding missed updates or duplicated data. This approach adds resilience and guarantees a smoother user experience for any production-grade blockchain app.
You can explore our other related articles: Streaming Real-Time Data on Solana, Real-Time Data Streaming with gRPC: Accounts, Transactions, Blocks, How to Stream Real-Time Pump.fun Updates on Solana, and Tracking New Pools on Raydium.

Learn how to modify your yellowstone gRPC Subscribe Requests on Solana without stopping your stream or losing data ...
January 24, 2026

A comprehensive guide on how to stream Transactions, Accounts, and Block updates swiftly using Shyft’s gRPC Services ...
January 22, 2026

A step-by-step guide to launch token, maximize token’s visibility and trading volume using Jito bundles on Raydium In th...
January 22, 2026
Get in touch with our discord community and keep up with the latest feature
releases. Get help from our developers who are always here to help you take off.