Solana
Solana reads for wallets and apps, without trusting a single RPC node. Every read
is sent to three independent Solana RPC operators at finalized
commitment, and it is answered only when all three agree. If one is down,
behind, rate limiting or says something different, you get an error, never one
provider's word.
Endpoint
POST https://mainnet.subfrost.io/v4/YOUR_API_KEY/solana
The API key is a path segment, or an X-Subfrost-Api-Key header, which takes
precedence (see Authentication). It is
standard Solana JSON-RPC 2.0, so @solana/web3.js, solana-client and other
SDKs work by pointing their RPC URL here:
{ "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": ["25oyhHXj19hFvtut2Vd9fq3u3E2YJrHwQXoY7vRFpfBb"] }
How an answer is agreed
The providers are api.mainnet-beta.solana.com (Solana Foundation), solana-rpc.publicnode.com (Allnodes) and solana.leorpc.com (LeoRPC): three operators on three networks.
- Every read is made at
finalized.confirmedandprocessedare refused, because no two nodes can agree on them. - Honest nodes answer
finalizeda few slots apart. When they differ and the answers came from different slots, the read is repeated at the newest of those slots (minContextSlot) until they agree, up to 3 times. A difference at the same slot is an error at once. context.slotin the answer is the oldest slot any provider read at, so every one of them holds that state.
Agreed answers carry these headers:
| header | value |
|---|---|
X-Subfrost-Quorum | 3/3 |
X-Subfrost-Quorum-Providers | the three operators |
X-Subfrost-Commitment | finalized |
X-Subfrost-Slot | the slot the answer is from |
X-Subfrost-History-From-Slot | on history reads: the oldest slot every provider holds |
Methods
| method | notes |
|---|---|
getAccountInfo, getMultipleAccounts (≤ 100), getBalance | the agreed account (lamports, owner, data, executable, rentEpoch, space) |
getSlot | the newest slot every provider has finalized |
getLatestBlockhash | the oldest provider's blockhash, confirmed valid at finalized by every provider (isBlockhashValid) |
getTransaction | returned only when every provider has it, identical; null when no provider knows it |
getSignaturesForAddress | the signatures every provider holds, newest first; page with before |
simulateTransaction | the verdict (err) must agree. unitsConsumed is the highest any provider reports (budget for it). logs / accounts / returnData are returned only when identical everywhere, otherwise null. |
sendTransaction | your signed transaction, sent to all three; one acceptance returns the signature. A preflight failure is passed back. |
getHealth | liveness |
History is limited to about 6.5 hours
One of the three providers keeps only the most recent ~58,000 slots (about 6.5
hours). Older transactions and signatures cannot be confirmed by all three, so
the endpoint answers quorumMissing, naming the provider and the oldest slot it
holds. It never presents a short list as "the end of history". Recent deposits
and payments are within the window. Use X-Subfrost-History-From-Slot to see
where it starts.
Errors
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32051, "message": "quorumMissing: …", "data": { "edge": "quorumMissing" } } }
data.edge | code | HTTP | what to do |
|---|---|---|---|
quorumMissing | -32051 | 503 | a provider did not answer, or lacks that history; retry (Retry-After) |
quorumRateLimited | -32052 | 503 | retry |
quorumPending | -32053 | 503 | not finalized everywhere yet; retry in a few seconds |
quorumDisagree | -32054 | 500 | providers disagree; nothing is returned |
| (invalid params / commitment) | -32602 | 400 | fix the request |
| (unknown method) | -32601 | 400 |
An error every provider gives alike (an invalid pubkey, say) is returned as their agreed answer.