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

# Pending and stranded deposits (/deposit/pending)

Most deposits settle before `deposit` returns. This page covers the ones that do not: a deposit
Solana had not decided on yet, and funds an older wallet left waiting halfway.

In this guide, Alice deposits 25 USDC and the SDK stops waiting before Solana settles it.

## When a deposit is pending

`deposit` sends the transaction and waits for Solana to settle it. Sometimes the wait ends first.
The result is then `submitted`, and its submission's kind is `unknown`.

A pending deposit may still land. Its transaction stays valid until its blockhash expires. Until
you know how it ended, do not deposit the same funds again. If both landed, Alice would deposit
twice.

A deposit also combines up to three earlier amounts of the same token in Alice's private balance.
Those stay reserved while the deposit is pending, so her available balance can look lower.

## Resolve it

Call `resolvePendingDeposit` with the deposit's context and its submission. It waits for Solana to
decide, then tells the device how the deposit ended.

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

if (
  result.kind === "submitted" &&
  result.submission.submission.kind === "unknown"
) {
  const resolution = await resolvePendingDeposit(context, {
    submission: result.submission,
  });
}
```

- **`context`** is the deposit context the deposit ran in, or a new one for the same wallet.
- **`submission`** is the `submitted` result's `submission`, whose kind is `unknown`.

It accepts only a pending submission. Pass one that already settled, and it throws a
`NimbusSdkError` with reason `misconfigured` before it fetches anything.

| `kind`          | What happened                                                                          | What to do                                |
| --------------- | -------------------------------------------------------------------------------------- | ----------------------------------------- |
| `landed`        | The deposit went through. The balance shows it after the next sync.                    | Nothing more.                             |
| `failed`        | It reached Solana and failed. Nothing was deposited, and the reserved funds are freed. | Let Alice deposit again.                  |
| `expired`       | It can never land. Nothing was deposited, and the reserved funds are freed.            | Let Alice deposit again.                  |
| `unknown`       | Solana still has not decided.                                                          | Call it again in a few seconds.           |
| `store-refused` | This browser could not open Alice's private account data. Nothing was recorded.        | Ask her to reload the page and try again. |

TypeScript checks that this switch covers every case:

```ts
function describeResolution(resolution: PendingDepositResolutionResult): string {
  switch (resolution.kind) {
    case "landed":
      return "Deposited.";
    case "failed":
    case "expired":
      return "The deposit did not go through. You can deposit again.";
    case "unknown":
      return "The deposit is still pending.";
    case "store-refused":
      return "This browser could not record the deposit. Reload and try again.";
  }
}
```

## Survive a page reload

The session and the context live in memory, so a reload loses them. To finish the deposit after
one, store its submission as soon as `deposit` returns. Store the mint, amount and recipient too, so
your app can show what is pending.

A `DepositSubmission` holds `Uint8Array` and `bigint` values. `JSON.stringify` cannot write either,
so encode them first. These two functions tag each value and restore it:

```ts
function encodeForStorage(value: unknown): string {
  return JSON.stringify(value, (_key, item: unknown) => {
    if (typeof item === "bigint") return { bigint: item.toString() };
    if (item instanceof Uint8Array) return { bytes: Array.from(item) };
    return item;
  });
}

function decodeFromStorage(text: string): unknown {
  return JSON.parse(text, (_key, item: unknown) => {
    if (typeof item !== "object" || item === null) return item;
    if ("bigint" in item && typeof item.bigint === "string") {
      return BigInt(item.bigint);
    }
    if ("bytes" in item && Array.isArray(item.bytes)) {
      return Uint8Array.from(item.bytes);
    }
    return item;
  });
}
```

Save the pending deposit when `deposit` returns:

```ts
if (
  result.kind === "submitted" &&
  result.submission.submission.kind === "unknown"
) {
  localStorage.setItem(
    "pending-deposit",
    encodeForStorage({
      submission: result.submission,
      mint: usdc,
      amount: 25_000_000n,
      recipient,
    }),
  );
}
```

After the reload, open a session for the same wallet, then rebuild the context with
`createDepositContext`:

```ts
import {
  createDepositContext,
  resolvePendingDeposit,
} from "@umbra/nimbus-solana-web";

