How Solana RPC is organized
Every Solana node exposes a JSON-RPC interface, and it comes in two flavours that solve different problems.
HTTP methods are request/response. You POST a method name and parameters, the node does the work, and it answers once. The connection has no memory of you afterwards. This is the right shape for one-off reads (what's in this account?), for writes (send this transaction), and for history (what happened at this signature?). It is also the shape that tempts you into polling - calling the same method on a timer to watch something change - which is exactly where latency creeps in.
WebSocket subscriptions are push channels. You open a long-lived connection, subscribe to a thing once, and the node sends you a message every time that thing changes. No loop, no interval, no re-asking. Subscriptions are the right shape for watching state evolve: an account you care about, the logs of a program, the status of a transaction you just sent.
HTTP methods answer a question once. WebSocket subscriptions keep answering it for you. The mistake is using the first to do the second's job - that's just polling with extra steps.
Both speak the same data model and both respect a commitment level (processed, confirmed,
finalized) that decides how settled the data you get back is. We unpack that in
Solana Commitment Levels Explained; for this reference,
just remember that commitment is a knob on almost every method, and the looser it is the faster -
and less settled - your answer.
The core read methods
These are the HTTP methods you'll call most. They fetch state and history without changing anything on chain.
| Method | What it does |
|---|---|
getAccountInfo | Returns the data, owner, lamports, and metadata for a single account |
getMultipleAccounts | Same as above for a batch of accounts in one round-trip - prefer this over many getAccountInfo calls |
getBalance | Returns just the lamport balance of an account |
getProgramAccounts | Returns every account owned by a program that matches your filters - powerful and heavy |
getTokenAccountsByOwner | Returns the SPL token accounts held by a wallet, optionally filtered by mint |
getLatestBlockhash | Returns a recent blockhash and its last-valid height - required to build any transaction |
getSlot | Returns the current slot the node has reached at your commitment |
getBlock | Returns a full block - its transactions, rewards, and metadata - for a given slot |
getTransaction | Returns a confirmed transaction (and its decoded meta) by signature |
getSignaturesForAddress | Returns recent transaction signatures touching an address, newest first - the basis of "account history" |
getSignatureStatuses | Returns the confirmation status of one or more signatures - used to poll for a send landing |
A few habits worth forming early. Batch with getMultipleAccounts instead of firing
getAccountInfo in a loop. Use getSignaturesForAddress to page history, then getTransaction
to hydrate the ones you care about. And treat getProgramAccounts as a tool you reach for
deliberately, not reflexively - more on why below.
The write and send methods
These submit work to the cluster or estimate it before you commit.
| Method | What it does |
|---|---|
sendTransaction | Submits a signed, serialized transaction to the leader - returns a signature, not a confirmation |
simulateTransaction | Runs a transaction against the current bank without submitting it - catches errors and reports compute used and logs |
getFeeForMessage | Returns the fee a given message would cost at a recent blockhash |
The key thing about sendTransaction is what it does not do: it does not wait for the
transaction to land. It hands the bytes to the leader and gives you back a signature. Confirmation
is a separate job - poll getSignatureStatuses or open a signatureSubscribe. Run
simulateTransaction first and you'll catch most failures (bad accounts, insufficient compute,
program errors) for free, before you spend a fee or a blockhash. Why so many sends fail anyway is
its own topic, covered in Why Solana Transactions Fail.
The WebSocket subscriptions
Open a WebSocket connection and these let the node push updates to you instead of you asking.
| Subscription | What it pushes |
|---|---|
accountSubscribe | A message every time a specific account's data or lamports change |
logsSubscribe | Transaction logs matching a filter (all, or mentioning an address) as they occur |
signatureSubscribe | A single message when a given signature reaches your chosen commitment - then it's done |
slotSubscribe | A message each time the node processes a new slot - a cheap heartbeat |
programSubscribe | A message every time any account owned by a program changes, with optional filters |
signatureSubscribe is the clean way to confirm a send: subscribe to the signature, get one push
when it lands, move on. programSubscribe is the push-based answer to getProgramAccounts - watch
a program's accounts change instead of re-scanning them on a timer. That said, the per-subscription
model has a ceiling: thousands of accountSubscribe channels or a busy programSubscribe firehose
will strain a single WebSocket, which is the wall that pushes serious pipelines toward gRPC.
The gotchas that bite
A reference is only useful if it tells you where the edges are. These are the three that catch people most often.
getProgramAccounts is heavy. It walks every account a program owns and returns the ones that
match your filters. On a program with a large account set, that's a slow, memory-hungry scan the
node performs on every call. Shared and public tiers routinely rate-limit it, cap the response
size, or surcharge it - and rightly so, because one tight loop of getProgramAccounts can degrade
a node for everyone on it. Use filters (dataSize, memcmp) to shrink the result, and if you're
calling it repeatedly to track changes, you want a programSubscribe or a gRPC stream, not a poll.
Polling adds latency you didn't choose. Any method called on a timer - getAccountInfo,
getSignaturesForAddress, getProgramAccounts - pays, on average, half your poll interval in pure
staleness before you even see a change, plus the round-trip every call. A 400ms loop is ~200ms
behind the network on a good day. For latency-sensitive work that's disqualifying; the full
breakdown is in Why RPC Polling Can't Keep Up.
Commitment matters. Nearly every read takes a commitment, and the default isn't always what you
want. Read at processed and you may see state that gets dropped; read at finalized and you wait
longer for certainty. Confirm sends and reconcile accounting at the commitment your strategy
actually requires - don't inherit a default and hope.
Outgrowing request/response?
rpc edge runs RPC, push-based gRPC, and decoded shreds co-located with the cluster - the earliest view, fewest hops.
A couple of calls in practice
Reading one account, then sending a transaction and confirming it, looks roughly like this.
// read a single account at confirmed commitment
const info = await connection.getAccountInfo(pubkey, "confirmed");
// build, simulate, send, confirm
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash("confirmed");
tx.recentBlockhash = blockhash;
tx.sign(payer);
// catch errors before paying for them
const sim = await connection.simulateTransaction(tx);
if (sim.value.err) throw new Error("would fail: " + JSON.stringify(sim.value.err));
// send returns a signature, not a confirmation
const sig = await connection.sendRawTransaction(tx.serialize());
// confirm by polling status (or use signatureSubscribe on the WebSocket)
const status = await connection.getSignatureStatuses([sig]);Notice the rhythm: getLatestBlockhash to build, simulateTransaction to check,
sendTransaction to submit, getSignatureStatuses to confirm. Four methods, one transaction.
When to stop using RPC methods and stream instead
RPC methods are the right tool for one-off reads, for writes, and for history. They stop being the right tool the moment you're watching state change continuously and competing on time.
The first step off the methods is Yellowstone gRPC. Instead of polling
getProgramAccounts or fanning out thousands of accountSubscribe channels, you open one stream,
describe what you care about with server-side filters, and the validator pushes account and
transaction updates as it processes them. One connection, filtering next to the data, no loop. We
compare the two models directly in
Yellowstone gRPC vs Standard RPC.
The step beyond that is decoded shreds. gRPC removes the poll interval but still surfaces data at the validator's commitment. Shreds sit one layer further upstream, at the propagation layer, where blocks travel before any node has assembled a confirmed one. Decode transactions straight from shreds and you get first-seen, pre-confirmation visibility - the earliest the network makes a transaction observable.
The takeaway
Most of Solana RPC is a dozen methods: a handful of reads, two or three writes, five
subscriptions. Learn the shapes - HTTP for ask-once, WebSocket for keep-answering - watch out for
getProgramAccounts and tight poll loops, and set commitment deliberately. Then, when
request/response stops keeping up with a real-time strategy, move up the stack: push with gRPC,
read upstream from shreds, and put it all next to the cluster.