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

# Production checklist (/production/checklist)

Work through these before you ship. Each one says what to set up and how to check it in the
deployed app.

## Every page is cross-origin isolated

Both headers are on every route that loads the SDK, in production as well as in development.
[Turn on cross-origin isolation](https://sdk.nimbusprivacy.xyz/getting-started/setup#turn-on-cross-origin-isolation) has the
config for each host.

**How to check.** Open the deployed app and run `crossOriginIsolated` in the console. It must print
`true`. A CDN or proxy in front of your app can drop the headers, so check the live site.

## The SDK's files are pinned and current

Your install runs `nimbus-solana-web copy-assets`, so the served files match the installed SDK.
Your app refuses to run a build that does not check its files:

```ts
if (sdk.settings.assetPinning !== "pinned") {
  throw new Error("Refusing to run unchecked WebAssembly.");
}
```

**How to check.** `sdk.settings.assetPinning` reads `"pinned"` in the deployed app. A payment
then runs without a `dependency-nonconforming` error.

## The Content Security Policy allows the SDK

A strict policy is your best defence for the user's signature, which the default store keeps
readable by any script on your site. On top of your own rules, the SDK needs these:

```text
script-src 'self' 'wasm-unsafe-eval';
worker-src 'self';
connect-src 'self' <every origin the SDK fetches from>;
```

- **`'wasm-unsafe-eval'`** lets the page and the workers compile WebAssembly. It does not allow
  JavaScript `eval`.
- **`worker-src 'self'`** lets the SDK start its workers. The prover worker and the threads it
  starts are scripts on your own site, so `blob:` is not needed.
- **`connect-src`** lists every origin the SDK fetches from. Build it from the loaded SDK, so it
  follows the preset and your own endpoints:

```ts
const { endpoints, provingKeys } = sdk.settings;
const urls = [
  endpoints.rpc,
  endpoints.indexer,
  endpoints.context,
  endpoints.feeEnclave,
  endpoints.relayer,
  endpoints.coordinator,
  ...endpoints.guardians.map((node) => node.endpoint),
  ...endpoints.screeners.map((node) => node.endpoint),
  ...endpoints.dataEndorsers.map((node) => node.endpoint),
  provingKeys.baseUrl,
].filter((url) => url !== "");

const origins = new Set(urls.map((url) => new URL(url, location.href).origin));
console.log([...origins].join(" "));
```

Send the policy with the SDK's files too. A worker follows the policy its own script was served
with, not the page's.

**How to check.** Run a deposit and a withdrawal with the console open. No policy violation is
reported.

## The files are cached sensibly

The SDK's files keep the same names from one version to the next. Serve them with a short cache
lifetime. Or copy them into a folder named for the version, such as `/nimbus-0.1.0/`, and cache that
for as long as you like.

The SDK caches the larger circuit files itself, in IndexedDB. They need nothing from you.

**How to check.** Upgrade the SDK, deploy, and load the app in a browser that visited before. It
loads without a `dependency-nonconforming` error.

## The gateway token stays out of sight

Your app needs a gateway token for mainnet. To get one, email [dev@umbraprivacy.com](mailto:dev@umbraprivacy.com) or reach out to any Umbra
team member.

The SDK never logs the token or puts it in an error. Keep your own code the same:

- Keep the token out of your repository. Load it from your configuration at build or run time.
  [The mainnet gateway token](https://sdk.nimbusprivacy.xyz/getting-started/setup#the-mainnet-gateway-token) shows how to pass it.
- Never log request headers, or the options you pass to `openNimbusSession`.

**How to check.** Search your logs and your error reports for the token. It appears nowhere.

## You chose where the sign-in is kept

The signature that unlocks a private account can be kept for the tab, for the browser, or not at
all. The first two are readable by any script on your site. Pick one on purpose, and tell your
users. [Sessions](https://sdk.nimbusprivacy.xyz/helpers/sessions) compares them.

**How to check.** Find the `signatureSecretStore` you pass to `openNimbusSession`. Leaving it out
means the tab store.

## Errors reach your reports

Report what helps you fix a problem, and nothing secret:

- For a `NimbusSdkError`, report its `reason` and `message`. The message never holds the token.
- For a protocol refusal, `isNimbusSolanaFlowError` tells you it is one. Report its `reason`.
- For a result that is not a success, report its `kind`.
- Never report the session object. It holds the user's keys.

**How to check.** Load the app on a page without the isolation headers. Your reports show
`unsupported-browser` and the message.

## Balances update while the app is open

Payments sync the device on their own. Payments others make to your user arrive only with a sync.
Run one on an interval while a session is open, as
[Use it with React](https://sdk.nimbusprivacy.xyz/getting-started/react#sync-in-the-background) shows.

**How to check.** Leave the app open, and pay its user from another account. The balance updates
without a reload.

## Users can release stuck funds

A payment that stops halfway, because its tab closed or a step failed, keeps its funds reserved. A
sync frees one that never sent anything. Give users a button that calls `releaseReservedNotes` for
the rest.
[Reserved funds](https://sdk.nimbusprivacy.xyz/helpers/reserved-funds) shows the list and the button.

**How to check.** While `listReservedSpends` lists a payment, press the button. Its amount moves
from `reserved` back to `available` once nothing of it can still land.

## You tested the browsers you support

The SDK's own tests run in headless Chrome only. Test your app in every browser you support,
including on phones if you support them. [Browser support](https://sdk.nimbusprivacy.xyz/production/browser-support) lists what
each browser must offer.

**How to check.** Run a deposit, a Private Send and a withdrawal in each browser.

## Next

- [Endpoints](https://sdk.nimbusprivacy.xyz/production/endpoints): The mainnet endpoints, your own RPC and your own services.
- [Troubleshooting](https://sdk.nimbusprivacy.xyz/production/troubleshooting): Symptoms, causes and fixes.


Last updated 21 October 2026
