Monyr

§ 00 · Technical proof

Built on Umbra. Here’s the receipt.

Monyr is a thin product layer on top of Umbra’s privacy primitives. This page lists every SDK call we make, what it does, and which parts of the flow are live versus still on the roadmap.

SDK
@umbra-privacy/sdk · v4.0.0
ZK prover
@umbra-privacy/web-zk-prover · v2.0.1
Program (mainnet)
UMBRAD2ishebJTcgCLkTkNUx1v3GyoAgpTRPeWoLykh
Program (devnet)
DSuKkyqGVGgo4QtPABfxKJKygUDACbUhirnuv63mEpAJ
Indexer (mainnet)
utxo-indexer.api.umbraprivacy.com
Indexer (devnet)
utxo-indexer.api-devnet.umbraprivacy.com
§ 01The SDK surface

Every Umbra call we make, tagged honestly.

Live means it runs in the shipping app today. Wired means it’s integrated and proven in code, used by an internal surface. Plannedmeans we’ve scoped the call but haven’t merged it.

  • Live@umbra-privacy/sdk

    getUmbraClient()

    Instantiate the Umbra client with our signer + RPC + indexer config.

    Configured per network (mainnet/devnet) with QuickNode RPC and the official Umbra UTXO indexer endpoint. We pass `deferMasterSeedSignature: true` so the master seed is only derived when an action genuinely needs it — keeping signatures intentional, not surprise prompts.
  • Live@umbra-privacy/sdk

    getUserAccountQuerierFunction()

    Read the user’s on-chain Umbra registration state.

    Used to check `isInitialised`, `isUserAccountX25519KeyRegistered`, `isUserCommitmentRegistered`, and `isActiveForAnonymousUsage` — the four flags that gate whether a wallet can transact privately. The dashboard branches on this to show the registration prompt vs. the regular flow.
  • Live@umbra-privacy/sdk

    getUserRegistrationFunction()

    One-time on-chain registration for a wallet.

    Called with `{ confidential: true, anonymous: true }` so the same registration covers both encrypted balances and anonymous deposits. Idempotent: re-runs are no-ops once an account is fully initialised. Wired into `Activate Private Payments` for recipients, and into the payer flow when a wallet hasn’t yet registered with Umbra.
  • Live@umbra-privacy/web-zk-prover

    getUserRegistrationProver()

    Generate the Groth16 proof needed by registration.

    ZK assets are loaded from Umbra’s CDN, but proxied through `/api/umbra-zk` to sidestep CORS. The prover runs inside a Web Worker so the UI stays interactive while proving.
  • Live@umbra-privacy/web-zk-prover

    getCdnZkAssetProvider()

    Configure where circuit assets and the manifest are fetched from.

    Pointed at our own `/api/umbra-zk/*` route handlers, which transparently proxy Umbra’s CDN. Same bytes, same hashes — just origin-friendly so the prover can stream them without CORS errors.
  • Live@umbra-privacy/sdk

    getEncryptedBalanceQuerierFunction()

    Decrypt the vault’s encrypted USDC balance for the dashboard.

    Reads the on-chain ETA ciphertext and decrypts client-side with the master viewing key. The server never sees a dollar amount — every total on the dashboard is reconstituted in the browser.
  • Live@umbra-privacy/sdk

    getPublicBalanceToReceiverClaimableUtxoCreatorFunction()

    Payer flow: deposit public USDC → mixer → receiver-claimable UTXO.

    The production primitive behind `/@alice` payments. The payer’s public USDC enters Umbra’s UTXO pool and produces a UTXO whose destination is AES-encrypted in the commitment — the on-chain create event reveals the payer and amount, but not the recipient vault. Called from `pay-confirmation-modal.tsx`.
  • Live@umbra-privacy/sdk

    getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction()

    Vault-to-vault sends: encrypted balance → receiver-claimable UTXO.

    Used when a Monyr user sends from their own vault to another `@handle`. The sender’s vault is visible as the create-tx signer, but the amount (a homomorphic encrypted-balance debit) and the destination vault (AES-encrypted in the UTXO) are both opaque on-chain — so observers see “vault X did a private send”, not “Alice paid Bob.” Called from `send-privately-dialog.tsx`.
  • Live@umbra-privacy/sdk

    getClaimableUtxoScannerFunction()

    Scan the UTXO tree and decrypt entries claimable by this user.

    Runs on dashboard load via `useInboxPayments`, bounded to a recent window so the main thread doesn’t freeze on a full-tree scan. Also runs inside the withdrawal flow to confirm a self-claimable UTXO is indexed before claiming it. No automatic background poll yet — claims are user-triggered from the inbox.
  • Live@umbra-privacy/sdk

    getReceiverClaimableUtxoToEncryptedBalanceClaimerFunction()

    Recipient-side: claim incoming UTXOs into the encrypted balance, gas-free.

    Submitted via `getUmbraRelayer` so the recipient pays no SOL and the claim is signed by the relayer, not the vault — which is what unlinks the deposit and the credit on-chain. Wired into the inbox `Claim` action.
  • Live@umbra-privacy/sdk

    getEncryptedBalanceToSelfClaimableUtxoCreatorFunction()

    Withdrawal step 1: encrypted balance → self-claimable UTXO.

    The first leg of a private withdrawal. The vault signs an Arcium-backed flow that debits the encrypted balance and inserts a UTXO whose destination is AES-encrypted. Called from `withdraw-dialog.tsx`.
  • Live@umbra-privacy/sdk

    getSelfClaimableUtxoToPublicBalanceClaimerFunction()

    Withdrawal step 2: relayer claims the UTXO into the destination’s public token account.

    Submitted via the relayer — the on-chain claim is signed by Umbra, not the vault, and credits the destination wallet’s public USDC balance. There is no direct vault-to-wallet SPL transfer; the link between create and claim is hidden behind the mixer’s anonymity set.
  • Live@umbra-privacy/sdk

    getUmbraRelayer()

    Submit claim transactions on behalf of the user.

    Used by both the inbox claim and the withdrawal claim leg. The relayer pays gas and signs the transaction; it sees the proof but never holds funds or sees the encrypted contents.
  • Planned@umbra-privacy/sdk

    getComplianceGrantIssuerFunction()

    Issue a scoped, on-chain viewing grant to an accountant or auditor.

    Time-, mint-, or transaction-scoped. Grant holders decrypt amounts in the window without holding spend rights. The accountant story is documented; grant issuance and CSV/PDF export are not in the shipping build.

