Skip to content
Helpers

Reserved funds

List the payments holding part of the private balance, and give the user a button that releases the stopped ones.

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 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

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

const  = await (, {});

for (const  of ) {
  .(., ., ., .);
}

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.
kindWhat the payment is
spendA Private Send, a withdrawal, or a step that combines amounts before one. The SDK does not tell these apart.
escapeA withdrawal through the pool's escape hatch.
depositA deposit that also combines amounts already in the private balance. Its amount is what it takes from the wallet.

Each state

stateWhat it meansWhat Release does
unsentNothing left the device.Frees the funds at once.
sentIt may have reached the relayer or Solana.Stops new attempts. The funds come back once it lands or can no longer land.
releasingAlready 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

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

const  = await (, {}, {  });
const  = await (, {});

.(.., ..);

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

FieldWhat happened
releasedThe funds are available again now.
settlingAn attempt may still land. The funds come back once it lands or can no longer land.
runningA 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:

import {  } from "@umbra/nimbus-solana-web";
import type {
  ReservedSpend,
  ,
} from "@umbra/nimbus-solana-web";
import {  } from "react";

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

export function ({  }: { : ReservedSpend }) {
  const [, ] = ("");

  async function (): <void> {
    const  = await (
      ,
      {},
      { : . },
    );
    if (.. > 0) {
      ("Released. The funds are available again.");
    } else if (.. > 0) {
      ("It may still land. The funds return once it settles.");
    } else if (.. > 0) {
      ("Another tab is still running it.");
    }
    await ();
  }

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

  return (
    <>
      <>{[.]}</>
      {. === "releasing" ? null : (
        < ="button" ={() => void ()}>
          Release
        </>
      )}
      < ="status">{}</>
    </>
  );
}

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

Last updated

On this page