RPC & WebSockets In Depth
Solana clients never talk to the ledger as a raw database; they talk to RPC nodes that expose JSON-RPC over HTTP and real-time updates over WebSockets.
Wallet balances, program account fetches, transaction sends, signature waits, and (on many providers) digital-asset galleries all ride that access layer.
This page is the section umbrella: HTTP vs subscriptions, cheap vs expensive methods, core RPC vs DAS, and how providers, pagination, and fee/simulation APIs complete a production stack.
Summary
- Solana RPC is the cluster-facing API surface: clients use HTTP JSON-RPC for reads and transaction submission, WebSocket subscriptions for push updates, and (on DAS-enabled providers) asset-centric methods layered on indexed chain data.
- Insight: Wrong transport, method, or commitment choices create rate limits, stale UI, false success states, and mainnet timeouts; a shared map of HTTP vs WS, cheap vs expensive methods, and core RPC vs DAS prevents treating every call as free and interchangeable.
- Key Concepts: JSON-RPC, commitment, HTTP RPC, WebSocket subscription, getAccountInfo, getMultipleAccounts, getProgramAccounts (GPA), filters, DAS, provider, pagination, simulateTransaction, priority fee.
- When to Use: First orientation before wiring
@solana/kitclients; choosing poll vs subscribe; planning mainnet GPA and NFT portfolio reads; selecting provider features and fee estimation paths. - Limitations/Trade-offs: Public RPC has tight rate limits and no SLA; DAS and enhanced fee APIs are provider features, not guaranteed on every URL; WebSocket subscriptions are node-local views and still need commitment discipline; GPA does not replace a real indexer at scale.
- Related Topics: RPC basics and commitment, core read methods, GPA filters, pagination, providers, and priority-fee/simulation APIs.
Foundations
RPC (Remote Procedure Call) is how applications observe and submit work on a Solana cluster.
An RPC node runs (or fronts) validator software such as Agave, serves JSON-RPC 2.0 over HTTPS, and typically exposes a companion WebSocket endpoint for subscriptions.
Clients do not need to run a validator; they need a correct RPC URL for the cluster they mean (mainnet-beta, devnet, testnet, or localnet).
Requests are JSON-RPC objects: method names like getBalance or sendTransaction, optional params, and an id echoed in the response.
Responses carry either a result or an error with a code and message; application code should treat HTTP 429, timeouts, and JSON-RPC errors as first-class failure modes.
Commitment (processed, confirmed, finalized) is orthogonal to method choice: it tells the node how deep in consensus a read or notification must be before it answers.
Reading at processed is fast and fork-sensitive; reading at finalized is slower and safer for irreversible product decisions.
The HTTP path is request/response: get this account, send this transaction, simulate this payload, return once.
The WebSocket path is long-lived: subscribe once, receive push notifications when an account changes, a signature reaches commitment, or matching logs appear.
HTTP and WebSocket are two faces of the same RPC product, not two ledgers; you still pick one cluster and one commitment policy.
Core read methods hydrate application state from accounts the runtime already knows by pubkey: balances, single-account data, and batched multi-account loads.
getProgramAccounts asks a harder question: return every account owned by a program (optionally filtered), which forces a scan of program-owned state and is the classic source of mainnet timeouts and rate limits.
DAS (Digital Asset Standard) APIs extend JSON-RPC with asset-centric methods (getAsset, getAssetsByOwner, search/group queries) backed by provider indexers that stitch token programs, metadata, and compression trees into one model.
DAS is not a required Agave built-in on every public URL; it is a provider capability you enable when NFT, cNFT, and portfolio UX would be painful on raw token walks alone.
Providers (managed RPC, DAS, webhooks, gRPC) and self-hosted validators trade ops burden for control; production apps leave free public endpoints behind.
Pagination and efficiency keep you under RPS and payload caps: batch known pubkeys, cursor signature history, filter GPA tightly, and move discovery to indexers when sets grow.
Simulation and priority-fee APIs sit on the write path: estimate compute units and micro-lamports per CU, and use simulateTransaction to surface program errors without spending mainnet fees on every dry run.
Together these pieces form one map:
Client (@solana/kit, wallet, backend worker)
|
+-- HTTPS JSON-RPC --------> RPC / provider edge
| reads, send, simulate, fees
|
+-- WSS subscriptions -----> same cluster surface
| account / logs / signature / slot
|
+-- DAS methods (optional) -> DAS-enabled provider
getAsset, getAssetsByOwner, searchAssets
Sibling pages in this section zoom each branch; this page keeps the whole diagram in view.
Mechanics & Interactions
HTTP request lifecycle
A typical read is one POST with Content-Type: application/json.
The node resolves the method against its bank snapshot at the requested commitment, encodes account data (base64, base58, or jsonParsed where supported), and returns.
sendTransaction accepts a serialized, signed transaction and returns a signature handle; durability still requires polling or a WebSocket subscription until the chosen commitment is reached.
App builds + signs tx
|
v
HTTP sendTransaction --> signature S
|
+-- poll getSignatureStatuses(S), or
+-- signatureSubscribe(S) over WSS
|
v
commitment ladder: processed -> confirmed -> finalized
WebSocket subscriptions
The client opens wss://..., sends a *Subscribe method, and receives a subscription id plus later *Notification messages.
Common subscriptions:
| Subscription | Fires when |
|---|---|
accountSubscribe | Account lamports or data change |
logsSubscribe | Transactions match a filter (e.g. mentions) |
signatureSubscribe | Transaction reaches the subscription commitment |
slotSubscribe | New slots progress |
Notifications are push views from the node you connected to, not a global ordered bus across all providers.
Unsubscribe (or abort the kit subscription) when components unmount so connections do not leak.
Cheap reads vs expensive discovery
| Pattern | Methods | Cost posture |
|---|---|---|
| Known pubkey | getBalance, getAccountInfo, getMultipleAccounts | Low to medium; batch multi-get |
| Program scan | getProgramAccounts | High without filters; may timeout |
| History | getSignaturesForAddress, getTransaction | Medium to high; paginate and cache |
| Assets | DAS getAsset / getAssetsByOwner | Provider-indexed; plan-dependent |
| Live UI | WebSocket subscriptions | Connection + notify load |
Always prefer known-pubkey reads when an indexer, PDA derivation, or prior response already gave you addresses.
Use GPA with dataSize and memcmp filters only when you must discover accounts by layout; never default to unfiltered GPA on mainnet for large programs.
When GPA still cannot meet product scale, move discovery to Geyser/Yellowstone-style pipelines or a dedicated indexer rather than retrying the same scan.
DAS relative to core RPC
Core RPC answers "what is the account at this address?"
DAS answers "what digital assets does this owner hold, and what metadata/compression proof do I need to render or verify them?"
Use core RPC (and kit) for custom program accounts, fee payers, and transaction submission.
Use DAS for NFT galleries, collection queries, and compressed assets where the provider has already paid the indexing cost.
Providers, limits, and efficiency
Managed providers front Agave (or equivalent) with load balancers, API keys, and optional sidecars (DAS, webhooks, enhanced fee estimates).
Public cluster URLs exist for learning and light devnet work; they throttle aggressively and are not a production plan.
Efficiency rules that apply everywhere:
- Chunk
getMultipleAccounts(often ~50-100 keys per call, subject to provider docs). - Paginate
getSignaturesForAddresswithbefore/untilcursors and alimit. - Cache immutable or slowly changing account data; refresh on subscription or after your own writes.
- Back off on 429; fail over to a secondary URL when SLAs allow.
Simulation and priority fees
Before landing sensitive transactions, clients often:
simulateTransactionwith options such assigVerify: falseandreplaceRecentBlockhashto readunitsConsumed, logs, and errors.- Sample
getRecentPrioritizationFees(and/or provider percentile fee APIs) for accounts involved in the lock set. - Attach compute budget instructions with headroom over simulated CUs and a CU price in micro-lamports.
Simulation is not a substitute for mainnet confirmation; it reduces avoidable failures and underpriced inclusion.
Advanced Considerations & Applications
Section concept map (what each sibling owns)
| Concern | Primary page | Builder takeaway |
|---|---|---|
| JSON-RPC shape, commitment, rate limits | RPC Basics | Set commitment explicitly; treat 429 as normal |
| Balance, account info, multi-get, GPA overview | Core RPC Methods | Prefer multi-get over chatty single reads |
memcmp / dataSize, Anchor discriminators | getProgramAccounts & Filters | Filter server-side or do not scan |
| Cursors, batching, avoiding heavy scans | Pagination & Efficiency | GPA is not paginated; design discovery offline |
| Fee samples, simulate options, CU headroom | Priority Fee & Simulation APIs | Estimate then send; log CU and fee choices |
| Managed vs self-host, feature matrices | RPC Providers | Match DAS/gRPC needs to vendor; keep keys out of source |
WebSocket subscriptions and DAS have dedicated how-to pages in this section; operationally they still hang off the same provider URL and commitment policy described above.
Designing a client stack
Separate HTTP RPC, WSS, and DAS configuration even when one vendor issues all three.
Pin commitment per action class in one module rather than scattering string literals.
For write-heavy products, combine simulation-driven CU limits with dynamic priority fees and a confirmation path that does not treat sendTransaction success as settlement.
When to leave pure RPC
| Need | Stay on core RPC / WS | Leave for indexer / gRPC / webhooks |
|---|---|---|
| Wallet balance + few PDAs | Yes | No |
| Live single-account UI | WebSocket | Optional |
| Full program analytics | No | Yes |
| Large NFT portfolio + cNFTs | DAS if available | Custom indexer if DAS insufficient |
| Millisecond multi-account streams | Limited | Yellowstone / Geyser class |
Tooling alignment
@solana/kit 7.0.0 provides createSolanaRpc for HTTP and createSolanaRpcSubscriptions for WSS on standard methods.
DAS often uses direct fetch or a provider SDK until you wrap it; keep DAS base URLs configurable next to kit RPC URLs.
CLI (solana against the same RPC URL) is useful for debugging the identical methods your app calls.
Common Misconceptions
- "A transaction signature from sendTransaction means the transfer is final." The signature is a handle for status polling or
signatureSubscribe; durability is the commitment level you wait for afterward. - "HTTP and WebSocket are interchangeable for every use case." HTTP fits one-shot reads and sends; WebSockets fit continuous updates, but both still need commitment, auth, and cleanup discipline.
- "getProgramAccounts is just another read like getAccountInfo." GPA can scan large program-owned sets; without filters it is often the most expensive RPC call in your app.
- "DAS methods work on any Solana RPC URL." DAS requires a DAS-enabled provider endpoint; vanilla public RPC will error or omit those methods.
- "Polling every 200ms is equivalent to a subscription." Aggressive polling burns rate limits and still lags; subscriptions reduce chatter but are not ordered global events across nodes.
- "Priority fees and simulation guarantee inclusion." They improve prediction and reduce avoidable failures; leaders, congestion, and blockhash expiry still require retry and confirmation logic.
FAQs
What is Solana RPC in one sentence?
It is the JSON-RPC (and WebSocket) API surface through which clients read accounts, submit transactions, and observe commitment on a chosen cluster.
When should I use HTTP vs WebSockets?
Use HTTP for request/response reads, sends, simulation, and fee samples; use WebSockets when you need push updates for accounts, logs, or signature confirmation without tight polling loops.
What commitment should my dapp use by default?
Many product actions use confirmed for responsive UX; use finalized before irreversible external effects; treat processed as optimistic only.
Why does getProgramAccounts fail on mainnet?
Unfiltered or weakly filtered scans timeout or hit provider limits; add dataSize and memcmp filters or move discovery to an indexer.
How is getMultipleAccounts different from GPA?
getMultipleAccounts loads known pubkeys in a batch; GPA discovers accounts by owner (and filters) when you do not already know the addresses.
Is DAS part of core Solana protocol RPC?
DAS is a standardized asset-oriented API offered by indexing providers on top of chain data; it is not guaranteed on every Agave public endpoint.
Can I use DAS for my custom program accounts?
Usually no for arbitrary layouts; DAS targets digital assets (tokens, NFTs, cNFTs). Custom program state still uses core RPC, filters, or your own indexer.
Do WebSocket notifications survive forks the way finalized reads do?
No. Notifications honor the commitment you subscribed with; processed updates can roll back on forks, so UI must remain reversible until deeper commitment.
How do I paginate Solana RPC results?
Signature history uses cursors (before / until) and limit; multi-get is chunked by pubkey list; GPA is not offset-paginated, so filters or external indexes carry large discovery; DAS often exposes page / limit on provider APIs.
What are common provider choices?
Managed options include Helius, Triton, and QuickNode for hosted RPC plus optional DAS, gRPC, or webhooks; self-hosting Agave trades ops cost for control; public free RPC is for light non-production use.
How do simulation and priority fee APIs fit the send path?
Simulate to estimate compute units and catch program errors; sample recent prioritization fees (or provider percentiles) to set CU price; then send and wait for commitment.
Does @solana/kit replace understanding RPC methods?
No. Kit 7.0.0 types and wraps the same JSON-RPC and subscription surfaces; you still choose methods, commitment, filters, and providers correctly.
Should production apps share one global public RPC URL?
No. Use authenticated provider endpoints (and often separate HTTP/WSS/DAS URLs), rate-limit client-side, and plan failover for critical paths.
When is an indexer mandatory instead of RPC alone?
When you need full historical analytics, large program-wide discovery, or multi-account streams that exceed filtered GPA and subscription capacity.
Where should I go next after this page?
Start with RPC Basics for commitment and client setup, then Core RPC Methods for everyday reads; open GPA, pagination, fees, and providers as those pain points appear.
Related
- RPC Basics - JSON-RPC shape, commitment levels, and rate limits
- Core RPC Methods -
getAccountInfo,getBalance,getMultipleAccounts, GPA overview - getProgramAccounts & Filters -
memcmp,dataSize, and safe program scans - Pagination & Efficiency - batching, cursors, and avoiding heavy scans
- Priority Fee & Simulation APIs - fee samples,
simulateTransaction, and CU headroom - RPC Providers - managed providers, public RPC, and self-hosting trade-offs
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.