The factory-function shape (get*Function) is intentional on Umbra’s side — a small, composable surface where each call returns a typed, reusable builder.

§ 02The flow

Six steps from click Pay to decrypted inbox.

What actually happens between a payer’s wallet popup and a row’s appearance in your dashboard. No hand-waving — every hop is a real network actor.

  1. Step 01

    Payer browser

    Wallet connects · loads the public profile

    Payer hits monyr.xyz/@alice. We fetch handle metadata (vault pubkey, display name). Wallet adapter mounts and waits for an intent.

  2. Step 02

    ZK prover (Web Worker)

    Groth16 proof generates

    The prover, bootstrapped from CDN assets via getCdnZkAssetProvider, runs inside a worker. UI stays responsive; the user sees a progress card. 2–8 seconds end-to-end.

  3. Step 03

    Solana program

    Deposit lands in the mixer

    getPublicBalanceToReceiverClaimableUtxoCreatorFunction submits the tx. Public USDC enters the unified mixer pool. The on-chain trace shows: program touched, anonymity set updated.

  4. Step 04

    Umbra indexer

    UTXO commitment indexed

    The indexer publishes the new claimable commitment. Alice’s dashboard, on its next scan tick, will see exactly one new UTXO destined for her vault pubkey — and only she can decrypt it.

  5. Step 05

    Umbra relayer

    Auto-claim, gas-free

    Alice’s client builds a claim proof for the new UTXO and hands it to the relayer. The relayer pays SOL gas; Alice never has to. Funds settle into her encrypted balance.

  6. Step 06

    Alice browser

    Inbox decrypts · the row appears

    Master Viewing Key, derived in-browser from Alice’s vault signature, decrypts the amount and memo. The dashboard inbox animates the new payment in. The server saw none of it.

§ 03Real vs. simulated

What ships today. What is still seeded.

A judge or a careful reader should be able to tell, line by line, what executes against Umbra in production and what is fixture data wearing a real-looking UI.

Live · against Umbra

Real today

Code that runs against Umbra mainnet/devnet right now

Real
  1. 01

    Wallet Standard sign-in

    Phantom, Solflare, Backpack via @solana/wallet-standard-features. Real signatures, real accounts.

  2. 02

    Umbra client + signer

    getUmbraClient configured for mainnet or devnet; signer adapter bridges Wallet Standard to IUmbraSigner.

  3. 03

    Umbra account registration

    getUserRegistrationFunction({ confidential: true, anonymous: true }) — the on-chain prerequisite for any private flow.

  4. 04

    Real Groth16 proof generation

    getUserRegistrationProver with CDN assets proxied through /api/umbra-zk. The proof your wallet signs is genuine.

  5. 05

    Live USDC payments via the receiver-claimable mixer flow

    getPublicBalanceToReceiverClaimableUtxoCreatorFunction. Real USDC base units (6 decimals); the encryption is Umbra’s, not ours. Wired into the /@alice payment flow.

  6. 06

    Two-wallet vault model

    Main wallet signs; vault keypair is generated in-browser; encrypted vault secret is stored as opaque ciphertext. No main-wallet pubkey hits the database.

Seeded · roadmap

Still simulated or seeded

UI is wired; the privacy hop is on the next milestone

