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

# Register a user (/registration)

Registering is a one-time Solana transaction. It creates Alice's identity, the public record of her
private account, and links her wallet to it.

Nobody can pay Alice privately until she registers. That includes deposits into her own private
balance, from her wallet or anyone else's.

In this guide, Alice checks whether she is registered, then registers from the wallet she connected.

## What's public

Her platform wallet is a second Solana address the SDK derives from her signature. It signs her
deposits alongside her wallet. Registering links the two wallets in public, so anyone can tell they
belong to one private account.

Registering reveals nothing about what Alice later holds or pays.
[What each operation shows](https://docs.nimbusprivacy.xyz/privacy/visibility) explains each line.

## Check first

`session.registration` says where Alice stood when the session opened. It never changes while the
session is open. To read where she stands now, call `readRegistration`:

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

if (session.registration.kind === "unregistered") {
  const standing = await readRegistration(session, {});
  if (standing.kind === "unregistered") showRegisterButton();
}
```

- **`session.registration`** is current when the session opens. Use it to decide what to show first.
- **`readRegistration`** asks Nimbus again. Use it after `register` lands, or when another tab may
  have registered.

## Register

Create a registration context from the session, then call `register`.

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

const context = await createRegistrationContext(session);
const result = await register(context, {}, { transactionVersion });
```

- **`{}`** is the arguments, and there are none. The session says who registers.
- **`transactionVersion`** is what the wallet can sign. A registration fits one transaction at
  either version. See [Connect a wallet](https://sdk.nimbusprivacy.xyz/helpers/wallets).
- **`createRegistrationContext`** picks the newest open pool. Pass `poolIndex` to choose another.

First the SDK builds a proof on the device and collects Nimbus's approval. That takes a few
seconds. Then Alice's wallet asks her to approve one transaction.

That transaction registers her and links her wallet in one step. She pays its network fee from her
wallet. Registering opens no new account on Solana, so there is no rent to pay.

Her wallet needs a little SOL for that fee. The SDK checks her balance before it builds the proof.
A wallet without enough throws at once, so she never waits for a proof that cannot be sent.

## Handle the result

`register` returns a result instead of throwing for anything your user can cause. Check its `kind`:

| `kind`          | What happened                                                           | What to do                                |
| --------------- | ----------------------------------------------------------------------- | ----------------------------------------- |
| `complete`      | Alice was registered already. Nothing was sent.                         | Carry on to her first deposit.            |
| `submitted`     | The registration was sent to Solana.                                    | Read where it ended up, below.            |
| `declined`      | Alice rejected it in her wallet. Nothing was sent.                      | Let her try again.                        |
| `store-refused` | This browser could not open her private account data. Nothing was sent. | Ask her to reload the page and try again. |

A `submitted` result carries the transaction's fate in `result.submission.submission.kind`:

| `kind`                 | What it means                                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `landed`               | Done. Alice is registered.                                                                                          |
| `failed`               | It reached Solana and failed. Only the network fee was charged.                                                     |
| `expired` or `refused` | It never reached Solana. Nothing changed, and it is safe to try again.                                              |
| `unknown`              | The SDK stopped waiting before Solana decided. See [A registration still settling](#a-registration-still-settling). |

Here is the whole check in one function. TypeScript checks that it covers every case.

```ts
function describeRegistration(result: RegistrationResult): string {
  switch (result.kind) {
    case "complete":
      return "You are registered.";
    case "submitted":
      switch (result.submission.submission.kind) {
        case "landed":
          return "You are registered.";
        case "failed":
          return "Registration failed on Solana. Only the network fee was charged.";
        case "expired":
        case "refused":
          return "Registration never reached Solana. Try again.";
        case "unknown":
          return "Registration is still pending.";
      }
    case "declined":
      return "You declined to register in your wallet.";
    case "store-refused":
      return "This browser could not open your account data. Reload and try again.";
  }
}
```

Alice is registered when the result is `complete`, or `submitted` with a `landed` submission. She
can deposit straight away.

## Show progress

Pass `onProgress` to tell Alice what is happening:

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

await register(context, {}, { onProgress: (event) => showStatus(event.stage) });
```

The stages are `prepared`, `endorsed`, `proved`, `signed`, `sent` and `confirmed`. `endorsed` and
`proved` can arrive in either order. A registration with nothing left to send stops after
`prepared`. [Show progress](https://sdk.nimbusprivacy.xyz/api-design/progress) has a message for each stage.

## Edge cases

### Already registered

`register` checks first and sends only what is missing. If Alice is registered and her wallet is
linked, it returns `complete`. Her wallet never asks her to sign.

### A registration still settling

A submission of kind `unknown` may still land until its blockhash expires. Do not call `register`
again straight away. Wait a minute, then call `readRegistration`. If it says `unregistered`, try
again.

A landed registration lets Alice deposit at once. Spending takes a little longer. Her private
balance can pay only once the registration is final on Solana and her device has synced. Until then,
a Private Send or a withdrawal is refused with `not-registered`.

### Two tabs registering at once

`register` holds a lock for Alice's wallet for the whole call. The lock is shared by every tab of
your site in one browser. A second tab waits for the first to finish, then checks again. If the
first tab's registration landed and Nimbus has seen it, the second returns `complete`.

## Common errors

These are thrown, not returned, because your app usually cannot fix them by asking the user.

| Error                   | Reason                   | Cause                                                                                                     |
| ----------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------- |
| `NimbusSolanaFlowError` | `binding-mismatched`     | Alice's wallet is already linked to a different platform wallet, such as one from another `accountIndex`. |
| `NimbusSolanaFlowError` | `standing-mismatched`    | A Nimbus service answered for another pool or a stale state. Try again later.                             |
| `NimbusSolanaFlowError` | `fee-payer-unfunded`     | Alice's wallet holds too little SOL for the network fee. Nothing was proved or sent. Ask her to add some. |
| `NimbusSolanaFlowError` | `transaction-unsendable` | The transaction failed its check before sending. Nothing was signed. Try again.                           |
| `NimbusSolanaFlowError` | `transaction-altered`    | The wallet changed the transaction it was asked to sign. Nothing was sent.                                |
| `NimbusSdkError`        | `service-unavailable`    | Solana or a Nimbus service could not be reached. Try again.                                               |
| `NimbusSdkError`        | `unauthorized`           | The session has no gateway token, or the gateway refused it.                                              |
| `NimbusSdkError`        | `wallet-unsupported`     | The wallet failed while signing.                                                                          |

[Handle errors](https://sdk.nimbusprivacy.xyz/api-design/results-and-errors) shows one function that covers every error the SDK throws.

## Options

| Option                | Default       | What it does                                                      |
| --------------------- | ------------- | ----------------------------------------------------------------- |
| `transactionVersion`  | `1`           | The transaction version the wallet signs.                         |
| `onProgress`          | none          | Called at each stage of the registration.                         |
| `commitment`          | `"confirmed"` | `"finalized"` waits until the registration cannot be rolled back. |
| `priorityFeeLamports` | `0n`          | A priority fee, in lamports, to land faster when Solana is busy.  |

The [API reference](https://sdk.nimbusprivacy.xyz/reference/registration#register) lists every option.

## Next

- [Deposit](https://sdk.nimbusprivacy.xyz/deposit): Alice's first payment into her private balance.
- [Open a session](https://sdk.nimbusprivacy.xyz/helpers/sessions): Where session.registration comes from.
- [Handle errors](https://sdk.nimbusprivacy.xyz/api-design/results-and-errors): Cover every result and error in one place.


Last updated 21 October 2026
