> For AI agents: the documentation index is at https://sdk.nimbusprivacy.xyz/llms.txt, and every page is available as Markdown at its URL with .md appended.

# Reserved funds (/helpers/reserved-funds)

While a payment runs, it holds the part of the private balance it spends. `readBalances` shows that
part as `reserved`. Most payments let go on their own when they finish.
[Reserved funds](https://sdk.nimbusprivacy.xyz/explainers/reserved-funds) explains why they hold it.

A payment that stopped midway can keep holding funds. In this guide, Alice closed her tab during a
Private Send to Bob. Her app lists what is still held and offers her a Release button.

## List what is held

```ts
import { listReservedSpends } from "@umbra/nimbus-solana-web";

const held = await listReservedSpends(session, {});

for (const spend of held) {
  console.log(spend.kind, spend.amount, spend.state, spend.running);
}
```

It reads only the device, so it is cheap to call after every sync. Each `ReservedSpend` has:

- **`id`**, which `releaseReservedNotes` takes.
- **`kind`**, what the payment is, below.
- **`mint`** and **`amount`**, what it moves, in the token's smallest unit. `mint` is absent for a
  token the SDK's preset does not list.
- **`state`**, where it stands, below.
- **`running`**, `true` while a tab of your app is still running it.
- **`attempts`**, how many times it has been tried.

| `kind`    | What the payment is                                                                                                 |
| --------- | ------------------------------------------------------------------------------------------------------------------- |
| `spend`   | A Private Send, a withdrawal, or a step that combines amounts before one. The SDK does not tell these apart.        |
| `escape`  | A withdrawal through the pool's escape hatch.                                                                       |
| `deposit` | A deposit that also combines amounts already in the private balance. Its `amount` is what it takes from the wallet. |

## Each state

| `state`     | What it means                                          | What Release does                                                            |
| ----------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `unsent`    | Nothing left the device.                               | Frees the funds at once.                                                     |
| `sent`      | It may have reached the relayer or Solana.             | Stops new attempts. The funds come back once it lands or can no longer land. |
| `releasing` | Already released, while a sent attempt may still land. | Nothing more. Wait for it to settle.                                         |

`running` is separate from `state`. A running payment is still in progress in some tab. Leave it
alone. It frees on its own when that tab finishes or closes.

If a `sent` payment lands, it completes. Bob receives his USDC, and Alice's funds are spent, not
returned. Say so beside the button.

## Release them

```ts
import { releaseReservedNotes } from "@umbra/nimbus-solana-web";

const one = await releaseReservedNotes(session, {}, { spendId });
const everything = await releaseReservedNotes(session, {});

console.log(everything.released.length, everything.settling.length);
```

Leave out `spendId` to release every held payment. The result sorts them by what happened:

| Field      | What happened                                                                       |
| ---------- | ----------------------------------------------------------------------------------- |
| `released` | The funds are available again now.                                                  |
| `settling` | An attempt may still land. The funds come back once it lands or can no longer land. |
| `running`  | A tab is still running them, so they were left alone.                               |

Releasing what is already released, or an id that names nothing, changes nothing. A release reads
the latest slot from your RPC. When it cannot be reached, it throws a `NimbusSdkError` with reason
`service-unavailable`.

## A Release button

This React component follows the example app's balances panel. It shows one held payment, hides the
button while the payment runs, and reports what the release did:

```tsx
import { releaseReservedNotes } from "@umbra/nimbus-solana-web";
import type {
  ReservedSpend,
  ReservedSpendState,
} from "@umbra/nimbus-solana-web";
import { useState } from "react";

const STATE_TEXT: Record<ReservedSpendState, string> = {
  unsent: "Stopped before it was sent. Release returns it now.",
  sent: "May have been sent. Releasing stops new attempts.",
  releasing: "Released. It returns once the sent attempt settles.",
};

export function HeldPayment({ spend }: { spend: ReservedSpend }) {
  const [message, setMessage] = useState("");

  async function release(): Promise<void> {
    const result = await releaseReservedNotes(
      session,
      {},
      { spendId: spend.id },
    );
    if (result.released.length > 0) {
      setMessage("Released. The funds are available again.");
    } else if (result.settling.length > 0) {
      setMessage("It may still land. The funds return once it settles.");
    } else if (result.running.length > 0) {
      setMessage("Another tab is still running it.");
    }
    await refreshBalances();
  }

  if (spend.running) {
    return <p>Still running in another tab. It frees when that tab finishes.</p>;
  }

  return (
    <div>
      <p>{STATE_TEXT[spend.state]}</p>
      {spend.state === "releasing" ? null : (
        <button type="button" onClick={() => void release()}>
          Release
        </button>
      )}
      <p role="status">{message}</p>
    </div>
  );
}
```

## Force a release

Pass `force: true` to release a payment a tab is still running. That payment then fails at its next
step.

Do not offer it as a normal button. Use it only when Alice says the other tab is gone, but the
browser still reports it running. A payment released mid-flight may already have reached the
relayer, so it can still land.

## Next

- [Why funds are reserved](https://sdk.nimbusprivacy.xyz/explainers/reserved-funds): What holds them, and when they come back.
- [Balances](https://sdk.nimbusprivacy.xyz/helpers/balances): Show reserved funds beside the rest.
- [Sync](https://sdk.nimbusprivacy.xyz/helpers/sync): A sync frees payments that stopped before sending.
- [API reference](https://sdk.nimbusprivacy.xyz/reference/reserved-funds): listReservedSpends and releaseReservedNotes.


Last updated 21 October 2026
