Live demo! (the xx network stuff is working, but it takes a VERY LONG TIME. faster locally)
Privacy-Preserving Cross-Chain Payments
Plata Mia enables anyone to have spendable stealth balances on any chain that are unlinkable to their wallet or other stealth balances.
It uses Hyperbridge to spend the stealth balances that exist on Asset Hub anywhere. It then uses xx-network to send private notifications so we don't to have a use a tagging system (more private!)
Plata Mia solves the privacy problem in blockchain payments by implementing:
- Stealth Addresses: Receive payments to unlinkable addresses
- UTXO Model: Each payment is a separate unspent transaction output. This is how most privacy blockchains work
- Aggregated Payments: Pay from multiple UTXOs in a single transaction
- Cross-Chain Verification: Verify balances across chains without bridging tokens
- Private Notifications: Metadata-resistant messaging - get the info to withdraw or spend your stealth balances in a private quantum-resistant XX inbox
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PLATA MIA SYSTEM β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
β β β
ββββββΌβββββ βββββββΌβββββββ ββββββββΌβββββββ
βFrontend β β Indexer β β XX Proxy β
β(Next.js)β β(Fastify) β β (Go/xxDK) β
ββββββ¬βββββ βββββββ¬βββββββ ββββββββ¬βββββββ
β β β
ββββββββββββββ¬βββββββββββββ΄ββββββββββββββββββββββββββ
β
ββββββββββββββΌβββββββββββββ
β Smart Contracts β
β (StealthVault.sol) β
β Passet Hub Testnet β
β β
ββββββββββββββ¬βββββββββββββ
β
ββββββββββββββΌβββββββββββββ
β β β
ββββββΌββββββ βββββΌβββββ βββββββΌβββββ
βHyperbridgeβ βXX Networkβ βPolkadot β
β SDK β β cMix β βAsset Hub β
ββββββββββββ βββββββββββ ββββββββββββ
| Component | Purpose | Technology |
|---|---|---|
| Frontend | User interface for sending/receiving payments | Next.js 16, React 19, wagmi |
| StealthVault | Smart contract managing stealth balances & invoices | Solidity 0.8.24, Hardhat |
| Indexer | Hyperbridge storage proof provider | Fastify, @hyperbridge/sdk |
| XX Proxy | Private messaging gateway | Go, gitlab.com/elixxir/client |
βββββββββββ βββββββββββ
β Sender β βRecipientβ
β (Bob) β β (Alice) β
ββββββ¬βββββ ββββββ¬βββββ
β β
β 1. Enter "alice-test" + 0.1 PAS β
β β
βββΊ 2. Generate stealthId = hash("alice-test:timestamp:random")
β receiverTag = hash("alice-test") β
β β
β 3. depositStealthNative(stealthId, receiverTag) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β StealthVault.sol β β
β balances[stealthId][PAS] = 0.1 β β
β emit StealthPayment(...) β β
β β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β β 4. Scan events
β β for receiverTag
β ββββ
β β
β β 5. Discover payment
β β stealthId: 0.1 PAS
β β
ββββββββββββ ββββββββββββ
β Customer β β Merchant β
ββββββ¬ββββββ ββββββ¬ββββββ
β β
β 1. Browse items (0.5 PAS total) β
β β
ββββββββββββββββ 2. createInvoice() ββββββββββββββββββ€
β invoiceId: 0xABC β
β β
β 3. Hyperbridge: Check aggregated stealth balance β
βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β Storage proofs for all stealthIds: β β
β - stealthId1[PAS] = 0.1 β β
β - stealthId2[PAS] = 0.2 β β
β - stealthId3[PAS] = 0.15 β β
β - stealthId4[PAS] = 0.3 β β
β Total: 0.75 >= 0.5 β β β
βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 4. payInvoiceMulti([id1, id2, id3], assetId, invoiceId)
ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β Contract: β β
β - Withdraw 0.1 from id1 β β
β - Withdraw 0.2 from id2 β β
β - Withdraw 0.15 from id3 β β
β - Transfer 0.45 to merchant... need 0.05 more β β
β - Withdraw 0.3 from id4 β β
β - Transfer additional 0.05 (total: 0.5) β β
β - Refund 0.25 to id4 β β
β - Set invoices[0xABC].paid = true β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β β 5. Hyperbridge verify
β ββββββββββββββββ
β β Read storage:β
β β paid = true ββ
β ββββββββββββββββ
User Public ID: "alice-test"
β
βββΊ receiverTag = keccak256("alice-test")
β Purpose: Discovery (constant per user)
β Example: 0x7a9b3c... (same for all Alice's payments)
β
βββΊ Per-Payment Generation:
ββ timestamp = 1234567890
ββ random = 0xDEADBEEF
ββ stealthId = keccak256("alice-test:1234567890:0xDEADBEEF")
Purpose: Unlinkable storage (unique per payment)
Example: 0x1f3a8e... (different every time)
Onchain Storage:
ββββββββββββββββββββββββββββββββββββββββββββ
β balances[0x1f3a8e...][PAS] = 0.1 β β Unlinkable
β balances[0x9c2d4f...][PAS] = 0.2 β β Unlinkable
β balances[0x5b7e1a...][PAS] = 0.15 β β Unlinkable
ββββββββββββββββββββββββββββββββββββββββββββ
β
βββΊ Discovery: Scan events for receiverTag = 0x7a9b3c...
Why we need it: To verify stealth balances and invoice payments across chains without bridging tokens or trusting third parties
What it does: Provides proofs of storage state on other chains
- Storage Slot Computation
Solidity nested mappings require calculating storage slots:
const BALANCES_BASE_SLOT = 0n;
const outerSlot = keccak256(
encodeAbiParameters(["bytes32", "uint256"], [stealthId, BALANCES_BASE_SLOT])
);
const finalSlot = keccak256(
encodeAbiParameters(["bytes32", "uint256"], [assetId, hexToBigInt(outerSlot)])
);
const balance = await client.getStorageAt({
address: stealthVaultAddress,
slot: finalSlot,
});- Aggregated Balance Verification
Without Hyperbridge, you'd need to:
- Bridge tokens to the merchant's chain, OR
- Trust a centralized oracle
With Hyperbridge:
// Merchant on Chain B checks customer's stealth balance on Chain A (Passet Hub)
const utxos = await discoverStealthUTXOs(publicId);
const totalBalance = await queryAggregatedStealthCreditViaHyperbridge({
stealthIds: utxos,
assetId: PAS_TOKEN,
requiredAmount: invoiceAmount,
});
// Hyperbridge returns: { sufficient: true, total: 0.75 }
// Uses storage proofs- Invoice Status Verification
struct Invoice {
address merchant; // slot offset 0
bytes32 assetId; // slot offset 1
uint256 amount; // slot offset 2
bool paid; // slot offset 3 β We verify this
}const INVOICES_BASE_SLOT = 1n;
const structSlot = keccak256(
encodeAbiParameters(["bytes32", "uint256"], [invoiceId, INVOICES_BASE_SLOT])
);
const paidSlot = structSlot + 3n; // Access 'paid' field
const isPaid = await client.getStorageAt({
address: stealthVaultAddress,
slot: paidSlot,
});
// Hyperbridge verifies this storage value via consensusSee docs/HYPERBRIDGE_INTEGRATION.md for detailed implementation.
Why we need it: To notify users of incoming stealth payments without revealing sender/receiver identity to network observers
Existing privacy solutions either require the user to try to decrypt all messages onchain, or apply some sort of tag mechanism that decreases privacy. With XX network we can send untraceable messages with the data available to allow the user to spend or withdraw their stealth balances.
What it does: Routes messages through a quantum-resistant mixnet with end-to-end encryption
No Tagging Scheme = Maximum Privacy
Unlike traditional messaging systems that use topics/tags for routing, XX Network uses computational discrimination:
Traditional Systems:
Topic: "alice-payments" β Metadata leak
XX Network:
No tags β Recipients try to decrypt all messages β Only valid recipient succeeds
How it provides privacy:
- All messages encrypted with recipient's public key
- Recipients attempt to decrypt passing messages
- Only valid recipient can decrypt
- No metadata: network can't determine sender/receiver
- Quantum-resistant: LFG
This eliminates metadata leakage that would compromise stealth address privacy.
- Node.js 20+
- Go 1.21+ (for XX Network proxy)
- Wallet with PAS tokens on Passet Hub Testnet
- Wallet (I've only tested this with MetaMask)
git clone https://github.com/your-org/plata-mia.git
cd plata-miacd contracts
npm install
npx hardhat test
# Deploy to Passet Hub Testnet
npx hardhat ignition deploy --network passetHubTestnet ignition/modules/StealthVault.ts
# Or use this: 0xe4F461EaECD4DeFF700e31f7Fb74bb4395089020cd xxproxy
go mod download
# Start proxy on port 8787
XX_PROXY_LISTEN=:8787 go run main.go
Expected output:
2025/01/15 10:30:45 Starting XX Network proxy on :8787
2025/01/15 10:30:45 Downloading NDF...
2025/01/15 10:31:02 Creating new client state...
2025/01/15 10:33:15 Network follower healthy
2025/01/15 10:33:15 DM client initialized
2025/01/15 10:33:15 XX Network proxy ready!
cd indexers/paseo-hyperbridge
npm install
# Configure environment
cat > .env <<EOF
STEALTH_VAULT_ADDRESS_PASSET=0xe4F461EaECD4DeFF700e31f7Fb74bb4395089020
PASSET_RPC_URL=https://paseo-asset-hub-rpc.polkadot.io
HYPERBRIDGE_RPC_URL=https://hyperbridge.polkadot.io
EOF
# Start indexer
npm run dev
# Listens on http://localhost:4545cd app
npm install
# Configure environment
cat > .env.local <<EOF
NEXT_PUBLIC_STEALTH_VAULT_ADDRESS_PASSET=0xe4F461EaECD4DeFF700e31f7Fb74bb4395089020
NEXT_PUBLIC_PASSET_CHAIN_ID=420420422
HYPERBRIDGE_INDEXER_URL=http://localhost:4545
XX_PROXY_BASE_URL=http://localhost:8787
EOF
# Start development server
npm run dev- Visit Polkadot Faucet
- Select "Paseo Asset Hub"
- Enter your wallet address
- Receive PAS tokens
The app tells you what to do and explains what's going on. Just follow!
Contributions welcome! Please open an issue or PR.
MIT License - see LICENSE file
- Documentation: docs/
- Hyperbridge SDK: https://docs.hyperbridge.network/
- XX Network: https://xx.network/
- Polkadot: https://polkadot.network/
Built with β€οΈ for the Polkadot ecosystem