Confidential & Automated RWA Hub for Institutional Finance 4 bounties: ADI Foundation, Canton Network, Hedera, 0G Labs
# 1. Start backend
cd packages/backend && pnpm dev
# Expected: "Server running on port 3001"
# 2. Start frontend
cd packages/frontend && pnpm dev
# Expected: "Ready on http://localhost:3000"
# 3. Verify backend is live
curl http://localhost:3001/api/health
# Expected: { "status": "ok", "timestamp": "..." }
# 4. Verify ADI chain is reachable
curl -s https://rpc.ab.testnet.adifoundation.ai/ \
-X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
# Expected: { "result": "0x1869f" } (= 99999)
# 5. Verify Hedera testnet contracts exist
curl -s "https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.7996912" | python3 -m json.tool | head -5
# Expected: contract_id, evm_address, etc.- MetaMask installed with ADI testnet (chain 99999, RPC:
https://rpc.ab.testnet.adifoundation.ai/) - Wallet funded with test ETH on ADI chain
-
.envinpackages/backend/hasDEV_MODE=true - Two browser tabs ready: app + block explorer
- Terminal open for on-chain verification
| Chain | Explorer |
|---|---|
| ADI Testnet (99999) | https://explorer.ab.testnet.adifoundation.ai |
| Hedera Testnet | https://hashscan.io/testnet |
| Sepolia | https://sepolia.etherscan.io |
- Open
http://localhost:3000 - Scroll slowly — glass "Metaphor" bubble transforms into the title
- Point out partner logos at the bottom: ADI, Canton, Ethereum, 0G Labs, OpenZeppelin
- Click "Launch App"
"Metaphor is a confidential, automated RWA hub for institutional finance. 4 chains, 1 interface."
Goal: Show that roles live on-chain, not in a database.
- Click Connect Wallet (top-right)
- Connect MetaMask (ADI testnet chain 99999)
- Click Sign In — sign the EIP-191 message
- Role badges appear in topbar (ADMIN, ISSUER, INVESTOR...)
- Sidebar items change — some links disappear based on roles
Prove it's on-chain — open terminal:
# Read roles directly from the AccessControl contract
cast call 0x8E7D4E14583a37770C743D33092bbCC4E3Dd656d \
"isWhitelisted(address)(bool)" \
YOUR_WALLET_ADDRESS \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: true"Roles are stored in our AccessControl contract on ADI chain. The backend reads
hasRole()on-chain and embeds them in the JWT. The UI adapts — an auditor sees a completely different app than an issuer."
Bounty: ADI — RBAC on-chain, whitelisting
Goal: Create an ERC-20 RWA token with on-chain metadata. Show the tx hash.
- Go to Issue Asset (
/issue) - Fill:
- Type: Sovereign Bond
- Name: France OAT 2030
- ISIN: FR0014007LW0
- Nominal Value: 10,000,000
- Tokens: 10,000 (= $1,000 per token, shown in preview)
- Coupon Rate: 2.75%
- Frequency: Semi-Annual
- Maturity: 2030-06-15
- Jurisdiction: France
- Click Issue Token
- MetaMask pops up — confirm the tx
- Wait for confirmation — you get redirected
Prove it's on-chain — in terminal after tx:
# List all tokens created by the factory
cast call 0x0eD29f8c992bB10515296A301B27cd8F0a5d7d65 \
"getAllTokens()(address[])" \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: array containing the new token address
# Read the RWA metadata from the new token
cast call NEW_TOKEN_ADDRESS \
"getMetadata()((string,uint256,uint256,address))" \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: (FR0014007LW0, 275, 1907971200, ISSUER_ADDRESS)
# ISIN rate maturity issuerWhat the judges see:
- Token address on ADI explorer
TokenCreatedevent in tx logs- ISIN, coupon rate (in bps), maturity (unix timestamp) stored on-chain — not off-chain metadata
"This isn't a regular ERC-20 — ISIN, coupon rate, and maturity date are stored directly in the contract. The factory registers it and emits
TokenCreated. Transfer is restricted to whitelisted addresses only."
Bounty: ADI — RWAToken, RWATokenFactory, fractionalization
Goal: Show vault lifecycle on-chain.
- Go to My Vaults (
/vaults) - Click Create Vault
- Fill: Name "Fixed Income EU", Strategy Conservative, Sovereign Bonds, $5M deposit, Low risk
- Click Create Vault — MetaMask tx
- New vault appears in the grid
- Click into the vault — show detail page:
- 4 KPI cards (value, return, risk, payments)
- Asset composition table
- Allocation donut chart
Prove it's on-chain:
# Read vault info
cast call 0x6b6449bDEC04dd8717AC71565C7c065680C1534f \
"getVaultInfo(uint256)((address,bool))" 0 \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: (owner_address, true)
# Check how many vaults exist
cast call 0x6b6449bDEC04dd8717AC71565C7c065680C1534f \
"nextVaultId()(uint256)" \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: number > 0What the judges see:
VaultCreatedevent in tx logs on ADI explorer- Vault ID returned and displayed in the UI
- Multi-token vault ready for deposits
"VaultManager handles the full lifecycle — create, deposit, withdraw, allocate. It's pausable and restricted to whitelisted addresses. Each vault can hold multiple RWA tokens."
Bounty: ADI — VaultManager
Goal: Show 0G Compute inference + the HITL gate (AI never signs).
- On any vault detail page, click "Analyze with AI"
- Spinner: "0G Compute is analyzing..."
- Report appears with:
- Risk score: 42/100 (Moderate)
- Position Analysis: each asset scored with color + comment
- Stress Tests: 3 scenarios (e.g. "Central bank rate +1%: -2.5%")
- Recommendations: "Reduce EM exposure", "Increase duration hedge" — each with Approve / Reject buttons
- Click Approve on one — confirmation dialog: "You are about to approve an AI-recommended action..."
- Click Confirm & Execute — status changes to "Approved"
- Click Reject on another — status changes to "Rejected"
- Go to AI Reports (
/ai-reports) — show full report history with score evolution chart
Prove the AI output is structured:
# Fetch the raw AI report from the API
curl -s http://localhost:3001/api/v1/ai/reports | python3 -m json.tool | head -30
# Expected: structured JSON with reportId, riskScore, riskLevel,
# recommendations[].status = "pending_approval" / "approved" / "rejected"What the judges see:
- Structured risk report (not free text) — Zod-validated schema
- Position-level analysis per asset
- Stress test scenarios
- Approve/Reject buttons = human-in-the-loop gate
- The AI NEVER has signing authority — it recommends, the human decides
- "0G Compute" badge on every report
"The AI runs on 0G Compute and returns a Zod-validated structured report — scores, position analysis, stress tests, actionable recommendations. But notice: the AI never signs anything. Every recommendation needs explicit human approval through this confirmation dialog. That's our HITL architecture."
Bounty: 0G Labs — 0G Compute, structured output, human-in-the-loop
Goal: Show that the same vault looks different depending on who's looking. Privacy at the protocol level.
Page: Canton Demo /demo/canton
- Navigate to Canton Demo
- 3 panels show the SAME vault from 3 different roles simultaneously:
| What | Owner (BNP Paribas) | Counterparty (BlackRock) | Auditor (Deloitte) |
|---|---|---|---|
| Vault value | $5,200,000 | $5,200,000 | "Value hidden" |
| Asset values | Full (amount, allocation %) | Full | "Financial data restricted" |
| Coupon rates | 2.75%, 4.5% | 2.75%, 4.5% | Hidden |
| Parties visible | All 3 (BNP + BlackRock + Deloitte) | 2 (BNP + BlackRock) | 1 (BNP only) |
| Trades | All trades with full details | Only own trades | "Trade data not visible" |
| Audit log | Full (names, amounts, actions) | None | Anonymized (no names/amounts) |
- Point out each difference side by side:
- Owner: "$5.2M total" vs Auditor: lock icon + "Value hidden"
- Owner: 3 parties listed vs Counterparty: only 2
- Owner: full trade details vs Auditor: "Trade data not visible for this role"
Prove it's not just UI hiding:
# Fetch the same vault as each role — the API returns DIFFERENT data
curl -s http://localhost:3001/api/demo/canton/demo/cv-1/owner | python3 -m json.tool | grep totalValue
# Expected: "totalValue": 5200000
curl -s http://localhost:3001/api/demo/canton/demo/cv-1/auditor | python3 -m json.tool | grep totalValue
# Expected: "totalValue": null (not present)
# Auditor cannot see counterparty names
curl -s http://localhost:3001/api/demo/canton/demo/cv-1/auditor | python3 -m json.tool | grep -c "BlackRock"
# Expected: 0- Go to Data Room (
/data-room) to show the production interface:- Authorized Parties table with role icons
- Pending Trade Proposals with Accept/Counter/Reject
- Invite Party dialog
"This is native Daml on Canton — not a wrapper. ConfidentialVault, PrivateTrade, and AuditRight are 3 Daml modules with 9 templates. Visibility is enforced at the ledger protocol level — the auditor's transaction tree literally doesn't contain counterparty names or trade terms. It's not hidden in the UI, it's not in the data."
Bounty: Canton — Native Daml, 9 templates, visibility separation
Goal: Show coupon payments scheduled via Hedera's Schedule Service precompile.
- Go to Yield Calendar (
/yield-calendar) - Show the 4 summary cards: Total Distributed, Upcoming Total, Completed, Scheduled
- Timeline view: vertical chronological timeline
- Green checks = past completed payments
- "Now — Feb 2026" marker
- Dashed circles = future scheduled payments
- Switch to Upcoming tab — list with "in N days" badges
- Switch to Completed tab — list with amounts
Prove the bonds exist on Hedera:
# Read bond count from CouponScheduler on Hedera testnet
cast call 0x00000000000000000000000000000000007a05f0 \
"bondCount()(uint256)" \
--rpc-url https://testnet.hashio.io/api
# Expected: 2 (France OAT 2028 + US Treasury 10Y)
# Read bond details
cast call 0x00000000000000000000000000000000007a05f0 \
"getBond(uint256)((address,address,uint256,uint256,uint8,uint256,uint256,address,bool))" 0 \
--rpc-url https://testnet.hashio.io/api
# Expected: bond struct with token, faceValue, rate, frequency, startDate, maturity
# View contracts on HashScan
# CouponScheduler: https://hashscan.io/testnet/contract/0.0.7996912
# YieldDistributor: https://hashscan.io/testnet/contract/0.0.7996914"CouponScheduler calls Hedera's Schedule Service precompile at address 0x16b — that's the IHRC755 and IHRC1215 interfaces. It computes payment dates from bond parameters and creates scheduled on-chain transactions. When the date arrives, the coupon is distributed pro-rata via YieldDistributor. All from Solidity."
Bounty: Hedera — Schedule Service precompile from smart contract
Goal: Show on-chain RBAC management + multi-tenant white-label.
- Go to Administration (
/admin) - Roles & Permissions: 4 role cards with member counts
- Wallet Whitelist table:
- Find a wallet with KYC: Pending
- Click Approve KYC — triggers
addToWhitelist()on ADI chain - Badge changes to Verified with green check
- Click Add Wallet — add a new address with Issuer role
- This triggers 2 on-chain txs:
addToWhitelist()+grantRole()
- This triggers 2 on-chain txs:
Prove it's on-chain:
# After approving KYC, verify the address is whitelisted
cast call 0x8E7D4E14583a37770C743D33092bbCC4E3Dd656d \
"isWhitelisted(address)(bool)" \
THE_APPROVED_ADDRESS \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: true
# Check the role was granted
cast call 0x8E7D4E14583a37770C743D33092bbCC4E3Dd656d \
"hasRole(bytes32,address)(bool)" \
$(cast keccak "ISSUER_ROLE") THE_ADDRESS \
--rpc-url https://rpc.ab.testnet.adifoundation.ai/
# Expected: true- White-Label Config: Change institution name + primary color, show live preview
"Each institution gets its own isolated deployment via InstitutionRegistry — with 2-of-N multisig governance. That's propose, approve, execute. Full white-label."
Bounty: ADI — RBAC, whitelisting, white-label, multisig
| Contract | Address | Explorer |
|---|---|---|
| AccessControl | 0x8E7D4E14583a37770C743D33092bbCC4E3Dd656d |
View |
| RWATokenFactory | 0x0eD29f8c992bB10515296A301B27cd8F0a5d7d65 |
View |
| VaultManager | 0x6b6449bDEC04dd8717AC71565C7c065680C1534f |
View |
| InstitutionRegistry | 0xAB3Cbc56D958245a2688b2171417679e743B1daF |
View |
| Contract | Account | EVM Address | Explorer |
|---|---|---|---|
| CouponScheduler | 0.0.7996912 |
0x...7a05f0 |
View |
| YieldDistributor | 0.0.7996914 |
0x...7a05f2 |
View |
| Module | Templates | Tests |
|---|---|---|
| ConfidentialVault | ConfidentialVault, VaultInvitation, VaultAccessRight, TradeRequest, TradeSettlement | 28 passing |
| PrivateTrade | TradeProposal, TradeAgreement | |
| AuditRight | AuditInvitation, AuditRight |
# ADI contracts
cd packages/contracts-adi && forge test
# 111 tests passing
# Hedera contracts
cd packages/contracts-hedera && forge test
# 74 tests passing
# Canton/Daml
cd packages/contracts-canton && daml test
# 28 tests passing| Bounty | What They Want | What We Show | Killer Line |
|---|---|---|---|
| ADI | MVP on ADI chain, real utility, white-label, RBAC | Live tokenization tx + vault creation tx + whitelist tx on chain 99999 | "Multi-tenant InstitutionRegistry with 2-of-N multisig — each institution gets isolated contracts" |
| Canton | Native Daml, deployed on devnet, visibility separation | 3-panel same-vault demo, curl showing different API responses per role | "The auditor's transaction tree doesn't contain the data — it's not hidden, it doesn't exist" |
| Hedera | Schedule Service usage, coupon payments from smart contract | Bonds on HashScan, payment timeline, scheduleCoupon() calling precompile 0x16b |
"Coupon scheduling happens in Solidity via the Schedule Service precompile — not from a backend script" |
| 0G Labs | 0G Compute inference, structured decisions, HITL | Live analysis, Zod-validated report, approve/reject buttons | "The AI never has signing authority — every on-chain action requires explicit human approval" |
| Problem | Quick Fix |
|---|---|
| "Missing Authorization header" | Run: curl -X POST http://localhost:3001/api/auth/dev-login -H "Content-Type: application/json" -d '{"address":"0x1"}' → copy token → localStorage.setItem('metaphor_jwt','TOKEN') in browser console |
| MetaMask wrong network | Add ADI testnet: chain 99999, RPC https://rpc.ab.testnet.adifoundation.ai/ |
| Tx fails (insufficient funds) | Get test ETH from ADI testnet faucet |
| AI analysis slow | Set ZG_USE_MOCK=true in backend .env and restart |
| Canton demo shows mock data | That's fine — the demo data shows the same privacy model. Canton sandbox is optional |
| "Restricted Access" on a page | Wrong role — use dev-login for all roles |
| Hedera tx reverts | Admin address must be ECDSA alias, not long-zero format (see CLAUDE.md) |
| Backend crashes on start | Check pnpm install was run, check .env exists |