Results and errors
A call can end badly in three ways. Learn which to expect where, and turn every error into a message your user understands.
A call that does not succeed tells you why in one of three ways. Each needs different handling, so it helps to know which to expect.
The three ways
| Way | How you see it | What causes it |
|---|---|---|
| A result | Returned, with a kind | Your user, their balance, or Solana's answer |
NimbusSdkError | Thrown | The browser, your setup, or a service that cannot be reached |
NimbusSolanaFlowError | Thrown | The protocol's rules refuse what was asked |
The rule is simple:
- If your user caused it, or trying again could fix it, it is returned. A declined signature, a
balance that is too small and a transaction that expired are all results. Handle them where you
make the call, with a
switchonkind. - If the SDK cannot go on, it throws a
NimbusSdkError. Itsreasonsays whether to fix your setup, try again later or ask for another wallet. - If the protocol refuses, it throws a
NimbusSolanaFlowError. The token is not supported, or the amount cannot cover its fees. Change what you ask for.
Compare the reason, never the message. Test with isNimbusSdkError and isNimbusSolanaFlowError
rather than instanceof, so the test still works across bundles.
One function for every error
Wrap each call in try, and pass whatever it throws to one function. TypeScript checks that every
reason has a message, so a new reason in the SDK stops this from compiling.
import {
,
,
} from "@umbra/nimbus-solana-web";
function (: ): string {
switch () {
case "misconfigured":
return "This app is set up wrongly. Please tell us.";
case "unreadable":
return "A service sent something unexpected. Try again.";
case "unsupported-browser":
return "This browser cannot make private payments. Try another one, outside private mode.";
case "wallet-unsupported":
return "This wallet cannot be used here. Connect another wallet.";
case "service-unavailable":
return "The network cannot be reached right now. Try again shortly.";
case "unauthorized":
return "Your session was not accepted. Reload the page and sign in again.";
case "dependency-nonconforming":
return "A downloaded file failed its check, so it was not used. Reload the page.";
case "recipient-unregistered":
return "The recipient has not set up a private account yet.";
case "account-unregistered":
return "Set up your private account before you pay.";
}
}
function (: ): string {
switch () {
case "amount-unfundable":
return "That amount cannot cover its fees. Try a larger amount.";
case "mint-unsupported":
return "This token cannot be used. Choose another.";
case "fee-changing":
return "This token's fee is about to change. Try again in a few minutes.";
case "context-mismatched":
return "The payment could not be prepared. Try again.";
case "payout-unpayable":
return "That address cannot receive this token. Choose another address.";
case "relay-unavailable":
return "The relayer cannot carry this payment. Try another token or amount.";
case "binding-mismatched":
return "This wallet is linked to a different private account.";
case "signature-invalid":
case "transaction-altered":
return "Your wallet changed what it was asked to sign. Try again, or use another wallet.";
case "transaction-unsendable":
return "Solana would not accept this transaction. Try again.";
case "fee-payer-unfunded":
return "Your wallet needs a little SOL to pay the network fee. Add some and try again.";
case "committee-mismatched":
case "standing-mismatched":
case "absence-unproven":
return "A Nimbus service gave an answer that did not check out. Try again.";
case "relay-mismatched":
return "The relayer sent something unexpected. The payment may still land.";
case "zone-unauthorised":
return "This payment was not authorised. Contact the app's operator.";
case "message-altered":
case "deployment-invalid":
case "proof-mismatched":
return "This app is set up wrongly. Please tell us.";
}
}
export function (: unknown): string {
if (()) {
return (.);
}
if (()) {
return (.);
}
return "Something went wrong. Try again.";
}Log the error's own message beside the message you show. It names what failed, for you to debug.
A party who has not registered
Paying a recipient who has not registered throws a NimbusSdkError with reason
recipient-unregistered. Paying from an account that has not registered throws one with reason
account-unregistered. Either is thrown before the wallet is asked to sign, and nothing is sent.
A registration that has only just landed counts once Nimbus's indexer reaches it. Ask the user to wait a moment and try again.
NimbusSdkError reasons
The SDK throws a NimbusSdkError when it cannot go on. Each reason comes from the SDK's own
description:
| Reason | What it means |
|---|---|
misconfigured | the cluster's preset leaves an endpoint or the proving keys unconfigured and the options did not supply them, or a configured value or an argument is unusable: an assetsBaseUrl that is not a directory URL, no prover worker, no endpoint for the committee node a context names, an address that is not base58, an option outside its range, a mint identifier shorter than 32 bytes, a deposit submission whose outcome is settled already, a gateway token that is not a bearer token, or no open pool. The message names each one; pass it in loadNimbusSdk's options or fix the argument. Not recoverable by retrying. |
unreadable | a party answered, or the browser's storage held, something that cannot be read: a malformed service answer, a note store another deployment or a newer schema wrote, or a note store another tab replaced or kept writing to while it was in use. Retrying the same party is unlikely to help, though a call that met a note store being replaced may succeed when made again, since each call opens the store afresh. |
unsupported-browser | the browser lacks a global the SDK needs (fetch, Web Crypto, navigator.locks, navigator.storage, IndexedDB, IDBKeyRange, WebAssembly), or refused to open its storage, as a private window may; or the page is not cross-origin isolated, which the server fixes by sending Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Not recoverable in this browser or on this page. |
wallet-unsupported | the wallet cannot connect, sign messages and sign transactions on the cluster's chain, its account's address is not its public key's, or it failed while connecting or signing, signed other bytes than it was given, or answered a signature that does not verify. A user who declines is not this error: the call answers declined. Ask the user to connect another wallet. |
service-unavailable | a service, the RPC or the artifact host could not be reached or answered a failing status. Retry later. |
unauthorized | a gateway service answered 401 or 403, because the session was opened without a gateway token or the gateway refused it, or the session's token issuer failed. The message names the service and never the token. Open a session with a token the gateway accepts, or have the issuer return a fresh one. |
dependency-nonconforming | a dependency answered against its contract: a proving key or witness calculator that is not the one the manifest and the deployed program pin, a prover that refused the statement, a file of this package's assets/ whose SHA-256 is not the one the package pins, or primitives that fail their known-answer checks. The value must not be used; retrying will not help, and serving the assets/ packed with this copy of the package fixes a mismatched file. |
recipient-unregistered | the context API answered that the wallet a deposit or a transfer pays has no registration in the pool, so no note can be sealed to it. The message carries the API's detail. Nothing was signed or sent. Ask the recipient to register, or pay another wallet; a registration that has only just landed is seen once the indexer reaches it. |
account-unregistered | the context API answered that the session's own identity, or for a withdrawal the connected wallet, has no registration in the pool. The message carries the API's detail. Nothing was signed or sent. Run register first; a registration that has only just landed is seen once the indexer reaches it. |
And here is what to tell your user for each:
reason | Tell your user | What you do |
|---|---|---|
misconfigured | Something is wrong on our side. | Fix the option or argument the message names. Retrying will not help. |
unreadable | Something unexpected happened. Try again. | Retry once. A replaced browser store can succeed on the next call. |
unsupported-browser | Try another browser, outside private mode. | Check the page is cross-origin isolated. |
wallet-unsupported | Connect another wallet. | Nothing. A user who declines gets a declined result instead. |
service-unavailable | Try again shortly. | Retry later. |
unauthorized | Sign in again. | Open the session with a gateway token the gateway accepts. |
dependency-nonconforming | Reload the page. | Serve the assets/ that came with this version of the package. |
recipient-unregistered | The recipient has not set up a private account yet. | Ask them to register, or pay someone else. |
account-unregistered | Set up your private account first. | Run register, then try again. |
NimbusSolanaFlowError reasons
A payment throws a NimbusSolanaFlowError when the protocol refuses it. Most mean the payment cannot
succeed as asked. A few mean a service's answer did not check out, and a retry may help.
reason | Cause | What to do |
|---|---|---|
amount-unfundable | The amount cannot settle: nothing reaches the pool, or the fees take it all. | Choose another amount or fee mode. |
mint-unsupported | The address is not an SPL Token or Token-2022 mint. | Choose another token. |
fee-changing | The token's Token-2022 transfer fee changes at the next epoch. | Try again once the epoch turns. |
context-mismatched | The context service's answer does not fit the payment. | Try again. If it persists, the service is wrong. |
payout-unpayable | The withdrawal's destination cannot receive this token. | Name another destination. |
relay-unavailable | The relayer does not carry this payment: its token, its fee or its destination. | Change the payment, or use another relayer. |
binding-mismatched | The wallet is already linked to another private account, such as one at another accountIndex. | Open the session with the matching accountIndex. |
standing-mismatched | The context service's answer does not fit the registration. | Try again later. |
absence-unproven | A proof the context service served does not check out. | Try again. If it persists, the service is wrong. |
committee-mismatched | A committee service refused, or signed the wrong thing. Nothing was sent. | Try again. If it persists, report it. |
transaction-altered | The wallet changed the transaction it was asked to sign. Nothing was sent. | Ask again, or use another wallet. |
transaction-unsendable | The transaction is too large, or its simulation failed. Nothing was sent. | Retry. A failed simulation may pass, a size will not. |
fee-payer-unfunded | The wallet holds too little SOL to pay for the transaction it signs. Nothing was proved or sent. | Ask your user to add SOL. detail.requiredLamports says how much it needs. |
relay-mismatched | The relayer accepted something other than what was built. | Sync to learn whether it landed. Its funds stay reserved until then. |
zone-unauthorised | The signer for a non-default zone refused. | Ask that zone's operator, or use the default zone. |
proof-mismatched | The prover proved something other than what was prepared. | Retrying will not help. Report it. |
deployment-invalid | The deployment settings contradict themselves. | Fix the configuration. |
message-altered | The sign-in message in this build is not the pinned one. | The build is wrong. Reinstall the package. |
signature-invalid | The wallet's sign-in signature does not verify. | Ask again. A wallet that wraps the message is unsupported. |
openNimbusSession reports message-altered and signature-invalid as a NimbusSdkError with
reason wallet-unsupported. So you seldom see those two directly.
Refusals inside results
Some results report that this browser's own storage refused a step. Nothing was sent when that happens:
depositcan returnstore-refused,intent-refusedorattempt-refused.withdrawandtransfercan returnrefusedwithstore-refused,lease-refused,attempt-refused,stale-handleorstore-unavailable. They can also returnunsent.registerandresolvePendingDepositcan returnstore-refused.
These are rare. They happen when another tab of your app took the same funds a moment earlier, or replaced the browser's store, or when the browser blocks its storage. Ask your user to reload and try again.
Last updated