Intermediate · LESSON 01 OF 12
1. Build a reliable read-only client
By the end, you can design a client that distinguishes missing, stale, and failed data.
Read the workflow
The learner chooses a cluster and public address; the client calls getBalance without connecting a signing wallet or sending a transaction. The response carries lamports and a context slot, which the client checks against the request and any minimum context slot. A request generation prevents an older response from replacing the active view. A timeout remains unavailable rather than becoming zero, while HTTP 429 follows Retry-After guidance with bounded backoff. The interface preserves the cluster, address, commitment, slot, and time for each observation.
- NETWORK + WALLET
- READ-ONLY RPC
- VALIDATE SLOT
- DISPLAY OR RETRY
Read a text version: Four RPC outcomes, four different UI states
An HTTP rate limit, timeout, or malformed body means the client did not receive a usable observation; keep any earlier value labeled stale and retry within a bound. A JSON-RPC error is a method or provider error. A successful getAccountInfo response with value null means no account exists at the requested commitment. A successful getBalance value of zero with a context slot is valid data. Keep the cluster, address, method, commitment, endpoint, slot, and time attached to each rendered result.
- READ RESULT · SELECT METHOD
- HTTP 429 · timeout · malformed body
- JSON-RPC error object
- getAccountInfo · value: null
- getBalance · value: 0 + context slot
Read a text version: Keep RPC lamports exact before SOL display
Solana getBalance returns a u64 lamport value. JavaScript Number preserves every integer only through Number.MAX_SAFE_INTEGER, so plain JSON.parse can round a larger value before the app sees it. Decode with Solana Kit to bigint and keep the exact value through application logic. Divide and take the remainder by one billion to create the SOL display string without floating-point arithmetic. Keep the cluster, address, commitment, response slot, time, and request generation with the observation.
- getBalance returns a u64 number of lamports
- JavaScript Number is exact only through 2^53 - 1
- Keep the Kit-decoded value as bigint
- Format SOL with exact quotient and remainder
- Keep the cluster, wallet, commitment, and slot attached
FIELD NOTE 01
Separate the interface from the evidence
An RPC client requests network data from a service. Treat the service as a dependency with timeouts, rate limits, and possible stale responses. Render loading, empty, error, and successful states separately. A failed request must not become a balance of zero.
FIELD NOTE 02
Bind requests to their context
Attach the cluster, account, commitment, and request generation to every read. If a learner switches wallets while an old request is pending, discard the old response instead of displaying it for the new wallet. Record the slot when available so observations can be compared meaningfully.
FIELD NOTE 03
A bounded client exercise
Design a balance viewer with an explicit network selector, address validation, timeout, and manual retry. Return integer lamports from the data layer and format only at the display boundary. Inject a fake transport in tests to simulate a slow response, a rate-limit response, and a wallet change. Add a per-request deadline with a fresh signal, such as `.send({ abortSignal: AbortSignal.timeout(15_000) })`; a timeout stays an error and must never become a zero balance. Create a new signal for every retry because an aborted signal remains aborted.
FIELD NOTE 04
Interpret the method's result shape
Read the method schema before deciding that a response means “empty.” A successful `getAccountInfo` result may contain `value: null`, which means that the address has no account at the requested commitment. A successful `getBalance` result instead contains an integer `value` and response context; zero lamports is valid returned data. Neither `null` nor zero means the request failed. HTTP errors, a JSON-RPC `error`, malformed JSON, and a timeout are different states and do not establish that an account is absent. Branch on the selected method's documented result type, then render the exact outcome without replacing it with a guessed default.
FIELD NOTE 05
Carry freshness context without overclaiming
Keep the selected cluster, public address, RPC method, requested commitment, endpoint, response context slot, observation time, and request generation beside each result. `minContextSlot` lets a supported request specify a minimum bank slot; it is a lower bound, not proof that the response is the cluster's latest state or that two providers agree. If the learner changes wallet or cluster while a request is pending, reject responses from the old generation. When providers disagree, compare the same method and commitment, returned slots, and provider health before describing a conflict. Keep raw lamports as integers and label an earlier observation stale instead of making it look current.
FIELD NOTE 06
Choose and operate the endpoint for its workload
An HTTP 429 is a rate-limit response, not an empty balance. Shared public Solana endpoints are useful for development, but Solana says they are not intended for production applications. Select an endpoint by required clusters and methods, history, request and response limits, latency, slot freshness, reliability, and cost. Measure representative reads from the regions where your learners or services run; a fast but lagging result is still stale. For production reads, define timeouts, bounded retries, and exponential backoff with jitter, and test a separately operated fallback. If a response supplies the HTTP `Retry-After` field, honor its delay within the client's retry limit; if it does not, use the bounded backoff policy. Treat paid RPC URLs and API keys as credentials: keep unrestricted keys out of source, logs, analytics, and public bundles.
FIELD NOTE 07
Test behavior without trusting the network
Inject the transport or RPC object so tests can choose exact responses and delays. Check that a successful read preserves the returned slot, that integer formatting is exact, and that an invalid address never invokes the transport. Then delay a response, switch the selected address, and prove that the older result is ignored. Add cases for rate limiting, provider rejection, and malformed JSON values. These tests establish client behavior under controlled inputs; they do not prove a public RPC endpoint is honest, available, or current. Keep those evidence claims separate in the capstone report. Include a delayed fake RPC that observes the abort signal; prove the deadline cancels the pending request and leaves the previous successful observation visibly stale instead of replacing it with zero.
FIELD NOTE 08
Choose a current SDK and document its limits
Solana’s current TypeScript documentation recommends `@solana/kit` for new development and labels `@solana/web3.js` the legacy SDK. Kit composes RPC, signer, and transaction capabilities as plugins. Existing ecosystem libraries may still depend on web3.js, so choose a compatible set deliberately and test the exact APIs you use instead of assuming the two SDKs are interchangeable. The Builder workbook pins Kit 8.3.0 and uses a fake RPC in automated tests; its optional Devnet command reads a public balance only. Version support is directional: `@solana/web3.js` 1.99.0 and later can read and deserialize v1 transactions when the RPC request sets `maxSupportedTransactionVersion: 1`, but web3.js v1 cannot build, sign, or send v1; earlier v1 releases are limited to legacy and v0. Use a current, pinned `@solana/kit` 8.x stack for v1 construction and sending, and verify the wallet path you will ship. Pin dependencies, record the SDK and cluster with each observation, and recheck the migration guide when dependencies change. A current SDK does not prove an RPC response is complete or truthful.
MAKE THE CONNECTION
A detail worth keeping
Avoid request races by associating the wallet and network with each pending call. A UI can issue request 1 for Alice and request 2 for Ben; if response 1 arrives last, the display must reject it. Cancellation helps but a generation counter is a useful final guard.
Work through an example
A retry should be bounded and visible. Exponential delay with jitter reduces synchronized load, but after a rate limit the UI must honour provider guidance. Cached readings should include their time and network so a learner knows they may be stale.
const response = await rpc.getBalance(owner, { commitment: 'confirmed' }).send({
abortSignal: AbortSignal.timeout(timeoutMs),
});
if (typeof response.value !== 'bigint' || typeof response.context?.slot !== 'bigint') {
throw new TypeError('RPC returned an unexpected balance response');
}
return {
lamports: response.value.toString(),
slot: response.context.slot.toString(),
commitment: 'confirmed',
};Write pseudocode for the viewer and tests for a delayed old-wallet response. Show an error message that keeps the last successful observation visibly labeled as stale.
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. Unless a lesson explicitly says “read-only Devnet,” use paper or a local test and do not connect to a public network.
- Download and unzip Builder workbook 01; keep the folder outside your course repository.
- Run npm ci with Node.js 20 or later to install the pinned Solana Kit dependency.
- Run npm test and explain why every test uses a fake RPC instead of a wallet or network.
- Choose a public address and run npm run read -- <PUBLIC_SOLANA_ADDRESS> against the fixed Devnet endpoint.
- Record the cluster, confirmed commitment, context slot, raw lamports, and exact SOL display.
Before you move on, check your reasoning
- Build a read-only account lookup using the current official Kit client guide.
- Mock a slow result for wallet A followed by a fast result for wallet B.
- Assert that only B is shown and an RPC failure is not rendered as zero.
OPEN SELF-CHECK · UNGRADED
Pause and test your reasoning
Practice 1 of 4
A wallet changes from address A to B. A slow balance request for A finishes after the request for B. Which UI behavior is safest?
- Display whichever response arrived last, even if it belongs to A.
- Show zero for B until the older request finishes.
- Associate each request with its address or generation and ignore a stale result that no longer matches the active context.
Reveal the answer and explanation
One best answer: Associate each request with its address or generation and ignore a stale result that no longer matches the active context.
Network responses can arrive out of order. Bind each result to the request context and active generation before rendering it; otherwise a valid old balance can be mislabeled as the newly selected wallet’s state.
Sources: getBalance ↗ · Solana RPC Overview ↗
Practice 2 of 4
An HTTP 200 JSON-RPC response to `getAccountInfo` has `result.value: null`. What should the account viewer infer?
- No account exists at that commitment; this is a successful result.
- The endpoint failed, so treat every address on this cluster as absent until another provider responds.
- A zero-balance System Program account exists, and its decoded fields should be shown as valid account data.
Reveal the answer and explanation
One best answer: No account exists at that commitment; this is a successful result.
A null getAccountInfo value means the account does not exist at the requested commitment. It is a valid method result, not a timeout or RPC failure, and it says nothing about another commitment or cluster.
Sources: getAccountInfo ↗ · Solana RPC Overview ↗
Practice 3 of 4
A cached balance was read on Devnet at processed commitment. What context must travel with it?
- Only the amount matters.
- Carry address, cluster, commitment, time, and request ID.
- The formatted balance remains valid for any wallet and cluster after the address or request context changes.
Reveal the answer and explanation
One best answer: Carry address, cluster, commitment, time, and request ID.
A numeric balance without request context can be stale or attributed to the wrong network. Bind data to the exact query so the UI can reject results that no longer apply.
Sources: getBalance ↗ · Clusters and Public RPC Endpoints ↗
Practice 4 of 4
A `getBalance` result contains 9,007,199,254,740,993 lamports. Which path keeps it exact in JavaScript?
- Convert it to `number`; JavaScript numbers can exactly represent every `u64`.
- Keep the Kit-decoded `bigint` and format SOL with integer arithmetic.
- Round the raw lamport value to a whole SOL before storing it.
Reveal the answer and explanation
One best answer: Keep the Kit-decoded `bigint` and format SOL with integer arithmetic.
A JavaScript number stops representing every integer exactly at 2^53 - 1, below the full u64 range. Preserve bigint through application logic and create the exact decimal string only for display.
Sources: getBalance ↗ · Solana Kit README ↗ · JavaScript Number.MAX_SAFE_INTEGER (MDN) ↗ · JavaScript BigInt (MDN) ↗
This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.
Scored checkpoints and level exams are available online through a free account. They are not included in this pack.
Optional lab files
These files are included with the pack. Follow the safety and toolchain notes in the lesson before running a lab; a Devnet read requires an internet connection.
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 when you reconnect.
- Solana RPC Overview ↗
- getBalance ↗
- JavaScript and TypeScript SDK for Solana ↗
- Kit Client ↗
- Larger Transaction Sizes ↗
- Migrating to Kit ↗
- Clusters and Public RPC Endpoints ↗
- getAccountInfo ↗
- Choosing an RPC Provider ↗
- HTTP Semantics: Retry-After (RFC 9110) ↗
- Solana Kit RPC request cancellation ↗
- JavaScript Number.MAX_SAFE_INTEGER (MDN) ↗
- Solana Kit README ↗
- JavaScript BigInt (MDN) ↗