ShieldPass is a private cross-border remittance corridor. It lets anyone swap crypto for Nigerian naira instantly, paid straight to their bank account — without ever exposing their identity or banking data on-chain — and send funds privately to other users, where the amount, sender, and receiver stay hidden.
Your crypto lives in a Shielded Pool, not a standard escrow. You prove ownership and compliance with zero-knowledge proofs generated locally on your device, and the smart contract verifies every proof on-chain using Stellar's native BN254 pairing functions. The chain only ever sees mathematics — never your BVN, name, or bank details.
Built for the Stellar Hacks: ZK hackathon. The ZK proof is load-bearing: no valid proof ⇒ no swap, no transfer, no payout. Verification happens on Soroban itself, not off-chain.
Shielded Pool • On-chain Groth16 (BN254) • Private P2P Transfers • Zero-Storage Backend • Passkey Smart Wallets • Gasless
Traditional off-ramps force you to upload your identity, trust a custodian with your funds, and publicly broadcast your transactions. ShieldPass eliminates all three — and adds fully private peer-to-peer payments on top.
- Old Way: When you sent money, everyone could see it sitting in an escrow account.
- New Way: All user funds are mixed in one pool. Each balance is a secret note (
Poseidon(amount, owner, randomness, …)) owned by your shielded key. Nobody scanning the chain can tell which note is yours.
- Old Way: Proofs were checked off-chain by a backend you had to trust.
- New Way: The Soroban contract verifies every Groth16 proof itself, on-chain, via the native
bn254pairing-check host function (~37.5M instructions — well within budget). The math is the only authority.
- Old Way: The pool would have to trust a relayer to publish the correct Merkle root.
- New Way: A dedicated
merkle_insertZK circuit proves everyold_root → new_roottransition. The contract verifies each append, so the commitment tree advances trustlessly — no party can forge a root.
4. Private Payments (Hidden Amounts)
- Old Way: Sending money revealed who, to whom, and how much.
- New Way: A
shielded_transferproof spends your note and mints one note for the recipient + a change note for you — entirely inside the pool. The amount, sender, and receiver are all hidden; only the recipient learns the amount (from an encrypted blob).
- Old Way: We saved your bank account in our database. A breach leaked your banking info.
- New Way: We don't save your bank account at all. It lives in your browser; at swap time your phone flashes the details to the backend, we pay the bank, and the details are dropped from memory.
- Old Way: If the off-ramp took your crypto but never paid the fiat, your funds were gone.
- New Way: A withdrawal pre-commits a refund note on-chain. If the naira never settles, after a 1-hour time-lock you reclaim your value trustlessly — the platform can never simply keep the crypto. (A future zkTLS
fiat_payoutproof will make the claim itself fully trustless; today the claim is admin-gated and the refund is your guarantee.)
Meet Tobi, who wants to use ShieldPass:
1. Onboard (invisible funding). Tobi signs up with a Passkey (Face ID). His shielded identity (spending + encryption keys) is derived from the passkey itself. ShieldPass mints him a secret note worth 500 XLM — no public transaction happens. To the outside world, his wallet holds $0.
2. Shield / Unshield. Tobi can move his own crypto into the private pool (Shield) or pull it back out to his wallet (Unshield) at any time.
3. Withdraw to Naira. Tobi cashes out 100 XLM. On his phone a ZK proof spends his note, mints a 400 XLM change note, and authorizes the contract. The backend pays his bank via Paystack/Lenco; the change stays private.
4. Send privately. Tobi sends 50 XLM to a friend by email or shp_ address. A shielded_transfer proof moves the value inside the pool — fully private. His friend's app scans, decrypts the note, and their shielded balance just goes up. A 🔔 notification fires.
5. Stay informed. Every action — faucet, shield, unshield, withdraw, send, and received payments — lands in an in-app Activity feed with an unread badge.
Three Circom/Groth16 circuits over BN254, all verified on-chain:
confidential_swap(browser): proves you own a valid note, derives the spendnullifier, and computes the change. Used by Withdraw and Unshield.swap_amountis public here (real value leaves the pool).shielded_transfer(browser): spends one note → two output notes (recipient + change). Amounts are private; the recipient's owner tag is bound into the proof. Powers private P2P payments.merkle_insert(backend): proves eachold_root → new_roottree append so the pool's commitment tree advances trustlessly — no on-chain hashing needed.
flowchart LR
subgraph Device["📱 Your Device"]
SK["Shielded key (from passkey PRF)"]
PROOF["confidential_swap / shielded_transfer proof"]
ENC["x25519 note encryption"]
end
subgraph Chain["⛓️ Soroban Shielded Pool"]
VERIFY["bn254 pairing_check (on-chain Groth16)"]
TREE["Poseidon commitment tree"]
NULL["Spent nullifiers"]
end
subgraph Backend["⚙️ ShieldPass Relayer"]
IDX["Tree indexer + merkle_insert prover"]
PAY["Paystack / Lenco"]
BLOB["Encrypted note blobs"]
end
SK -->|"prove locally"| PROOF
PROOF -->|"verified on-chain"| VERIFY
VERIFY --> TREE
VERIFY --> NULL
IDX -->|"advance root trustlessly"| TREE
ENC -->|"deliver to recipient"| BLOB
Backend -->|"naira"| PAY
Sending to another ShieldPass user keeps everything hidden, like a locked mailbox with no names:
- You encrypt the note details to the recipient's published encryption key (x25519 ECDH + XChaCha20-Poly1305) and submit the
shielded_transfer. - The funds never leave the pool — your note is nullified; the recipient gets a new note; nothing public is revealed.
- The recipient's app scans new encrypted blobs, trial-decrypts with their key, finds the ones meant for them, and updates their shielded balance — automatically, with a notification.
Sending to an external
G…/C…wallet instead unshields (the funds become public on arrival). Sending to a ShieldPass user stays fully shielded.
A tiered compliance model so small swaps stay frictionless while large ones stay regulated:
| Tier | Gate | Unlocks |
|---|---|---|
| Tier 1 | Passkey hardware attestation (hardware_attested) |
Everyday swaps |
| Tier 2 | BVN verification (bvn_verified) |
High-value swaps (> ₦1,000,000) |
The same circuit enforces both. A public input require_bvn is set by the backend from the swap's naira value; the contract rejects a high-value withdrawal that didn't use a Tier-2 proof. Compliance attributes are bound into the note.
No seed phrases, no extensions, no XLM required. Each user gets an OpenZeppelin Smart Account secured by a WebAuthn passkey; transactions are submitted gaslessly via the relayer. Your shielded key (which owns your private notes) is derived from the passkey's PRF — so resetting your login PIN never touches your private funds.
| Layer | Tech |
|---|---|
| ZK Circuits | Circom + snarkjs Groth16, Poseidon (BN254) |
| On-chain verification | Soroban native bn254 pairing-check (Rust contract) |
| Smart Contract | Rust / Soroban — Shielded Pool (deposit · insert · confidential_swap · unshield · shielded_transfer · claim · refund) |
| Note encryption | x25519 + XChaCha20-Poly1305 (@noble) |
| Wallets | OpenZeppelin Smart Accounts (WebAuthn passkeys / secp256r1) |
| Gasless Relay | OpenZeppelin Channels |
| Backend | Node, Express, Prisma 7 + Neon Postgres |
| Frontend | React, Vite, Tailwind, Framer Motion |
| Fiat Payouts | Paystack + Lenco (stateless / zero-storage) |
- Network: Stellar / Soroban testnet
- Shielded Pool contract:
CCSNB23PL3NFACTXI2U5GCQHI4ZG3CNYCPJDQLKTH4EVC6GZUXRKOKPD - Pool token: native XLM (its testnet SAC)
Prerequisites: Node 20+ (Prisma 7 requires ≥ 20.19), Rust + stellar CLI (for the contract), circom (only to regenerate circuits), a backend .env, and a frontend .env.
# Backend — prisma generate is required (the v7 client is gitignored)
cd backend && npm install && npx prisma generate && npm run dev # http://localhost:3001
# Frontend
cd frontend && npm install && npm run dev # http://localhost:5173Build & test the Soroban Shielded Pool:
cd SDK/contracts/shielded_pool && cargo test # contract tests (real proofs)
stellar contract build # wasm for deployRegenerate circuits + proving keys (only if you change a circuit):
cd SDK/circom && npm install
bash scripts/build.sh confidential_swap
bash scripts/build.sh merkle_insert
bash scripts/build.sh shielded_transferKey environment variables:
| File | Var | Purpose |
|---|---|---|
backend/.env |
NEON_CONNECTION_STRING |
Neon Postgres direct URL (?sslmode=require, no channel_binding) |
backend/.env |
STELLAR_CONTRACT_ID / STELLAR_RELAYER_SECRET |
Shielded Pool contract id + the admin/relayer keypair |
backend/.env |
PAYSTACK_SECRET_KEY / LENCO_* |
Naira payout providers |
backend/.env |
FAUCET_NOTE_AMOUNT / FAUCET_NOTE_ASSET |
Onboarding seed note (defaults 500 / XLM) |
frontend/.env |
VITE_API_URL |
Backend URL (powers swaps, transfers, scanning, notifications) |
frontend/.env |
VITE_ESCROW_CONTRACT_ID |
The Shielded Pool contract id |
See LICENSE.



