Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides13 min read

Solana Commitment Levels: processed vs confirmed vs finalized

How Solana's processed, confirmed and finalized commitment levels change what every RPC read returns, and how to choose one deliberately.

TL;DR

Solana's commitment parameter tells an RPC node how far along the fork-choice and vote pipeline a slot must be before it answers your read. processed means the node has produced or seen the block locally and it can still be rolled back; confirmed means a supermajority of stake has voted on it; finalized means the block is rooted and cannot be rolled back without a cluster restart. Commitment is a per-call argument, so the same address or signature can return different results at different levels, especially around forks. Choose confirmed for user-facing UX, finalized for settlement and accounting, and processed only for latency-sensitive speculative reads.

What the commitment parameter actually controls

Every Solana JSON-RPC read method accepts an optional commitment object, for example {"commitment":"confirmed"}. It is not a global node setting and not a property of an address or transaction: it is a per-call instruction that tells the node the minimum level of irreversibility you are willing to accept before it answers. The same getBalance call at processed and at finalized can legitimately return different lamport values.

The official Solana RPC documentation defines the three levels and their defaults, and the transaction confirmation and expiration documentation describes how a block moves from produced to rooted. Treat those two pages as the authoritative primary sources; everything below explains the mechanism and how to apply it over RPC.

Because commitment is per-call, a trading bot, indexer or dApp can read the same data at two levels in the same second and get two different, individually correct answers. The skill is deciding which level each read deserves rather than copying a default.

  • processed: the node has produced or observed the block locally; it may still be rolled back.
  • confirmed: a supermajority of stake has voted on the block; used by most UIs and by default for many reads.
  • finalized: the block is rooted; it cannot be rolled back without a cluster restart.

The slot stream, votes and rooting between the levels

Solana produces a continuous stream of slots via proof-of-history, with a leader scheduled for each slot. A block at a slot is not instantly irreversible; it becomes progressively more so as validators vote. A vote is a validator's signed attestation that it has seen and accepts a block at a given slot and height.

When a supermajority of staked validators have voted on a block, it reaches the confirmed threshold. As votes accumulate on that block and its descendants, the cluster roots the block, which is the finalized state. Rooting is what makes rollback require a cluster restart rather than an ordinary fork choice.

This is why the levels are a ladder, not three unrelated states. processed is the node's local view, confirmed is the stake-weighted cluster view, and finalized is the rooted history. A read at a higher level is answering a stricter question about the same chain.

The commitment ladder and its vote/root mechanics are documented by Solana in Transaction Confirmation & Expiration; that page is the authoritative primary source for the levels described below.

Why the same read can differ across commitment levels

During a fork, a transaction or block can exist at processed on one node and be absent at finalized because the fork it lived on was not rooted. A balance read at processed may include a transfer that a finalized read does not yet reflect, or may reflect a transfer that later disappears with the fork.

This is the central consequence for indexers and accounting systems: a successful processed read is not final. If you count deposits at processed, you can double-count or count a transfer that never roots. If you settle at confirmed, you accept a small residual reorg risk that finalized removes.

The practical rule is to match the commitment to the cost of being wrong. Speculative UI state can tolerate processed; money movement and ledger entries should not.

getLatestBlockhash, expiry and the retry loop

getLatestBlockhash requires a commitment because the blockhash it returns is only valid for a bounded window, commonly described as roughly 150 blocks. If you fetch the blockhash at processed and then wait, the hash can expire before your transaction lands, producing an expiry error rather than a fork problem.

Fetch the blockhash at the same level you intend to confirm at, or at least at confirmed, and re-fetch it when you rebuild a transaction after a timeout. This interacts directly with retry strategy: a retry that reuses an expired blockhash will fail regardless of how many times you send it. See Solana RPC timeouts, retries and transaction sending for the surrounding send-and-retry mechanics.

A robust sender fetches a fresh blockhash, signs, sends, then polls status at a chosen commitment and rebuilds with a new blockhash if the window closes.

Reading commitment from every major RPC surface

getBalance, getAccountInfo and getTransaction all accept a commitment and answer at that level. getSignatureStatuses is the right way to poll a transaction you have sent, because it returns a confirmationStatus field per signature that tells you whether the cluster currently sees it as processed, confirmed or finalized.

WebSocket subscriptions fix the notification level at subscribe time. You cannot change commitment on an existing subscription; you must unsubscribe and resubscribe. The Solana RPC WebSocket pubsub and subscriptions guide covers the subscription lifecycle in detail.

For historical reads, commitment still applies but the data is already rooted, so the level mostly affects how the node serves the query rather than whether the answer can change. See Querying Solana historical data over RPC for that distinction.

  • getLatestBlockhash: commitment controls which blockhash you receive and how fresh it is.
  • getBalance / getAccountInfo: commitment controls whether unrooted state is included.
  • getTransaction: commitment controls whether a transaction on an unrooted fork is returned.
  • getSignatureStatuses: exposes confirmationStatus, the correct polling surface for sent transactions.
  • WebSocket subscriptions: notification level is fixed when you subscribe.

Sending at one level and confirming at another

Sending a transaction does not carry a commitment in the same way a read does; the transaction is broadcast and the cluster decides. What you control is the commitment you use when you poll for its status. A common production pattern is to send, then poll getSignatureStatuses at confirmed for UX, and separately require finalized before crediting an account.

Mixing levels carelessly is a frequent bug: a bot sends, polls at processed, sees success, and acts on a transaction that later disappears with a fork. The fix is not to avoid processed entirely but to make the confirmation gate explicit and consistent with the action's risk.

If you need to migrate away from older confirmation methods, Migrating off deprecated Solana RPC methods explains the getConfirmed* replacements and how commitment fits the newer surface.

