How to Update a Yellowstone gRPC Subscription Without Disconnecting the Stream

Learn how to dynamically update your Yellowstone gRPC SubscribeRequest
on Solana without disconnecting the stream. Includes TypeScript code
example with dynamic wallet tracking and error handling.

Learn how to dynamically update your Yellowstone gRPC Subscribe Request on Solana without disconnecting the stream.

Modify a yellowstone subscribe request Cover

When building production Solana applications on Yellowstone gRPC, your subscription needs can change mid-stream — a new token launches, a wallet surges in activity, or your users switch what they’re tracking.

The naive approach is to disconnect and reconnect with a new SubscribeRequest. But that creates a data gap, adds latency, and adds unnecessary complexity to your reconnect logic.

In this guide, you’ll learn how to dynamically update a Yellowstone gRPC SubscribeRequest mid-stream using stream.write() — no disconnection, no missed events, no data gaps.

What’s covered:

  • How stream.write() enables live subscription updates.
  • Switching between transaction and account subscriptions dynamically.
  • Combining dynamic updates with reconnect logic for full resilience.

Before Getting Started

To get started, we will need a few things.

Authentication: gRPC endpoint and gRPC token

Shyft’s Yellowstone 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.

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.

The complete code for this article is available in our docs and on Replit — feel free to explore 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.

Code Example: Subscribing and then Updating Yellowstone gRPC Stream

Here is an example, which uses the client.subscribe() request to send a subscription, and the uses another client.write() to write the same subscribe request.

import Client, {
CommitmentLevel,
SubscribeRequestAccountsDataSlice,
SubscribeRequestFilterAccounts,
SubscribeRequestFilterBlocks,
SubscribeRequestFilterBlocksMeta,
SubscribeRequestFilterEntry,
SubscribeRequestFilterSlots,
SubscribeRequestFilterTransactions,
} from "@triton-one/yellowstone-grpc";
import { SubscribeRequestPing } from "@triton-one/yellowstone-grpc/dist/grpc/geyser";

interface SubscribeRequest {
accounts: { [key: string]: SubscribeRequestFilterAccounts };
slots: { [key: string]: SubscribeRequestFilterSlots };
transactions: { [key: string]: SubscribeRequestFilterTransactions };
transactionsStatus: { [key: string]: SubscribeRequestFilterTransactions };
blocks: { [key: string]: SubscribeRequestFilterBlocks };
blocksMeta: { [key: string]: SubscribeRequestFilterBlocksMeta };
entry: { [key: string]: SubscribeRequestFilterEntry };
commitment?: CommitmentLevel;
accountsDataSlice: SubscribeRequestAccountsDataSlice[];
ping?: SubscribeRequestPing;
}

const subscribedWalletsA: string[] = [
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"5n2WeFEQbfV65niEP63sZc3VA7EgC4gxcTzsGGuXpump",
"4oJh9x5Cr14bfaBtUsXN1YUZbxRhuae9nrkSyWGSpump",
"GBpE12CEBFY9C74gRBuZMTPgy2BGEJNCn4cHbEPKpump",
"oraim8c9d1nkfuQk9EzGYEUGxqL3MHQYndRw1huVo5h",
];

const subscribedWalletsB: string[] = [
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
];

const subscribeRequest1: SubscribeRequest = {
accounts: {},
slots: {},
transactions: {
modifying_A: {
vote: false,
failed: false,
signature: undefined,
accountInclude: subscribedWalletsA,
accountExclude: [],
accountRequired: [],
},
},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.PROCESSED,
};

// Subscribes to account changes for program-owned accounts of subscribedWalletsB
const subscribeRequest2: SubscribeRequest = {
accounts: {
modifying_B: {
account: [],
filters: [],
owner: subscribedWalletsB,
},
},
slots: {},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {
block: [],
},
entry: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.PROCESSED,
};

