All articles
Infrastructure·April 17, 2026·5 min read

Handling Missed Slots and Reorgs in Solana Slot Subscriptions

Production bots that consume slotSubscribe or blockSubscribe feeds must handle missed slots, forks, and confirmed-to-finalized transitions without double-processing or gaps. This guide covers detection logic, gap-filling from RPC, and idempotent state updates.

Solana's slot subscription APIs look simple until you deploy them against a real validator under load. Gaps appear, forks resolve silently, and a block you processed at confirmed commitment sometimes disappears. Every trading bot we've shipped at TierZero has had to solve these problems in production — here's the actual mechanics.

What slotSubscribe Actually Delivers

slotSubscribe pushes a JSON-RPC notification every time the subscribed validator's active slot advances. The payload contains three fields: slot, parent, and root. That's it. No block data, no transaction set — just an ordinal and two ancestry hints.

The critical thing people miss: the validator emits these notifications opportunistically. If your WebSocket falls behind or the validator skips a slot (which happens constantly — Solana's design allows validators to skip their scheduled slot), you will not receive a notification for every slot number in order. A sequence like [285710, 285711, 285714] is completely normal. The gap from 285711 to 285714 means slots 285712 and 285713 were skipped — no block was produced, no transactions landed.

blockSubscribe is higher-level but carries the same gap problem and adds a new one: you receive block data at a specific commitment level (processed, confirmed, or finalized), and a block at confirmed can still be rolled back if the fork it sits on is abandoned.

Detecting Gaps in Real Time

The detection pattern is trivial and must be the first thing your handler does:

let lastSeenSlot: number | null = null;

onSlotNotification(slot, parent, root) {
  if (lastSeenSlot !== null && slot > lastSeenSlot + 1) {
    const missed = slot - lastSeenSlot - 1;
    // slots [lastSeenSlot+1 .. slot-1] need inspection
    handleGap(lastSeenSlot + 1, slot - 1);
  }
  lastSeenSlot = slot;
  processSlot(slot, parent, root);
}

Keep this counter in memory, not in a database. The comparison has to be synchronous and zero-latency — if you await a DB read here, you've already introduced the ordering problem you're trying to fix.

Gap-Filling from RPC

Not every gap means you missed something actionable. Skipped slots have no block, so getBlock returns null for them. But you still need to check, because a slot that looks skipped from your subscription might have been produced on a fork your validator wasn't tracking.

For each slot in the gap range, call getBlock(slot, { commitment: "confirmed", transactionDetails: "accounts" }). Use transactionDetails: "accounts" rather than "full" if you only need to detect whether certain accounts were touched — it cuts the payload by roughly 70% and matters when you're filling gaps of 50+ slots after a reconnect.

Rate-limit this fill loop. Mainnet-beta public RPC endpoints will 429 you if you hammer them with 100 getBlock calls in a second. In practice we bucket fill requests at 20/second and use a priority queue so that slots adjacent to the current head are filled first.

Handling Forks and Rollbacks

The root field in the slot notification is your fork-resolution signal. root is the highest slot that has been finalized — once a slot is at or below root, it cannot be rolled back. Slots above root but at confirmed commitment are still on a candidate fork.

The consequence for bot state: never make an irreversible decision based on a confirmed block. If you're tracking position fills, P&L, or open orders, write those updates with a commitment tag and a finalization flag. When the root advances past a slot you processed, you can safely mark those records final. If a confirmed slot falls off (you'll detect this because its slot number never appears at a higher commitment level before a different block at the same height is confirmed), you roll back the pending records.

In Solana's current validator behavior, forks that reach confirmed are rolled back very rarely — empirically under 0.1% of blocks in normal conditions. But "rarely" is not "never," and the rollback always happens at the worst possible time.

Idempotent State Updates

Gap-filling and reconnect recovery mean you will sometimes process the same slot twice. Design every state mutation to be idempotent:

  • Use slot number as a primary key in your event log. An INSERT OR IGNORE (SQLite) or ON CONFLICT DO NOTHING (Postgres) is all you need.
  • For order placement logic, gate on a (slot, transaction_signature) pair before submitting. If the signature is already in your sent-orders table, skip.
  • Cache the last 500 processed slots in a Set<number> in memory for fast deduplication before you hit the database at all.

The memory set handles the common case (reconnect replays the last few slots); the database constraint handles the rare case (process crash and restart from a checkpoint behind the current head).

Reconnect and Subscription Recovery

WebSocket connections to Solana validators drop. The median uptime of a public RPC WebSocket connection under production load is around 4–8 hours before a forced reconnect. On reconnect:

  1. Note the last slot you successfully processed (checkpointSlot).
  2. Subscribe fresh — you'll immediately start receiving new notifications.
  3. Call getSlot({ commitment: "confirmed" }) to get the current head.
  4. Fill the range [checkpointSlot+1, currentHead] using the gap-fill logic above, processing in ascending order before you process the live feed.

Step 4 is the one people skip. Without it, your bot has a blind spot covering everything between your checkpoint and the reconnect moment — which on a volatile market day is exactly when you can't afford it.


If you're building or scaling a trading bot on Solana and want infrastructure that handles these edge cases without rolling it yourself, reach out to TierZero — this is the kind of problem we solve before deployment, not after.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#infrastructure#solana#trading-bots#websocket#rpc#slot-subscription#fork-handling#reorgs