> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.umbraprivacy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Registration

> getUserRegistrationFunction: idempotent flow that creates the EncryptedUserAccount, registers the X25519 encryption key (confidential), and stores the user commitment via MPC + ZK (anonymous).

## Overview

Before a user can deposit into an EncryptedTokenAccount or interact with the Stealth Pool, they need an on-chain **Umbra user account**. Registration creates this account and optionally registers the cryptographic keys needed for ETAs and anonymous transfers.

Registration is idempotent — it checks on-chain state before each step and skips any sub-steps already completed (handling key rotation when keys have changed). Each call may submit on-chain transactions with SOL costs, so in practice you should [check whether the account is already registered](./account-state) before calling it.

## Usage

```typescript theme={null}
import { getUserRegistrationFunction } from "@umbra-privacy/sdk/registration";

const register = getUserRegistrationFunction({ client });

const signatures = await register({
  confidential: true, // register the X25519 key (Shared ETAs + receiver-burnable notes)
  anonymous: true,    // register the user commitment (Stealth Pool transfers as the recipient)
});

console.log(`Completed ${signatures.length} registration transaction(s)`);
// 0 if already fully registered.
// 1–2 depending on which sub-steps were needed (registration splits into two independent on-chain instructions).
```

## Options

<ParamField path="confidential" type="boolean" default="true">
  Register the X25519 token-encryption pubkey for Shared-mode ETAs. Without this, deposits stay in MXE-only mode and you cannot query your balance locally.
</ParamField>

<ParamField path="anonymous" type="boolean" default="true">
  Register the on-chain user commitment via MPC + ZK. Required to receive **receiver-burnable** Stealth Pool Notes. Can be used independently of `confidential`.
</ParamField>

<ParamField path="hooks" type="RegistrationHooks">
  Per-step lifecycle hooks plus top-level phase hooks. Skippable step slots take `{ onPreSend, onPostSend, onSkipped }`.

  ```typescript theme={null}
  await register({
    confidential: true,
    anonymous: true,
    hooks: {
      // Top-level phase hooks
      onValidationStart:    async () => console.log("Validating..."),
      onAccountFetchComplete: async ({ userAccountExists, hasX25519Key, isAnonymous }) =>
        console.log("Account state:", { userAccountExists, hasX25519Key, isAnonymous }),
      onKeyDerivationComplete: async ({ elapsedMs }) => console.log("Keys ready in", elapsedMs, "ms"),

      // Per-step hooks (skippable; onSkipped fires when state is already correct)
      initUserAccount: {
        onSkipped: async ({ reason }) => console.log("Account already exists:", reason),
        onPreSend: async ({ signedTransaction }) => console.log("Creating account..."),
        onPostSend: async ({ signature }) => console.log("Account created:", signature),
      },
      registerX25519PublicKey: {
        onSkipped: async ({ reason }) => console.log("X25519 key already registered:", reason),
        onPreSend: async () => console.log("Registering X25519 key..."),
        onPostSend: async ({ signature }) => console.log("X25519 registered:", signature),
      },
      registerAnonymousUsage: {
        onSkipped: async ({ reason }) => console.log("Anonymous usage already enabled:", reason),
        onPreSend: async () => console.log("Registering user commitment..."),
        onPostSend: async ({ signature }) => console.log("Commitment registered:", signature),
      },

      // Terminal
      onComplete: async (result) => console.log("Registration complete:", result),
      onError:    async ({ phase, error }) => console.error("Failed in phase", phase, ":", error),
    },
  });
  ```
</ParamField>

<Note>
  The hook slot names are **`initUserAccount`, `registerX25519PublicKey`, `registerAnonymousUsage`**. Each is `SkippableDirectTransactionStepHooks` or `SkippableMpcTransactionStepHooks` — `{ onPreSend, onPostSend, onSkipped }`.
</Note>

## The registration flow

Registration is **two independent on-chain instructions**. Both `init_if_needed` the `EncryptedUserAccount` PDA, so either can run first.

<Steps>
  <Step title="register_user_for_confidential_usage">
    Synchronous. Initialises the `EncryptedUserAccount` PDA (if needed) and stores the X25519 token-encryption pubkey derived from your master seed. Enables **Shared encryption mode** — deposits get encrypted under both the Arcium MPC key and your key, so you can decrypt your own balance locally.

    Run when `confidential: true`.
  </Step>

  <Step title="register_user_for_anonymous_usage_v18">
    MPC + ZK. Initialises the user account (if needed), stages master-viewing-key material, runs Fiat-Shamir + [Groth16](https://eprint.iacr.org/2016/260.pdf), queues the Arcium MPC computation, and on callback stores the on-chain user commitment. Enables you to **receive** receiver-burnable Stealth Pool Notes.

    Run when `anonymous: true`.
  </Step>
</Steps>

<Note>
  Both sub-steps require the master seed to be derived (overridable via [dependency injection](/sdk/understanding-the-sdk/dependency-injection#master-seed-storage)). The wallet signing prompt fires the first time the seed is needed if it has not been derived yet in this session.
</Note>

## On-Chain Account

After registration, the user's `EncryptedUserAccount` PDA stores:

* X25519 public key (for Shared mode deposits addressed to this user)
* Poseidon user commitment (for ZK proof generation)
* Generation index (monotonic counter used for nonce derivation)
* Random generation seed (entropy mixed into nonces)
* Status flags for each registration step

This account is the persistent on-chain identity for the wallet address. It does not hold any tokens.

## Error Handling

Registration can fail at several distinct points. Use `isRegistrationError` from `@umbra-privacy/sdk/errors` and `switch` on `err.stage` to handle each one.

```typescript theme={null}
import { isRegistrationError } from "@umbra-privacy/sdk/errors";

try {
  await register({ confidential: true, anonymous: true });
} catch (err) {
  if (isRegistrationError(err)) {
    switch (err.stage) {
      case "master-seed-derivation":
        // User declined to sign the master seed derivation message.
        // Without the seed, no keys can be derived and registration cannot proceed.
        showNotification("Please sign the master seed message to set up your account.");
        break;

      case "transaction-sign":
        // User rejected one of the registration transactions in their wallet.
        showNotification("Registration cancelled.");
        break;

      case "zk-proof-generation":
        // ZK proof generation failed. Only applies to the commitment step (anonymous: true).
        console.error("Proof generation failed:", err.message);
        showNotification("Failed to generate proof. Please try again.");
        break;

      case "account-fetch":
        // RPC connectivity issue while fetching on-chain account state.
        console.error("RPC error during registration:", err.message);
        break;

      case "transaction-send":
        // Transaction submitted but confirmation timed out.
        // The transaction may have landed - check on-chain before retrying.
        console.warn("Confirmation timeout. Check on-chain before retrying.");
        break;

      default:
        console.error("Registration failed at stage:", err.stage, err);
    }
  } else {
    throw err;
  }
}
```

**Partial completion**: Registration submits up to two separate transactions. If the second transaction fails or is cancelled, the first is already confirmed on-chain. Call `register()` again — it checks on-chain state and skips the sub-step that already completed.

See [Error Handling](/reference/errors) for a full reference of all error types.
