Skip to main content

Checking Registration Status

Use getUserAccountQuerierFunction from @umbra-privacy/sdk/query to inspect an account’s current registration state without triggering any transactions:
import { getUserAccountQuerierFunction } from "@umbra-privacy/sdk/query";

const query = getUserAccountQuerierFunction({ client });

const result = await query(client.signer.address);

if (result.state === "non_existent") {
  console.log("Not registered — call register() first");
} else {
  const { data } = result;
  console.log("Account initialised:",        data.isInitialised);
  console.log("X25519 key registered:",       data.isUserAccountX25519KeyRegistered);
  console.log("User commitment registered:",  data.isUserCommitmentRegistered);
  console.log("Anonymous usage active:",      data.isActiveForAnonymousUsage);
}

When to Register

Register once, at account setup time. Check state before calling register() so you avoid unnecessary transaction prompts for users who are already fully set up:
import { getUserAccountQuerierFunction } from "@umbra-privacy/sdk/query";
import { getUserRegistrationFunction } from "@umbra-privacy/sdk/registration";

const query = getUserAccountQuerierFunction({ client });
const result = await query(client.signer.address);

const isFullyRegistered =
  result.state === "exists" &&
  result.data.isInitialised &&
  result.data.isUserAccountX25519KeyRegistered &&
  result.data.isUserCommitmentRegistered;

if (!isFullyRegistered) {
  const register = getUserRegistrationFunction({ client });
  await register({ confidential: true, anonymous: true });
}

Pre-check the recipient before a receiver-burnable create

This is the most common integration footgun. A receiver-burnable Stealth Pool Note encrypts the unlocker against the recipient’s userCommitment, so the recipient must have all three flags (isInitialised, isUserAccountX25519KeyRegistered, isUserCommitmentRegistered) set before you write the note. Pre-check at the API boundary and either error with a clear message or fall back to a self-burnable create:
const r = await query(recipientAddress);
const ready = r.state === "exists"
  && r.data?.isInitialised
  && r.data?.isUserAccountX25519KeyRegistered
  && r.data?.isUserCommitmentRegistered;

if (!ready) {
  // Either ask the recipient to register, or use the self-burnable creator
  // (sender keeps the unlocker; recipient needs zero on-chain state).
}

Registration state fields

The data object returned when result.state === "exists":
  • isInitialised — the base EncryptedUserAccount PDA has been created.
  • isUserAccountX25519KeyRegistered — the X25519 token-encryption pubkey has been stored on-chain (Confidential-usage sub-step complete). Required for Shared-mode ETAs.
  • isUserCommitmentRegistered — the Poseidon user commitment has been stored (Anonymous-usage sub-step complete). Required to receive receiver-burnable notes.
  • isActiveForAnonymousUsage — the account is active and cleared for anonymous usage (both sub-steps complete and valid).
  • x25519PublicKey — the registered X25519 public key bytes, if the confidential sub-step is complete.
  • userCommitment — the registered Poseidon commitment, if the anonymous sub-step is complete.
  • generationIndex — monotonic counter used for nonce derivation. Use this to derive the next note nonce — never invent your own.
  • randomGenerationSeed — entropy bytes mixed into nonces.
For the full type reference see Query.