Seeded
  1. 01

    Claims are user-triggered, not automatic

    The dashboard scans for claimable UTXOs on mount (and on the 60s stale window), but the claim itself only runs when the recipient presses “Claim” in the inbox. There is no background loop, no on-detect auto-claim, no push channel — so a recipient who never opens Monyr never settles. Once claimed, the relayer carries the tx gas-free.

  2. 02

    Memo encryption is vault-keyed, not MVK-derived

    Memos travel encrypted end-to-end inside the receipt payload, decrypted client-side with the vault’s receipt-encryption keypair — the server never sees them. But the key is a separate vault-derived secret, not the MVK-rooted hierarchy described in Umbra’s docs. Functionally private; not yet the canonical key derivation.

  3. 03

    Compliance / accountant viewing grants

    getComplianceGrantIssuerFunction is scoped but not wired. The accountant story — time-, mint-, or transaction-scoped read grants, plus CSV/PDF export — is documented in /privacy-model; the on-chain issuance and the export UI are not in the shipping build.

§ 04Setup & relayer model

What you sign, and when.

Every signature has a purpose. We don’t batch surprise prompts. Claiming a handle is one main-wallet signature. Activating private receiving — a separate dashboard step — derives the Master Viewing Key and runs the Umbra registration. First-time payers also register once before their first private payment; after that, it’s frictionless.

  1. 01Signature step

    Main wallet signs the unlock message

    At handle claim, the main wallet signs HUSH_UNLOCK_MESSAGE_V1 once. The signature feeds HKDF, which produces an AES-256-GCM key. That key encrypts the freshly generated vault keypair before anything is sent to the server.

  2. 02Signature step

    Vault keypair is generated in-browser

    A fresh Ed25519 keypair, never seen by the main wallet’s seed. Its public key becomes the @handle’s receiving address; its secret is encrypted at rest. Steps 01 and 02 are the entirety of handle claim — one main-wallet signature, no Umbra interaction yet.

  3. 03Signature step

    Vault signs UMBRA_MESSAGE_TO_SIGN

    Run later, when the user opens “Activate Private Payments” on the dashboard. The Master Viewing Key is derived from this signature, deterministically. Re-derivable on any device the user can decrypt the vault on.

  4. 04Signature step

    Vault registers with Umbra

    Same activation step. getUserRegistrationFunction runs the on-chain registration (account init, X25519 key, anonymous-usage flag). Registration gas is sponsored — the main wallet never sends SOL to the vault, so no on-chain edge links them.

  5. 05Signature step

    Payer-side registration (only first time)

    A first-time payer registers their own wallet too, the first time they pay through Umbra. The pay flow checks via getUserAccountQuerierFunction and surfaces the prompt before the deposit so it isn’t a surprise. Subsequent payments skip this step.

  6. 06Signature step

    Relayer carries the claim, gas-free

    Once payments arrive, the dashboard hands claim proofs to Umbra’s relayer. The relayer sees the proof but not the encrypted contents; it submits the claim and never holds funds.

§ 05Known limitations

Where the seams are — stated up front.

The honest tradeoffs of building on a privacy SDK in 2026. None of these surprise the team; we’d rather they not surprise you.

  1. Limit 01

    Proof generation latency

    Groth16 in-browser is 2–8 seconds the first time circuits warm. We mitigate with a Web Worker, a progress UI, and pre-warming on dashboard load — but a per-request privacy proof is still human-paced.

  2. Limit 02

    First-time payer friction

    A wallet that has never used Umbra registers once before its first private payment. That’s an extra signature for new payers. Subsequent payments don’t pay this cost.

  3. Limit 03

    Withdrawal privacy depends on the anonymity set

    There is no direct vault-to-wallet edge — the create leg has an encrypted destination and the claim leg is relayer-signed. But the destination wallet’s received amount and timing are public, and on devnet (or low-volume mainnet) the mixer’s anonymity set is small, so timing+amount correlation can still re-identify a withdrawal. See /privacy-model § 04.

  4. Limit 04

    Relayer dependency

    Auto-claim leans on Umbra’s relayer. If it’s down, recipients can fall back to claiming themselves and paying gas — degraded, not broken. Demo paths include a non-relayer fallback.

  5. Limit 05

    Per-call privacy is out of scope

    “Your business is private; not every API call is.” ZK latency precludes per-request mixer privacy. Future agentic surfaces (V1.5) will use end-of-day private settlement, not per-call ZK.

  6. Limit 06

    We are tightly coupled to Umbra

    If Umbra pivots or pauses, our flows stall. Mitigation: the privacy layer is behind an interface we control, so a future swap is not a rewrite.

§ 06  ·  Coda

Umbra makes payments private. Monyr makes them addressable.

The cryptography belongs to Umbra. Our job is the layer above it — identity, distribution, the dashboard. If anything on this page reads softer than the code, that’s on us. Open an issue.

Live on Solana devnet. First consumer product on Umbra.