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.
get[Source]To[Target][Verb]Function({ client }) → returns a typed callable. Stipend composes six of these.
Hierarchical TVKs derived from a single MVK via Poseidon — the root unlock for fiscal-year reporting.
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.
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.
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.
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.
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.
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’.
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.