Skip to main content

Prerequisites

  • Node.js 18+ or a modern browser environment
  • A Solana wallet (or a generated keypair for testing)
  • An RPC endpoint - any standard Solana JSON-RPC URL works

1. Install

pnpm add @umbra-privacy/sdk

2. Create a Signer

For quick testing, generate an in-memory keypair. For production, see Wallet Adapters.
import { createInMemorySigner } from "@umbra-privacy/sdk";

// Generate a random keypair (for testing only)
const signer = await createInMemorySigner();
console.log("Wallet address:", signer.address);
An in-memory keypair is ephemeral - it disappears when your process exits. Use a browser wallet or a persistent keypair for anything beyond local testing.

3. Create the Umbra Client

import { getUmbraClientFromSigner } from "@umbra-privacy/sdk";

const client = await getUmbraClientFromSigner({
  signer,
  network: "mainnet",
  rpcUrl: "https://api.mainnet-beta.solana.com",
  rpcSubscriptionsUrl: "wss://api.mainnet-beta.solana.com",
  indexerApiEndpoint: "https://acqzie0a1h.execute-api.eu-central-1.amazonaws.com",
});
The Umbra program address is different on devnet and mainnet. The SDK resolves the correct address automatically based on the network parameter.
Client construction is synchronous and instant. The wallet signing prompt does not appear until an operation requires the master seed - typically at your first register() or deposit() call.

4. Register Your Account

Registration sets up your on-chain Umbra identity. This function can be called regardless of whether the user is already registered - it handles the full setup, including key rotation when keys have changed. That said, each call submits on-chain transactions with SOL costs, so in practice you should check whether the account is already registered before calling it.
import { getUserRegistrationFunction } from "@umbra-privacy/sdk";

const register = getUserRegistrationFunction({ client });

// This is where the wallet signing prompt appears for the first time.
// The user signs once to derive the master seed; subsequent operations
// reuse the cached seed without prompting again.
const signatures = await register({
  confidential: true, // enable encrypted balances
  anonymous: true,    // enable mixer / anonymous transfers
});

console.log(`Registered in ${signatures.length} transaction(s)`);

5. Deposit Tokens

Shield an SPL or Token-2022 token balance by moving it from your public wallet into an encrypted account.
import { getDirectDepositIntoEncryptedBalanceFunction } from "@umbra-privacy/sdk";

const deposit = getDirectDepositIntoEncryptedBalanceFunction({ client });

const MINT = "C6KsXC5aFhffHVnaHn6LxQzM3SJGmdW6mB6FWNbwJ2Kr";
const amount = 1_000_000n; // 1 token (6 decimals)

const signature = await deposit(
  client.signer.address, // deposit into your own encrypted account
  MINT,
  amount,
);

console.log("Deposit confirmed:", signature);

6. Withdraw Tokens

Move tokens back from your encrypted account to your public wallet.
import { getDirectWithdrawIntoPublicBalanceV3Function } from "@umbra-privacy/sdk";

const withdraw = getDirectWithdrawIntoPublicBalanceV3Function({ client });

const signature = await withdraw(
  client.signer.address, // destination address
  MINT,
  amount,
);

console.log("Withdrawal confirmed:", signature);

7. Create a Receiver-Claimable UTXO

Send tokens privately to a recipient by depositing them into the mixer. The recipient can later claim them with no on-chain link back to you as the sender.
UTXO creation requires a zkProver dependency for Groth16 proof generation. Install @umbra-privacy/web-zk-prover for the recommended browser-based prover - see ZK Provers for details.
import { getCreateReceiverClaimableUtxoFromPublicBalanceFunction } from "@umbra-privacy/sdk";
import { getReceiverClaimableUtxoFromPublicBalanceProver } from "@umbra-privacy/web-zk-prover";

const zkProver = getReceiverClaimableUtxoFromPublicBalanceProver();

const createUtxo = getCreateReceiverClaimableUtxoFromPublicBalanceFunction(
  { client },
  { zkProver },
);

const RECIPIENT = "RecipientWalletAddressHere";

const signatures = await createUtxo({
  destinationAddress: RECIPIENT,
  mint: MINT,
  amount,
});
console.log("UTXO created:", signatures[0]);

8. Fetch Claimable UTXOs

As the recipient, scan the Merkle tree for UTXOs addressed to your X25519 key. The SDK attempts to decrypt each ciphertext and returns only those belonging to you.
import { getFetchClaimableUtxosFunction } from "@umbra-privacy/sdk";

const fetchUtxos = getFetchClaimableUtxosFunction({ client });