/**
* Dynamically updates the current stream subscription with new request parameters.
*/
async function updateSubscription(stream: any, args: SubscribeRequest) {
try {
stream.write(args);
} catch (error) {
console.error("Failed to send updated subscription request:", error);
}
}

/**
* Handles a single streaming session.
* Automatically switches to a second subscription request after a timeout.
*/
async function handleStream(client: Client, args: SubscribeRequest) {
const stream = await client.subscribe();

// Waits for the stream to close or error out
const streamClosed = new Promise<void>((resolve, reject) => {
stream.on("error", (error) => {
console.error("Stream Error:", error);
reject(error);
stream.end();
});
stream.on("end", resolve);
stream.on("close", resolve);
});

// Automatically switch subscription after 10 seconds
setTimeout(async () => {
console.log("🔁 Switching to second subscription request...");
await updateSubscription(stream, subscribeRequest2);
}, 10000);

// Handle incoming data
stream.on("data", async (data) => {
try {
console.log("📦 Streamed Data:", data);
// You can add more processing logic here
} catch (error) {
console.error("Error processing stream data:", error);
}
});

// Send initial subscription request
await new Promise<void>((resolve, reject) => {
stream.write(args, (err: any) => (err ? reject(err) : resolve()));
}).catch((reason) => {
console.error("Initial stream write failed:", reason);
throw reason;
});

await streamClosed;
}

/**
* Starts the stream and continuously attempts to reconnect on errors.
*/
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));
}
}
}

const client = new Client("YOUR-GRPC-URL", "YOUR-GRPC-TOKEN", undefined);

// Start streaming with the first subscription
subscribeCommand(client, subscribeRequest1);

In the above illustrated example we can see that we have two different subscribe requests. The stream begins with subscribeRequest1, which listens for transactions involving a predefined set of wallet addresses (subscribedWalletsA).

“Build with Confidence. Our Dedicated Yellowstone gRPC Nodes are purpose-built for peak uptime and blazing-fast throughput—shred-accelerated nodes with resilient DNS and load balancing at their core.”

Dynamic update

After 10 seconds, the code calls updateSubscription() with subscribeRequest2, which switches the stream to monitor accounts owned by another wallet set (subscribedWalletsB) and starts listening for blocksMeta.

This is done without ending or restarting the stream, thanks to the stream.write() method which sends a new SubscribeRequest mid-connection.

Robust Error Handling

The handleStream() function listens for errorclose, or end events. If the stream breaks, the subscribeCommand() wrapper retries the connection after a 1-second delay—ensuring resilience in unstable network conditions or during backend hiccups.

If you’ve mastered dynamic gRPC filters but need even faster raw data, check out Rabbitstream for shred-level speed with the same filtering precision.

Conclusion: Why Dynamic Subscription Updates Matter

Modifying a SubscribeRequest mid-stream in Solana Yellowstone gRPC is a critical feature for building responsive, production-ready applications that rely on real-time blockchain data. Here’s why this approach is so powerful:

  • Live Updates Without Stream Interruption: By dynamically updating the subscription while the stream remains open, the application avoids the overhead of disconnecting and then reconnecting. This means no dropped connections, no resyncing delays, and a smoother developer experience.
  • Flexible to Real-Time Needs: Blockchain applications often need to adapt based on user behavior or external events — like tracking a newly listed token, following a surge in wallet activity, or reacting to governance proposals. Dynamic subscription updates give you the robustness to respond immediately, without reinitializing your data infrastructure.
  • Lower Latency & Zero Missed Data: When the stream stays active, there’s no gap in data delivery. Events continue to flow uninterrupted, which is crucial for time-sensitive use cases like trading bots, monitoring dashboards, or automated analytics pipelines.

In case you missed out, the complete code for this article is available in Shyft docs and on Replit — feel free to explore 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.

You can explore our other related articles: Streaming Real-Time Data on SolanaReal-Time Data Streaming with gRPC: Accounts, Transactions, BlocksHow to Stream Real-Time Pump.fun Updates on Solana, and Tracking New Pools on Raydium.

Resources