Intermediate · LESSON 02 OF 12
2. Stream Solana account and transaction changes reliably
By the end, you can choose and operate websocket subscriptions, preserve commitment and cluster context, and recover missed transaction history without silently losing events.
Look up Solana terms in the glossary
Optional reading history is saved only in this browser. It is not a scored checkpoint or exam result.
In this lesson8 field notes · practice · sources · checkpoint
Read a text version of this diagram
The client selects a cluster, filter, and commitment before subscribing. It stores the request and returned subscription identifiers for the active connection. Each notification is checked for the expected generation and ID, then recorded by cluster and signature with slot, commitment, error, and parser provenance. A socket close changes the feed to stale but does not move its durable cursor. The client restores the subscription and separately pages HTTP signature history until it finds that cursor. It de-duplicates overlap and replays recovered events oldest-to-newest. If the cursor is absent from retained history, it preserves a coverage-unknown state and seeks an archival source or operator review instead of claiming completeness.
- SUBSCRIBE · method, filter, cluster, commitment
- ACK · request ID and ephemeral subscription ID
- NOTIFY · method, ID, slot, signature, result
- DISCONNECT · mark feed stale; preserve cursor
- HTTP BACKFILL · page signatures to saved cursor
- REPLAY · de-duplicate, persist, then mark current
DOWNLOADABLE DEVELOPER LAB · OFFLINE FIXTURES
Recover a Solana stream after disconnect.
This deterministic Node.js workbook validates fictional WebSocket notifications, scopes idempotency by cluster and signature, and plans chronological HTTP history backfill to a durable cursor. It has no RPC endpoint, wallet, signing, transaction submission, or real learner data.
Download RPC Stream Recovery Workbook 01Requires Node.js 20+. Unzip the package and run npm test. If the saved cursor is missing from available history, the workbook preserves an incomplete coverage state; it never claims the index is current.
Choose a stream for the fact you need
Solana PubSub sends JSON-RPC notifications over a persistent WebSocket connection. `accountSubscribe` reports changes to one account; `programSubscribe` reports matching owned accounts; `logsSubscribe` reports logs for a filter; and `signatureSubscribe` watches one transaction signature. These methods answer different questions. An account notification is an account-state observation, a log notification is a transaction execution record, and a signature notification reports one signature reaching the selected commitment. Do not substitute one kind for another. `logsSubscribe` currently allows one address in its `mentions` filter, so a list of program IDs in one request is invalid. A subscription acknowledgement returns an ephemeral numeric subscription ID; later notifications echo it so the client can route each message to the active listener. Keep the selected cluster and requested commitment attached to every observation.
Treat the subscription acknowledgement as a boundary
A subscribe request and a later notification are separate JSON-RPC messages. Match the acknowledgement to the client request ID, then store its subscription ID with the connection generation, cluster, method, filter, encoding, and commitment. Ignore notifications for IDs or generations that are no longer active; a delayed message from an old connection must not mutate the new view. If a subscribe request fails, show an unavailable state rather than an empty result. Unsubscribe when the view no longer needs the stream and close the socket when its owners are gone. `signatureSubscribe` is a one-signature watch and automatically ends after the requested status notification; account, program, log, slot, and block subscriptions have their own unsubscribe methods.
Commitment changes what the notification establishes
Many subscriptions accept `processed`, `confirmed`, or `finalized`; if omitted, the documented default is `finalized`. Choose the level for the product claim rather than relying on an implicit default. A processed observation can be provisional and its fork may not become canonical. A confirmed observation is stronger but does not equal finalized. A signature notification with `err: null` means success at that subscription’s selected commitment; a non-null `err` means execution failed at that level. A string such as `receivedSignature`, when the optional signature setting requests it, reports receipt and is not an execution result. Put the commitment in storage and in the interface. Do not turn “notification received” into “transfer settled” or a course credential awarded.
Notifications are not a durable event log
A WebSocket sends what the server observes while this subscription is active. The subscription ID is not a replay cursor, and a disconnected client cannot assume the provider queued every missed notification. Notifications may be duplicated, delayed, or unavailable during a provider or client outage. Slot numbers also have gaps during normal cluster operation, so the difference between two slot values does not by itself prove that a block or event was lost. Persist a meaningful cursor such as the last fully reconciled transaction signature, together with cluster, filter/program revision, commitment, parser version, and update time. Make writes idempotent and key transaction events by cluster plus signature; if the record’s interpretation changes after a parser upgrade, keep enough provenance to rebuild it.
Backfill the gap before declaring the feed current
After reconnect, restore the intended subscription and separately fetch history from HTTP RPC. For an address-scoped transaction feed, page `getSignaturesForAddress` newest-to-oldest using its `before` signature cursor until the last durable signature is found; stop before replaying that cursor, de-duplicate overlap, then process the recovered records oldest-to-newest. Fetch and decode transaction details with an explicit commitment and supported transaction-version limit. If the saved cursor is not found, the gap is not proven complete: the cursor may be pruned, refer to another cluster or filter, or exceed the provider’s history. Keep the feed marked incomplete, continue bounded pagination or use an appropriate archival/streaming source, and escalate when coverage cannot be established. Never advance the durable checkpoint just because a reconnect succeeded.
Build a snapshot and subscription without hiding races
For a live account view, subscribe and wait for its acknowledgement, then request a fresh account snapshot with the intended cluster and commitment. Buffer notifications while the read is in flight. Compare response and notification context slots; apply only state consistent with the chosen ordering policy, and re-read when same-slot updates or provider differences cannot be ordered safely. `minContextSlot` can require a response bank to be at or beyond a slot, but it is a lower bound, not a global ordering proof. A program subscription can return many account changes, so filter and decode by account owner, discriminator, data layout, and application revision. A log line is not a typed event schema; decode transaction instructions and account effects with the versioned program interface before relying on business meaning.
Bound queues and choose infrastructure for the workload
A fast producer can outrun a database writer. Use a bounded queue, expose queue depth and oldest-message age, and pause, shed only explicitly disposable UI updates, or reconnect and backfill when the pipeline falls behind. Persist raw event identity and parser/version provenance before advancing a checkpoint. Monitor socket age, reconnect count, last notification time, last finalized slot, pagination progress, provider errors, duplicate rate, and unresolved cursor gaps. Solana’s shared public RPC endpoints are rate-limited and are not intended for production applications. For larger server-side indexes, compare dedicated WebSocket plans, archival history, webhooks, or validator Geyser/gRPC streams; each has distinct retention, ordering, decoding, and operational guarantees.
Test disconnects and report only observed coverage
Use deterministic fixtures for subscribe acknowledgement, duplicate notification, failed execution, wrong subscription ID, socket close, delayed old-generation event, history page overlap, missing cursor, unsupported transaction version, and provider timeout. Assert that no duplicate row is created, the cursor advances only after all earlier records are durably processed, and a missing cursor keeps the feed incomplete. Keep a human-readable status such as live, reconnecting, catching up, current-through-signature, or coverage-unknown. A green WebSocket connection indicator proves transport health at that moment; it does not prove complete history, canonical finality, correct decoding, or agreement between providers. Record the exact cluster, provider, filter, commitment, parser version, and last reconciled signature with any completeness claim.
A detail worth keeping.
Read the official WebSocket request and notification format. Draw one complete request, acknowledgement, notification, and unsubscribe exchange, including request ID, subscription ID, cluster, filter, and commitment.
Work through an example
Rehearse an indexer restart from a saved signature cursor using fictional newest-first pages. Test duplicate overlap, an unsupported transaction version, a provider timeout, and a cursor missing from retained history.
Design an account-change feed and a transaction-activity indexer. For each, select the subscription method, commitment, durable cursor, duplicate key, reconnect/backfill procedure, and visible incomplete state. Explain what evidence is still missing when a provider cannot return the saved cursor.
GUIDED PRACTICE · INTERMEDIATE
Do the work, one step at a time.
Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Most exercises work on paper or with a local test; only a lesson that clearly says “read-only Devnet” makes a public network request.
- Run `npm test` in the downloaded RPC Stream Recovery Workbook; all fixtures are local and require no endpoint or wallet.
- Read a fictional `logsNotification`, confirm its active subscription ID, preserve its slot and commitment, and classify a null versus non-null execution error.
- Replay one signature twice on Devnet, then once on Mainnet-beta; verify cluster-scoped idempotency.
- Recover two newest-first pages until the durable cursor is found; replay only newer missing signatures oldest-first.
- Remove the cursor from retained history. Keep the result incomplete, preserve the saved checkpoint, and describe the next evidence source or operator action.
- Add a reconnect generation to the exercise and prove that a delayed notification from the closed socket cannot update the active view.
Before you move on, check your reasoning
- Define the exact meaning of live, catching up, current-through-signature, and coverage-unknown.
- Measure queue depth, last finalized event age, reconnect frequency, duplicate rate, and unresolved cursor gaps with deterministic fixtures.
- Compare a shared WebSocket endpoint, a dedicated provider, archival history, and a Geyser/gRPC pipeline against one stated workload and recovery requirement.
OPEN SELF-CHECK · UNGRADED
Pause and test your reasoning
Practice 1 of 3
A browser reconnects to its Devnet logs subscription after a socket outage. The old cursor is still stored. What should it do before showing the feed as current?
Reveal the answer and explanation
One best answer: Backfill to the saved signature; dedupe and mark gaps.
A new subscription acknowledgement restores future delivery only. Notifications missed while disconnected are not replayed automatically, so page address history to the saved signature, deduplicate overlap, replay in order, and keep coverage incomplete until recovery is proven.
Sources: Solana RPC WebSocket Methods · getSignaturesForAddress
Practice 2 of 3
A getSignaturesForAddress page contains no saved cursor after the provider reaches its oldest retained result. What is the safe state?
Reveal the answer and explanation
One best answer: Coverage unknown; preserve the checkpoint and seek archival history or operator review.
Provider history may be pruned or narrower than the period your application must cover. If the durable cursor cannot be located, preserve it and report unknown coverage while seeking an archival provider or an operator decision.
Sources: getSignaturesForAddress · Clusters and Public RPC Endpoints
Practice 3 of 3
A notification arrives for a former subscription ID but matches the active cluster. Which check still rejects it?
Reveal the answer and explanation
One best answer: The subscription ID and connection generation do not match the current listener.
Subscription identifiers are scoped to a live connection and can be reused after reconnect. Bind each callback to its connection generation and active subscription so delayed messages from an old socket cannot mutate current state.
Sources: Solana RPC WebSocket Methods
This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.
Never enter a recovery phrase or private key into a learning site or lesson exercise.
READ THE SOURCE
Go deeper in the official docs
Reviewed on 2026-10-03. Protocol documentation can change; check the linked page’s current version as you use this material.
YOUR PROGRESS, SAVED
Ready for the checkpoint?
Make a free account to save your progress and unlock the quizzes.
Create free account