Pending and stranded deposits
Finish a deposit Solana had not settled, keep it across a page reload, and return funds an older wallet left behind.
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.
import { } from "@umbra/nimbus-solana-web";
if (
. === "submitted" &&
... === "unknown"
) {
const = await (, {
: .,
});
}contextis the deposit context the deposit ran in, or a new one for the same wallet.submissionis thesubmittedresult'ssubmission, whose kind isunknown.
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:
function (: ): string {
switch (.) {
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:
function (: unknown): string {
return .(, (, : unknown) => {
if (typeof === "bigint") return { : .() };
if ( instanceof ) return { : .() };
return ;
});
}
function (: string): unknown {
return .(, (, : unknown) => {
if (typeof !== "object" || === null) return ;
if ("bigint" in && typeof . === "string") {
return (.);
}
if ("bytes" in && .(.)) {
return .(.);
}
return ;
});
}Save the pending deposit when deposit returns:
if (
. === "submitted" &&
... === "unknown"
) {
.(
"pending-deposit",
({
: .,
: ,
: 25_000_000n,
,
}),
);
}After the reload, open a session for the same wallet, then rebuild the context with
createDepositContext:
import {
,
,
} from "@umbra/nimbus-solana-web";
const = .("pending-deposit");
if ( !== null) {
const { } = () as {
: DepositSubmission;
};
const = await ();
const = await (, { });
if (. !== "unknown" && . !== "store-refused") {
.("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.
import {
,
,
} from "@umbra/nimbus-solana-web";
const = await (
await (),
{ : },
{ , : 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. |
function (: ): string {
switch (.) {
case "nothing-held":
return "There is nothing to refund.";
case "declined":
return "You declined the refund in your wallet.";
case "submitted":
switch (..) {
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 shows how to release it. A
deposit into a private balance with nothing of that token reserves nothing.
Next
Last updated