getting started
Architecture
System architecture and component relationships
System Overview#
Mini Veil consists of four major components: the on-chain Solana program, the off-chain relayer, the browser-based frontend, and the core cryptographic SDK.
⚠
Architecture Note
The relayer manages the Merkle tree, not the browser. This is a deliberate design choice: the browser generates proofs but never holds the full tree. The relayer accumulates deposit events, maintains the in-memory Poseidon tree, and submits root updates via multisig transactions.
Component Map#
┌──────────────────────────────────────────────────┐
│ User Wallet │
│ ┌────────────┐ ┌──────────────────┐ │
│ │ PrivateSend │ │ ScanClaimCard │ │
│ │ (shield + │ │ (detect + │ │
│ │ prove + │ │ claim stealth) │ │
│ │ unshield) │ └──────────────────┘ │
│ └─────┬───────┘ │
│ │ │
│ ┌─────▼──────────────────────────────────────┐ │
│ │ @mini-veil/core (SDK) │ │
│ │ keys() │ mixer() │ stealth() │ eta() │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────┘
│
Instructions │ Events
│
┌──────────────────────▼───────────────────────────┐
│ Solana Program (BPF) │
│ ┌────────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ MixerState │ │ Vault │ │ KeyRegistry │ │
│ │ PDA │ │ PDA │ │ PDA │ │
│ └────────────┘ └──────────┘ └──────────────┘ │
│ ┌────────────┐ ┌──────────┐ │
│ │ Nullifier │ │Multisig │ │
│ │ PDA │ │PDA │ │
│ └────────────┘ └──────────┘ │
│ shield() │ unshield() │ update_root() │
│ announce_stealth() │ claim_stealth() │
│ register_keys() │ init_mixer() │
└──────────────────────┬───────────────────────────┘
│
onLogs(PROGRAM_ID)
│
┌──────────────────────▼───────────────────────────┐
│ Off-chain Relayer │
│ │
│ ┌────────────────┐ ┌────────────────────────┐ │
│ │ PoseidonMerkle │ │ Express Server │ │
│ │ Tree (in-mem) │ │ /proof /next-counter │ │
│ └────────────────┘ │ /health │ │
│ └────────────────────────┘ │
│ processDepositEvent() → pushRootUpdate() │
│ (2-of-3 multisig signing) │
└────────────────────────────────────────────────────┘
Component Responsibilities#
Solana Program#
- Maintains
MixerStateaccounts with current Merkle root andtotal_shielded - Holds pool funds in system-owned Vault PDAs
- Verifies Groth16 proofs via
alt_bn128pairing syscalls - Manages nullifier PDAs for double-spend prevention
- Stores registered keys per wallet in
KeyRegistryPDAs - Enforces 2-of-3 multisig for root updates
Relayer#
- Subscribes to program logs via
connection.onLogs(PROGRAM_ID) - Parses
DepositEventlogs and inserts commitments into the in-memory Poseidon tree - Batches deposits until threshold is reached, then submits
update_rootvia multisig - Serves Merkle proofs via
/proof?commitment=0x...&denomination=... - Manages per-key counters via
/next-counter?key=0x...&denomination=... - Configurable threshold (default 1 for MVP)
Frontend (Browser)#
PrivateSendcomponent: key registration check, shield (if unspent), proof generation, bundled unshield + announceScanClaimCardcomponent: scans for stealth announcements, decrypts, derives keys, claims funds- Uses snarkjs for client-side Groth16 proving (~2-5s)
- Communicates with relayer for proofs and counters
Core SDK (@mini-veil/core) npm package coming soon#
- Key derivation and management
- Poseidon hashing (arity 1-3)
- Deposit note creation (nullifier, commitment)
- Groth16 proof generation and packing
- Stealth address generation and scanning
- ScalarSigner for stealth key signing
- ETA confidential amounts —
coming soon
Data Flow#
- User imports spending key (derived from wallet signature)
- PrivateSend queries relayer for counter and existing proof
- If note is unspent and no pending proof, shield transaction is sent
- Relayer picks up DepositEvent, inserts into tree, resolves pending proof requests
- Frontend polls
/proof, receives Merkle path - Frontend generates Groth16 proof via snarkjs
- Bundled unshield + announce_stealth transaction is submitted
- Recipient's ScanClaimCard detects via RPC log scanning
- Recipient claims stealth payment via ScalarSigner transaction