Skip to content
Explainers

Tabs and reserved funds

Why a running payment reserves the notes it spends, what keeps them reserved, and what frees them.

On Solana, if two transactions need the same lamports, only one succeeds. A private balance has the same rule. A payment that loses that race wastes a proof and your user's time, so the SDK stops it before it starts.

Two tabs, one balance

Every tab of your app shares one copy of the private balance. Two tabs could pick the same notes for two payments at once.

To prevent that, a payment reserves its notes before it builds anything. Another payment, in this tab or another, chooses among the notes left. readBalances counts reserved notes as reserved.

A payment reserves every note it spends, change included. Paying 10 USDC from three notes worth 25 USDC reserves all 25.

Each running payment also holds a Web Lock named for it. The browser drops the lock when the tab closes or crashes. That is how the SDK tells a running payment from a stopped one.

A running payment keeps its notes until it finishes. A stopped one keeps them until something frees them, as below.

Where a reserved payment stands

listReservedSpends lists every payment holding notes. Each has a state, and a running flag that says whether some tab still holds its lock.

stateWhat it meansWhat releasing it does
unsentNothing has left the device.Frees its notes at once.
sentIt may have reached the relayer or Solana.Stops new attempts. The notes free once it lands or can no longer land.
releasingReleased while a sent attempt may still land.Nothing more. It is already waiting.

A sent payment keeps its notes until it settles. Spending them again meanwhile could race it.

A deposit can reserve notes too. A deposit merges up to three of your user's notes of that token into its change, and reserves them until it settles. It is listed with kind: "deposit".

What frees reserved funds

  • Finishing. A payment that lands spends its notes. Once a sync sees that, they leave the balance.
  • The next sync, if the tab closed before sending anything. The payment is unsent and no tab holds its lock.
  • Your app's release button. releaseReservedNotes releases what is not running. With force: true it releases a running payment too, which then fails at its next step.
  • A relayer's refusal. A payment that comes back relay-refused never reached Solana, so its notes free at once.
  • Time, for a sent payment that can no longer land. After a relay comes back failed, its notes stay reserved until its proof's window passes.

What this means for your app

  • Show reserved funds apart from the available balance, so the numbers add up.
  • Offer a release button for payments that are not running.
  • Expect a quick retry to fail after failed. Its notes may still be reserved. A retry after relay-refused can spend them at once.

Last updated

On this page