Runnable example: blockhash fetch plus a commitment ladder

The script below fetches a blockhash at confirmed, sends nothing, and demonstrates polling getSignatureStatuses with a commitment ladder. Replace the signature with one you have actually sent. It uses only standard JSON-RPC over HTTPS, so it works against any Solana RPC endpoint you configure.

Run it against your own endpoint and record the observed confirmationStatus transitions and elapsed time. Do not treat any single run as a benchmark; the point is to see the ladder move from processed to confirmed to finalized for your own traffic.

// Node.js 18+ (global fetch). Set RPC_URL to your endpoint.
const RPC_URL = process.env.RPC_URL || "https://api.mainnet-beta.solana.com";

async function rpc(method, params) {
  const res = await fetch(RPC_URL, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
  });
  const json = await res.json();
  if (json.error) throw new Error(JSON.stringify(json.error));
  return json.result;
}

async function main() {
  // 1. Fresh blockhash at confirmed. Re-fetch if the window closes.
  const bh = await rpc("getLatestBlockhash", [{ commitment: "confirmed" }]);
  console.log("blockhash:", bh.value.blockhash, "lastValidBlockHeight:", bh.value.lastValidBlockHeight);

  // 2. Poll a signature you have sent. Replace with a real signature.
  const signature = process.env.SIGNATURE;
  if (!signature) {
    console.log("Set SIGNATURE to poll a real transaction.");
    return;
  }

  const ladder = ["processed", "confirmed", "finalized"];
  const start = Date.now();
  for (const commitment of ladder) {
    const status = await rpc("getSignatureStatuses", [[signature], { commitment }]);
    const v = status.value[0];
    console.log(
      commitment.padEnd(10),
      "confirmationStatus:", v ? v.confirmationStatus : "null",
      "slot:", v ? v.slot : "-",
      "err:", v ? JSON.stringify(v.err) : "-",
      "t+", Date.now() - start, "ms"
    );
  }
}

main().catch((e) => { console.error(e); process.exit(1); });

Results table: measure against your own endpoint

Provider behaviour, node topology and network conditions vary, so the only trustworthy numbers are the ones you measure. Fill the table below from repeated runs of the script above against your own endpoint, and keep the sample size and time window with the results.

Record the confirmationStatus you observed at each commitment, the elapsed time from send to each level, and any null or error responses. A null status at finalized for a transaction you saw at processed is a fork or expiry signal worth investigating, not a script bug.

  • Columns to fill: commitment level | observed confirmationStatus | elapsed ms | slot | err | notes.
  • Run at least 20 sends across different times of day before drawing conclusions.
  • Keep the endpoint, region and client version alongside the table so results stay comparable.
  • Documented / varies by provider: absolute confirmation times and status latencies are not fixed constants.

Decision table: matching commitment to use case

Use this as a starting policy, then tighten it with your own measurements. The goal is a deliberate choice per call, not a single global default.

For anything that moves money or writes an immutable ledger entry, finalized is the safe gate. For interactive UI that a user can see change, confirmed is the usual balance. processed is for speculative, latency-sensitive reads where a later correction is acceptable.

  • Live UI balance or portfolio display: confirmed.
  • Deposit crediting, withdrawals, accounting entries: finalized.
  • Speculative price or state preview, non-binding: processed.
  • Polling a sent transaction for UX: getSignatureStatuses at confirmed.
  • Settlement or irreversible action: getSignatureStatuses at finalized.
  • Historical analytics over rooted data: finalized.

Troubleshooting common commitment failures

Most commitment bugs are mismatches rather than protocol faults. If a transaction appears successful and then vanishes, you likely acted on a processed read. If a deposit is credited twice, you may be counting at processed and again at finalized without deduplication.

If a transaction never confirms, check the blockhash window before blaming the cluster: an expired blockhash fails regardless of commitment. If a WebSocket notification never arrives at the level you expect, confirm you subscribed at that level, because it cannot be changed in place.

If getTransaction returns null at finalized but you saw the transaction at processed, treat it as a fork or expiry event and reconcile against a rooted source before retrying.

  • Mixing levels between send and confirm: standardize the confirmation gate in one place.
  • Assuming confirmed equals finalized: they are different thresholds with different rollback risk.
  • Ignoring confirmationStatus: read it explicitly instead of inferring from a null or non-null result.
  • Reading balances at processed and double-counting: deduplicate by signature and settle at finalized.
  • Treating a confirmed transaction as irreversible for settlement: require finalized for money movement.

Limitations, tradeoffs and when to revisit

Higher commitment costs latency and can return null for data that exists but is not yet rooted. Lower commitment is faster but exposes you to rollback. There is no setting that is both maximally fast and maximally safe; the tradeoff is the point of the parameter.

This article describes documented protocol and RPC behaviour, not OnFinality-specific performance. Absolute confirmation times, status latencies and rate behaviour are documented / varies by provider and should be measured on your own endpoint. For endpoint options and how to compare them, see Solana RPC endpoints (RPC Assistant) and RPC pricing.

Revisit your policy when the cluster's vote behaviour, your provider's topology, or your application's risk tolerance changes. If you are standing up new infrastructure, start from Solana networks and the OnFinality Learn hub.

Next steps: operationalize your commitment policy

Write your commitment policy down as a table like the one above, then enforce it in code so no call site silently defaults. Centralize the confirmation gate, log confirmationStatus transitions, and alert when a finalized read disagrees with an earlier processed read.

Measure your own endpoint with the results table, keep the sample size with the numbers, and re-run after any provider or topology change. If you are evaluating managed access, review the API service and the Solana RPC endpoints (RPC Assistant) pages, and pair this guide with Solana RPC timeouts, retries and transaction sending so expiry and retry handling match your commitment choices.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started