SDK integration map

Every Umbra primitive Stipend uses

Six recipes that cover the entire comp lifecycle — from shielding the treasury to producing a tax-ready CSV. Every snippet is a direct call into @umbra-privacy/sdk@4.

Factory pattern

get[Source]To[Target][Verb]Function({ client }) → returns a typed callable. Stipend composes six of these.

Viewing keys

Hierarchical TVKs derived from a single MVK via Poseidon — the root unlock for fiscal-year reporting.

Gasless relayer

Contributors claim without holding SOL — Umbra's relayer pays network fees from the encrypted amount.

1 · Client bootstrap & user registration

Run once on wallet connect. Idempotent on-chain.

getUmbraClientgetUserRegistrationFunction
import {
  getUmbraClient,
  getUserRegistrationFunction,
} from "@umbra-privacy/sdk";

const client = await getUmbraClient({
  signer,                         // wallet-adapter or in-memory
  network: "devnet",
  rpcUrl: "https://api.devnet.solana.com",
  rpcSubscriptionsUrl: "wss://api.devnet.solana.com",
  indexerApiEndpoint:
    "https://utxo-indexer.api-devnet.umbraprivacy.com",
});

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

2 · Employer shields treasury

Employer deposits USDC into the encrypted treasury ETA.

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

const deposit =
  getPublicBalanceToEncryptedBalanceDirectDepositorFunction({ client });

// Stipend calls this when the employer clicks "Shield into ETA".
await deposit(employerAddress, USDC_MINT, 25_000_000_000n);

3 · Scheduler fires a private disbursement

Cron fires on each stream's cadence. Umbra mints a receiver-claimable UTXO.

getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction@umbra-privacy/web-zk-prover
import { getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction }
  from "@umbra-privacy/sdk";
import { getCreateReceiverClaimableUtxoFromEncryptedBalanceProver }
  from "@umbra-privacy/web-zk-prover";

const zkProver =
  getCreateReceiverClaimableUtxoFromEncryptedBalanceProver();

const disburse =
  getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction(
    { client },
    { zkProver },
  );

// For each stream that is due:
await disburse({
  mint: stream.tokenMint,
  amount: stream.amountPerPeriod,
  recipientStealthAddress: contributor.stealthAddress,
  memo: stream.memo,                 // encrypted payload
});
// → a single UTXO leaf added to the Umbra mixer Merkle tree.

4 · Contributor scans & claims

Contributor opens Stipend. We scan the stealth pool and claim pending UTXOs.

getClaimableUtxoScannerFunctiongetReceiverClaimableUtxoToEncryptedBalanceClaimerFunction
import {
  getClaimableUtxoScannerFunction,
  getReceiverClaimableUtxoToEncryptedBalanceClaimerFunction,
} from "@umbra-privacy/sdk";

const scan = getClaimableUtxoScannerFunction({ client });
const pending = await scan(contributor.stealthAddress);

const claim =
  getReceiverClaimableUtxoToEncryptedBalanceClaimerFunction({ client });

for (const utxo of pending) {
  await claim(utxo, {
    // Umbra relayer covers the network fee — contributor
    // does NOT need to hold SOL.
    useRelayer: true,
  });
}

5 · Contributor issues a compliance grant

Contributor hands their accountant a scoped viewing key.

getComplianceGrantIssuerFunctiongetMasterViewingKeyDeriver
import {
  getComplianceGrantIssuerFunction,
} from "@umbra-privacy/sdk";
import { getMasterViewingKeyDeriver }
  from "@umbra-privacy/sdk/crypto";

const mvkDeriver = getMasterViewingKeyDeriver({ client });
const mvk = await mvkDeriver();

// Poseidon-hash chain → fiscal-year Transaction Viewing Key
const fy2026Tvk = mvk.deriveYear(2026);

const issueGrant = getComplianceGrantIssuerFunction({ client });
await issueGrant({
  scopeTvk: fy2026Tvk,
  grantee: accountantX25519PubKey,
  windowFrom: "2026-01-01",
  windowTo:   "2026-12-31",
});

6 · Accountant decrypts the authorized window

Accountant opens Stipend and clicks ‘Decrypt grant’.

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

const reencrypt =
  getSharedCiphertextReencryptorForUserGrantFunction({ client });

const reencryptedRows = await reencrypt({
  grantId,
  granteeX25519: accountantX25519PubKey,
});
// Rows now decrypt locally with the accountant's X25519 private
// key and are rendered + exported as CSV (see /accountant).

Where to find the live wiring in the repo

  • lib/umbra/service.ts — the UmbraService interface both implementations share.
  • lib/umbra/real.ts — thin wrappers over the factory functions above, used when demo-mode is off.
  • lib/umbra/demo.ts— deterministic shim so the hackathon video doesn't depend on devnet uptime.
  • lib/umbra/hook.ts useUmbra() picks the right instance based on the demo-mode toggle.