// Scan tree 0 from the start - pass your last seen index to resume
const { receiver } = await fetchUtxos(0, 0);

console.log("Received UTXOs:", receiver.length);

9. Claim the UTXO

Present a ZK proof on-chain to burn the UTXO and receive the tokens. Claiming into an encrypted balance keeps the received amount private - no on-chain trace of who received what.
import {
  getClaimReceiverClaimableUtxoIntoEncryptedBalanceFunction,
  getUmbraRelayer,
} from "@umbra-privacy/sdk";
import { getClaimReceiverIntoEtaProver } from "@umbra-privacy/web-zk-prover";

const zkProver = getClaimReceiverIntoEtaProver();
const relayer = getUmbraRelayer({
  apiEndpoint: "https://6yn4ndrv2i.execute-api.eu-central-1.amazonaws.com",
});

const claim = getClaimReceiverClaimableUtxoIntoEncryptedBalanceFunction(
  { client },
  { zkProver, relayer },
);

const result = await claim([receiver[0]]);

console.log("Claimed into encrypted balance:", result.signatures);

Full Example

import {
  createInMemorySigner,
  getUmbraClientFromSigner,
  getUserRegistrationFunction,
  getDirectDepositIntoEncryptedBalanceFunction,
  getDirectWithdrawIntoPublicBalanceV3Function,
  getCreateReceiverClaimableUtxoFromPublicBalanceFunction,
  getFetchClaimableUtxosFunction,
  getClaimReceiverClaimableUtxoIntoEncryptedBalanceFunction,
  getUmbraRelayer,
} from "@umbra-privacy/sdk";
import {
  getReceiverClaimableUtxoFromPublicBalanceProver,
  getClaimReceiverIntoEtaProver,
} from "@umbra-privacy/web-zk-prover";

async function main() {
  // 1. Signer (use your wallet adapter in production)
  const signer = await createInMemorySigner();

  // 2. Client
  const client = await getUmbraClientFromSigner({
    signer,
    network: "mainnet",
    rpcUrl: "https://api.mainnet-beta.solana.com",
    rpcSubscriptionsUrl: "wss://api.mainnet-beta.solana.com",
    indexerApiEndpoint: "https://acqzie0a1h.execute-api.eu-central-1.amazonaws.com",
  });

  // 3. Register (safe to call regardless of prior registration state)
  const register = getUserRegistrationFunction({ client });
  await register({ confidential: true, anonymous: true });

  const MINT = "C6KsXC5aFhffHVnaHn6LxQzM3SJGmdW6mB6FWNbwJ2Kr";

  // 4. Deposit into encrypted balance
  const deposit = getDirectDepositIntoEncryptedBalanceFunction({ client });
  await deposit(signer.address, MINT, 1_000_000n);

  // 5. Withdraw back to public wallet
  const withdraw = getDirectWithdrawIntoPublicBalanceV3Function({ client });
  await withdraw(signer.address, MINT, 1_000_000n);

  // 6. Set up ZK provers
  const utxoProver = getReceiverClaimableUtxoFromPublicBalanceProver();
  const claimProver = getClaimReceiverIntoEtaProver();

  // 7. Create a receiver-claimable UTXO
  const createUtxo = getCreateReceiverClaimableUtxoFromPublicBalanceFunction(
    { client },
    { zkProver: utxoProver },
  );
  const RECIPIENT = "RecipientWalletAddressHere";
  await createUtxo({
    destinationAddress: RECIPIENT,
    mint: MINT,
    amount: 500_000n,
  });

  // 8. Fetch UTXOs addressed to this wallet (as the recipient)
  const fetchUtxos = getFetchClaimableUtxosFunction({ client });
  const { receiver } = await fetchUtxos(0, 0);

  // 9. Claim the first received UTXO into an encrypted balance
  const relayer = getUmbraRelayer({
    apiEndpoint: "https://6yn4ndrv2i.execute-api.eu-central-1.amazonaws.com",
  });

  if (receiver.length > 0) {
    const claim = getClaimReceiverClaimableUtxoIntoEncryptedBalanceFunction(
      { client },
      { zkProver: claimProver, relayer },
    );
    const result = await claim([receiver[0]]);
    console.log("Claimed:", result.signatures);
  }
}

main().catch(console.error);

Next Steps

Wallet Adapters

Connect Phantom, Solflare, or any Solana wallet.

Mixer / UTXOs

Make transfers fully anonymous using the mixer.

Query State

Read encrypted balances and account state on-chain.

Error Handling

Handle deposit and withdrawal failures gracefully.