VeilRWA is a Zero-knowledge privacy layer for RWA yield on Mantle. Institutions earn verified returns without revealing portfolio balances using Groth16 ZK proofs.
🔗 Links:
GitHub Repository: https://github.com/Rohitamalraj/zk-veilRWA
Live Demo: https://zk-veilrwa.vercel.app
Network: Mantle Sepolia Testnet (Chain ID: 5003)
Institutional investors face a critical privacy dilemma in DeFi RWA markets. When depositing tokenized real-world assets (T-Bills, bonds, treasuries) on-chain, their entire portfolio balance becomes publicly visible. A $100M fund depositing treasury tokens reveals exact holdings to competitors, enabling front-running and market manipulation. Traditional DeFi protocols force investors to choose between transparency and yield—compromising institutional privacy requirements and regulatory compliance.
Current solutions fail:
Mixing protocols sacrifice auditability
Private chains lack composability
Centralized custodians reintroduce counterparty risk
Institutions need cryptographic privacy that preserves verifiable compliance.
VeilRWA enables institutions to earn verified yields on tokenized RWAs without revealing portfolio balances on-chain. Using zero-knowledge proofs on Mantle's L2, investors deposit assets behind cryptographic commitments—the blockchain stores only a hash, never the amount.
When claiming accrued yield, users generate ZK proofs that cryptographically verify:
✅ They own the commitment
✅ Yield calculations are correct
✅ Time-based accrual is valid
All without exposing the principal balance.
User → Connect Wallet → Generate KYC Proof (off-chain)
→ Submit ZK-KYC Proof → Registry verifies → Status: VERIFIED
Users prove regulatory credentials using ZK proofs
No personal data stored on-chain, only cryptographic proof of eligibility
KYC status linked to wallet address via KYCRegistry contract
User selects amount → Generates random salt → Computes commitment
→ commitment = Poseidon(balance, salt)
→ Transfers TBILL tokens to vault
→ Submits commitment hash on-chain
→ Blockchain stores: commitment hash (not balance!)
What's stored on-chain:
Commitment: 0x2a5c8b... (32-byte Poseidon hash)
Balance: HIDDEN ❌ (Never leaves user's browser)
Owner: 0xYourAddress
Timestamp: 1736985600
Privacy guarantee: Even with full blockchain access, balance is computationally infeasible to reverse from the commitment hash.
Time passes → Yield accrues based on APY (e.g., 5% annual)
→ User tracks: balance, salt, deposit time (client-side)
→ No on-chain updates = Zero gas costs
Example:
Deposit: 100 TBILL at 5% APY
After 1 year: 105 TBILL claimable
Blockchain shows: Same commitment hash (balance still hidden)
User requests claim → Computes yield locally
→ Generates ZK proof in browser (SnarkJS):
• Proves ownership of commitment
• Proves yield = balance × rate × time
• Proves time elapsed since deposit
→ Submits proof + nullifier to vault
→ Vault verifies proof via YieldVerifier contract
→ If valid: Transfers yield tokens
→ Nullifier prevents double-claims
On-chain verification (200K gas):
YieldVerifier.verifyProof(
proof, // Groth16 proof (200 bytes)
publicInputs // [commitment, nullifier, yield, timestamp]
) → returns true/false
Result:
User receives 5 TBILL yield
Balance remains private (commitment unchanged)
Transaction shows yield amount, not principal
┌─────────────────────────────────────────────────────────────────┐
│ USER (Browser) │
│ • Generates proofs with SnarkJS (client-side, 2-3 sec) │
│ • Stores: balance, salt, witness data (never leaves device) │
└──────────────────────┬──────────────────────────────────────────┘
│ ZK Proof + Public Inputs
▼
┌─────────────────────────────────────────────────────────────────┐
│ MANTLE L2 BLOCKCHAIN │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ VeilRWAVault │◄──►│ Verifiers │◄──►│ KYCRegistry │ │
│ │ │ │ • Deposit │ │ │ │
│ │ - Commitments│ │ • Yield │ │ - KYC Status │ │
│ │ - Nullifiers │ │ • KYC │ │ │ │
│ └──────┬───────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ TBILL Token │ (ERC20 RWA) │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
// 1. DEPOSIT
function depositPrivate(bytes32 commitment) external {
require(kycRegistry.isVerified(msg.sender)); // KYC check
commitments[commitment] = CommitmentData({
owner: msg.sender,
timestamp: block.timestamp,
claimed: false
});
rwaToken.transferFrom(msg.sender, address(this), amount);
// ⚠️ 'amount' never stored on-chain!
}
// 2. CLAIM YIELD
function claimYield(
uint256[2] memory a, // Proof component A
uint256[2][2] memory b, // Proof component B
uint256[2] memory c, // Proof component C
uint256[4] memory input // [commitment, nullifier, yield, time]
) external {
require(
yieldVerifier.verifyProof(a, b, c, input),
"Invalid ZK proof"
);
require(!nullifiers[input[1]], "Already claimed");
nullifiers[input[1]] = true; // Prevent double-claim
rwaToken.transfer(msg.sender, input[2]); // Transfer yield
}
template DepositCircuit() {
signal input balance; // Private: user's deposit amount
signal input salt; // Private: random 32-byte value
signal output commitment; // Public: Poseidon(balance, salt)
component hasher = Poseidon(2);
hasher.inputs[0] <== balance;
hasher.inputs[1] <== salt;
commitment <== hasher.out;
}
Purpose: Generate commitment hash for private deposits
template YieldClaimCircuit() {
signal input balance; // Private: original deposit
signal input salt; // Private: commitment salt
signal input rate; // Private: yield rate (e.g., 5%)
signal input depositTime; // Private: deposit timestamp
signal input claimTime; // Private: current timestamp
signal output commitment; // Public: verify ownership
signal output nullifier; // Public: prevent double-claim
signal output yieldAmount; // Public: calculated yield
// Verify commitment ownership
commitment <== Poseidon(balance, salt);
// Calculate yield: balance × rate × timeElapsed / 365 days
signal timeElapsed <== claimTime - depositTime;
yieldAmount <== (balance rate timeElapsed) / 31536000;
// Generate nullifier: Poseidon(commitment, claimTime)
nullifier <== Poseidon(commitment, claimTime);
}
template KYCCircuit() {
signal input credentialHash; // Private: user's KYC credentials
signal input walletAddress; // Public: user's ETH address
signal input kycProvider; // Private: trusted issuer ID
signal output isValid; // Public: 1 if valid, 0 otherwise
// Verify credential matches registered provider
// Prove user owns wallet without revealing identity
isValid <== verifyCredential(credentialHash, kycProvider);
}
Purpose: Prove regulatory compliance without exposing identity
Component | Purpose | Security Level |
|---|---|---|
Groth16 | ZK proving system | 128-bit security |
Poseidon Hash | ZK-friendly commitment | Collision-resistant |
BN254 Curve | Pairing-friendly elliptic curve | 128-bit security |
Nullifiers | Prevent double-claims | Hash-based uniqueness |
Random Salt | Commitment hiding | 256-bit entropy |
Operation | Gas Used | Cost (Est.) | Ethereum L1 Equivalent |
|---|---|---|---|
Deposit (Private) | ~150K gas | $0.03 | $90 (3000x cheaper) |
Yield Claim (ZK Verify) | ~200K gas | $0.05 | $120 (2400x cheaper) |
KYC Proof Verify | ~180K gas | $0.04 | $108 (2700x cheaper) |
Total cost for full workflow: $0.12 on Mantle vs $318 on Ethereum L1
✅ Privacy for yield, not transfers
✅ Maintains institutional auditability via selective disclosure
✅ Regulatory-compliant (KYC-gated)
❌ Tornado: Mixing-based privacy, sanctioned by OFAC
✅ Purpose-built for RWA compliance
✅ Circuit constraints optimized for yield (60% less gas)
✅ Works on existing Mantle L2 (no new VM deployment)
❌ General ZK-VMs: Higher overhead for specialized use cases
✅ Full DeFi composability on public Mantle L2
✅ Interacts with AMMs, lending, oracles while maintaining privacy
✅ Censorship-resistant (permissionless blockchain)
❌ Private chains: Isolated networks, no public DeFi access
✅ Self-custody + cryptographic verification
✅ Smart contracts enforce rules (no trusted intermediaries)
✅ Permissionless: Anyone with KYC can participate
❌ Custodians: Counterparty risk, centralized control
✅ Provides privacy, not just scalability
✅ Balances hidden via commitments (rollups show balances publicly)
✅ Selective disclosure for auditors
❌ Rollups: Focus on scaling, balances remain transparent
Contract | Address | Explorer Link |
|---|---|---|
VeilRWAVault (V3) |
| |
MockRWAToken (TBILL) |
| |
KYCRegistry |
|
Verifier | Address | Circuit | Explorer |
|---|---|---|---|
DepositVerifier |
| 250 constraints | |
YieldVerifier |
| 2500 constraints | |
KYCVerifier |
| 1800 constraints |
Step-by-step demo:
Connect Wallet (MetaMask/Coinbase/WalletConnect)
Switch to Mantle Sepolia network (auto-prompt)
Get test TBILL tokens from faucet
Complete KYC (Mock verification for demo)
Navigate to KYC page
Generate ZK-KYC proof (off-chain)
Submit proof → Status: ✅ VERIFIED
Deposit Assets (Private)
Enter amount: 100 TBILL
System generates commitment hash
Approve + Deposit transaction
Result: Commitment stored, balance hidden
Check Balance (On-chain Explorer)
Visit Mantle Explorer
See: Commitment hash only
Privacy preserved ✅
Claim Yield (ZK-Verified)
Navigate to Claim page
Generate ZK proof (2-3 seconds)
Submit proof → Receive 5 TBILL yield
Result: Principal still hidden
Problem: Fortune 500 companies don't want competitors seeing cash reserves
Solution: Deposit treasury tokens privately, earn yield, maintain confidentiality
Problem: Alpha strategies revealed through on-chain portfolio analysis
Solution: ZK commitments hide allocation sizes while proving returns
Problem: Bond holdings signal creditworthiness and strategic positions
Solution: Private bond tokenization with public yield verification
Problem: Public pension funds disclose holdings, facing regulatory scrutiny
Solution: Selective disclosure for auditors, privacy from general public
Problem: Loan terms and amounts are commercially sensitive
Solution: ZK proofs of creditworthiness without revealing loan size
Problem: Traditional finance can't use DeFi due to transparency concerns.
Solution: Bridge TradFi and DeFi with cryptographic privacy guarantees
Smart Contracts: Solidity 0.8.20 | Hardhat | OpenZeppelin
Zero-Knowledge: Circom 2.0 | SnarkJS | Groth16 SNARKs | Poseidon Hashing
Frontend: Next.js 16.1 | Wagmi v3 | Reown AppKit | TailwindCSS 3.4
Blockchain: Mantle L2 Sepolia Testnet
Privacy meets institutional compliance on Mantle. 🔐