const stored = localStorage.getItem("pending-deposit");
if (stored !== null) {
  const { submission } = decodeFromStorage(stored) as {
    submission: DepositSubmission;
  };
  const context = await createDepositContext(session);
  const resolution = await resolvePendingDeposit(context, { submission });
  if (resolution.kind !== "unknown" && resolution.kind !== "store-refused") {
    localStorage.removeItem("pending-deposit");
  }
}
```

The session must belong to the wallet that made the deposit. The device's record of the deposit is
kept per wallet, and the submission points into it.

## Wallets that sign only version 0

Most wallets sign version 1 transactions, and a deposit is then one transaction. A wallet that
signs only version 0 needs two, and Alice approves both:

### Funding

Moves the tokens, and the SOL for the second transaction's fee, from Alice's wallet to her platform
wallet. Her platform wallet is a second Solana address the SDK derives for her from her signature.

### Deposit

Moves them from her platform wallet into the pool. Her platform wallet signs and pays for it.

When the two do not both go through, the result says how far it got:

| `kind`               | What happened                                                                                               | What to do                     |
| -------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------ |
| `funding-failed`     | The funding transaction did not land. Nothing moved.                                                        | Try again.                     |
| `funding-unknown`    | The funding transaction may still land. Nothing was deposited.                                              | Wait a minute, then try again. |
| `deposit-unsendable` | The funding landed, but the deposit failed its check before sending. The funds wait in the platform wallet. | Try again, or refund.          |

A `submitted` deposit whose transaction then failed or expired also leaves its funding in the
platform wallet. So does an `attempt-refused` result after a funding that landed.

To try again, call `deposit` with the same mint. It counts what the platform wallet already holds
and funds only the rest. Once a funding has settled, a retry never funds it twice.

To give the funds back instead, refund them.

## Refund a stranded deposit

`refundStrandedDeposit` returns everything the platform wallet holds of one token to Alice's wallet.
It also returns the SOL left there for fees. Alice's wallet signs and pays the network fee.

```ts
import {
  createDepositContext,
  refundStrandedDeposit,
} from "@umbra/nimbus-solana-web";

const refund = await refundStrandedDeposit(
  await createDepositContext(session),
  { mint: usdc },
  { transactionVersion, closeTokenAccount: true },
);
```

`closeTokenAccount: true` also closes the platform wallet's token account. That returns the rent
Solana held for it. A wrapped SOL account is always closed.

> **Refund only after every deposit settles**
>
> The refund takes whatever the platform wallet holds at that moment. If a deposit of the same token
> is still in flight, the refund can take the funds it was about to move.

| `kind`         | What happened                                                             | What to do                                                |
| -------------- | ------------------------------------------------------------------------- | --------------------------------------------------------- |
| `nothing-held` | The platform wallet holds none of the token and no SOL. Nothing was sent. | Nothing to refund.                                        |
| `declined`     | Alice rejected it in her wallet. Nothing was sent.                        | Let her try again.                                        |
| `submitted`    | The refund was sent. Only a `landed` submission returned the funds.       | If it `failed`, `expired` or was `refused`, run it again. |

```ts
function describeRefund(refund: StrandedDepositRefundResult): string {
  switch (refund.kind) {
    case "nothing-held":
      return "There is nothing to refund.";
    case "declined":
      return "You declined the refund in your wallet.";
    case "submitted":
      switch (refund.submission.kind) {
        case "landed":
          return "Refunded to your wallet.";
        case "failed":
        case "expired":
        case "refused":
          return "The refund did not go through. Try again.";
        case "unknown":
          return "The refund is still pending. Check again in a minute.";
      }
  }
}
```

## A stopped deposit that reserved funds

A deposit that combines earlier amounts reserves them while it runs. If it stops before it settles,
they stay reserved. That happens when the result is pending, or when the tab closes mid-deposit.

`listReservedSpends` shows such a deposit with kind `"deposit"`. If you still have its submission,
resolve it as above. If not, [Reserved funds](https://sdk.nimbusprivacy.xyz/helpers/reserved-funds) shows how to release it. A
deposit into a private balance with nothing of that token reserves nothing.

## Next

- [Deposit](https://sdk.nimbusprivacy.xyz/deposit): The deposit guide this page continues.
- [Reserved funds](https://sdk.nimbusprivacy.xyz/helpers/reserved-funds): List and release funds a stopped payment holds.
- [API reference](https://sdk.nimbusprivacy.xyz/reference/deposit): Every option of resolvePendingDeposit and refundStrandedDeposit.


Last updated 21 October 2026
