Jetstream V2 - The Ins and Outs

Jetstream V2 - The Ins and Outs
Jetstream has always had one job: get you Solana transactions before everyone else sees them. It sends back decoded shreds instead of waiting for Geyser, which means the data reaches your client while other feeds are still assembling blocks. That part hasn't changed, and it was never going to. What has changed is everything around it.
Jetstream V2 is a new protocol running alongside V1 on the same endpoints. Slightly better latency, same authentication, same regions. The difference is in what each message carries and what you can do with a stream once it's open. V1 hands you a decoded transaction and wishes you luck with the rest. V2 hands you the decoded transaction plus the answers to the questions you were about to ask, and it lets you reshape your subscription without ever dropping the connection.
Let's walk through what that actually means.
More data per message, fewer RPC calls
Here's the dirty secret of consuming any fast transaction stream: the stream is rarely your bottleneck. Your follow-up work is. You get a transaction in a few milliseconds, and then you spend an order of magnitude longer figuring out who paid the fee, what compute price it set, and which accounts it actually touches.
The worst offender is versioned transactions. A v0 transaction doesn't carry its full account list. It carries a reference to an on-chain address lookup table plus a set of indexes into it. On V1, learning which accounts the transaction really touches means a full RPC round trip to fetch that table, then resolving the indexes yourself. That's one extra request and response per transaction, sitting directly on your critical path, and it's far slower than the stream that delivered the transaction in the first place. You paid for shred-level latency and then handed most of it back to an RPC call.
V2 fixes this with enrichment. Turn it on and every transaction arrives with the resolved lookup table addresses already inline, in loaded_writable_addresses and loaded_readonly_addresses. No round trip, no index math, no lookup sitting between a transaction arriving and you acting on it. If a table can't be fully resolved, the transaction is flagged with alt_resolution_incomplete. In practice you'll rarely see the flag at all. Last we measured, the miss rate was in the 0.00x percent range, and it keeps shrinking as the resolver improves.
The rest of the enrichment fields cover the busywork you'd otherwise repeat on every single transaction:
fee_payer, so you stop deriving it from the account keys and headercompute_unit_priceandcompute_limit, so you stop parsing compute budget instructionswritable_accountsandprogram_ids, precomputed instead of walkedtx_size, measured for you
One note worth internalizing: compute_unit_price is the micro-lamports-per-compute-unit value from SetComputeUnitPrice, not the total priority fee. If you want the fee in lamports, it's compute_unit_price × compute_limit / 1e6.
Enrichment is opt-in and off by default, set per filter with include_enrichment: true. Turning it on adds no latency. The only cost is a modestly larger message, which is a trade most people will sign without reading the fine print.
Filters you can change while the stream is running
On V1, filters are a one-shot deal. You declare them in the message that opens the stream, and if you later want to watch something different, you tear the connection down and open a new one. For a trading system, that reconnect window is a blind spot at exactly the moment you decided something new was worth watching.
V2 makes the request side of the stream bidirectional. You add and remove filters on the live connection, and the stream never blinks. Each filter carries a filter_id that you choose, the server explicitly acknowledges every add and remove (with a reason when it rejects one), and every delivered transaction lists the filter IDs it matched. So when you're running a pump.fun filter and a Raydium filter on the same connection, you know exactly which one produced each transaction without re-deriving it from the account keys.
Slot events, sequence numbers, and heartbeats
V2 adds a second stream that V1 never had: SubscribeSlots. It emits an event when a slot goes ALIVE (first shred received), COMPLETE (last shred received), or DEAD (skipped or superseded), along with the slot's leader and parent. If you've ever inferred chain progress from the rhythm of your transaction flow, this is the cheap, honest replacement for that guesswork.
Then there are the two features nobody gets excited about until three in the morning, when they suddenly become the most important features in the protocol.
Every V2 message, transactions and slot events alike, carries a monotonic sequence number. If you see a gap, you dropped a message, and you know it immediately instead of discovering it in a reconciliation job next week. And when a transaction stream goes quiet, the server sends a Heartbeat every 60 seconds stamped with its own clock. That keeps the connection alive through load balancer idle timeouts and, more importantly, tells a quiet stream apart from a dead one. V1 gave you neither, and every serious V1 consumer ended up half-building both.
V1 vs V2, side by side
Here's the whole comparison in one picture:

Our SDKs already speak V2
Both OrbitFlare SDKs - Rust and TypeScript - ship V2 as a parallel module next to V1, with the same builder ergonomics across both. Nothing about your V1 code breaks; the V1 client stays where it always was, and V2 lives one module deeper.
In Rust that looks like this:
use orbitflare_sdk::jetstream::v2::{JetstreamClientBuilder, TransactionFilter};
let client = JetstreamClientBuilder::new()
.url("http://fra.jetstream.orbitflare.com")
.build()?;
let filter = TransactionFilter::new()
.account_include(["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"])
.include_enrichment(true)
.with_id("pumpfun");
let mut stream = client.subscribe_transactions(vec![filter]);
And because the stream stays open for business, changing your mind mid-flight is a method call, not a reconnect:
let handle = stream.handle();
handle.add_filters(vec![
TransactionFilter::new()
.account_include([RAYDIUM])
.include_enrichment(true)
.with_id("raydium"),
])?;
Everything the SDK already did for your connection still works the same way on V2: retries, fallback URLs, keepalive, ping intervals, and picking up the endpoint from ORBITFLARE_JETSTREAM_URL. The SDK keeps the connection alive; you deal with the transactions.
The examples got the same upgrade
The jetstream-client-example repo now ships paired examples: a v1 binary on the classic fixed-filter client and a v2 binary that exercises the new protocol properly. The V2 examples aren't just the V1 code with a renamed import, either.
Both the Rust and TypeScript SDK repos carry their own jetstream_v2_basic examples as well, if you want the minimal version to crib from.
So, should you move?
For new integrations, yes, use V2. It's where enrichment, live filters, slot events, and the liveness signals live, and it's where every new capability will land. If you have a V1 client in production, there's no fire drill: V1 stays fully supported with no set retirement date, and since both versions share the same endpoints and the same authentication, migrating is a change in your client code rather than your infrastructure. You can even run both side by side on the same connection details while you cut over.
But the case for moving is easy to state. V2 takes an RPC round trip off your critical path, hands you precomputed answers instead of homework, never makes you reconnect to change what you're watching, and tells you immediately when something is missing or stalled. The stream was already fast. Now the rest of your pipeline gets to be fast too.
Curious and want to try Jetstream V2? Reach out to us on Discord
Resources
- Jetstream v2 docs
- v1 vs v2 comparison
- v2 protocol reference
- Client examples on GitHub
orbitflare-sdkon crates.io |@orbitflare/sdkon npm
If you build on V2, or hit an edge that should be smoother, come tell us about it on Discord. We'd love to see what you ship.
Jetstream V2 - The Ins and Outs
Related articles
Dual-Stream Solana Indexer
Build a Solana indexer that pulls from Orbit's Jetstream and Yellowstone gRPC simultaneously, merging both into a single queryable Postgres database.
DevelopersBuilding a Real-Time Solana Wallet Tracker
Tired of chasing Solana transactions and always being behind? Fret no more. Let's build a wallet tracker using OrbitFlare's Jetstream that filters and streams transactions in real-time, decodes PumpFun trades and fires alerts based on activity that you add in the config.
DevelopersPrepare your apps for Agave v2.0 upgrade
The Agave 2.0 validator client brings significant updates including breaking changes, API endpoint removals and Rust crate renamings.