Opaque documentation
Private payments, explained.
How the wallet keeps what you pay apart from who you are, how to use it on Arc and the XRP Ledger, and how to build on it: dapps, deployers and contracts owned by a post-quantum key.
What Opaque is
A self-custodial wallet whose money moves through a shared pool.
When you deposit, your funds become notes: fixed-size pieces held by a pool contract (on Arc) or a pool account (on the XRP Ledger). A note is a commitment on chain and a secret only your wallet knows.
When you pay, your wallet proves it owns one of eight same-size notes in the pool without saying which one, and the pool pays the recipient from its own account. On chain, the payment comes from the pool, never from you. The proof is built in your browser and the note secret never leaves the page.
Requests travel through three relays, each of which only knows the hop before and after it, so the services that answer them don't learn your IP address.
Get started
Three ways in, one wallet: the same recovery phrase opens all of them.
- Website: opaque.credit/app (Arc mainnet), /testnet/app (Arc testnet), /testnet/xrpl (XRP Ledger testnet).
- Browser extension: download the zip, open
chrome://extensions, turn on Developer mode, and load the unzipped folder. One extension covers every network; it opens in Chrome's side panel. - Phone: open the website and add it to your home screen.
- Create a wallet. Write down the 12-word recovery phrase it shows, then type three of the words back to prove it.
- Set a password. It encrypts everything the wallet stores in this browser. See Password.
- Back it up with a passkey (optional, recommended). Clearing the browser then costs you nothing. See Passkey backup.
- Get test funds. Arc testnet: USDC from faucet.circle.com to your address. XRP Ledger testnet: Get test XRP on Home gives 95 XRP straight into notes.
- Deposit, then send. Your funds become notes; a send pays from the pool.
Recovery phrase and accounts
One phrase, many accounts, every chain.
Your 12 words are the wallet. Every account, address, note and app account is drawn from them, so importing the phrase anywhere (another browser, the extension, a phone) finds the same accounts and looks up their notes on chain.
- Accounts: Accounts → Add account makes Account 2, 3 and so on. Each has its own addresses and notes, and holds money on every chain.
- Account key: Accounts → Show account key gives one account alone, as
opq1…. Import it with Import account in another Opaque wallet to bring just that account. Whoever holds it can spend that account. - Rescan: Rescan the ledger looks for notes another device made.
Password
Everything secret in the browser is encrypted under it, as MetaMask's vault is.
- The phrase, account keys, notes, app accounts and Arc's signing keys are sealed with AES-256-GCM under a random key, and the password (PBKDF2-SHA256, 600,000 rounds) protects that key.
- Opaque locks after 15 idle minutes, when the tab closes, or with Lock now. Show recovery phrase, Show account key and a deployer's key ask for the password again, even while unlocked.
- A wallet made before passwords asks you to set one the next time it opens.
- Forgot it? Opaque can't recover it. Forgot your password? on the lock screen removes the wallet from this browser; your phrase or passkey brings it back.
Passkey backup
Clear your browser, lose your laptop, and still get everything back.
The wallet encrypts your phrase, imported account keys and browser-only notes on your device, under a key only your passkey (Touch ID, Windows Hello, a phone or a password manager) can produce. Opaque stores the ciphertext, and can't open it.
To restore, choose Restore with a passkey when Opaque opens. Accounts → Remove backup deletes Opaque's copy. The backup keeps itself current within a minute of new notes arriving, without asking for the passkey again.
Deposit, send, receive
On Arc
- Deposit: USDC at your account's address becomes notes, in the fewest whole notes that make the amount. On testnet, auto-deposit does it for you. Each deposit keeps 0.2 USDC at the address for gas.
- Send: enter an Arc address and a whole amount. Each note is its own payment with its own proof. Wait for privacy holds the payment until its pool has fresh deposits around it, which makes it harder to match to yours by timing.
- Receive: share your address. What arrives is public until you deposit it.
On the XRP Ledger
- Receive privately: your account has a reusable address per asset (the pool's address plus a tag). Anything paid to it becomes notes in your wallet, and the payer sees only the pool.
- Deposit tags: under Deposit, a one-time tag for an exact amount.
- Send: to any XRPL address. The pool pays it in one Batch the attesters sign.
Swap
- Arc: USDC and EURC (cirBTC too on mainnet), routed by LI.FI. The quote is asked through the relays. The swap itself is public, at your account's address, signed by your post-quantum key. What you get becomes private once it is USDC in your notes.
- XRP Ledger: private pool-to-pool. Your notes in one pool pay out as another asset through the ledger's AMM. The attesters refuse a swap that settles far below the market.
Apps on the XRP Ledger
Connecting to an XRPL dapp gives it a fresh account of its own, funded by the pool, so the dapp never sees your main account and two dapps can't link you. That account's master key is off, and its regular key signs once and is replaced. Apps lists each one; closing it returns what's left to the pool under your tag.
Contracts, owned by a post-quantum key
Deploy and manage contracts on Arc with no classical key anywhere.
The Contracts screen on Arc deploys bytecode from your account through the standard CREATE2 factory (0x4e59b44847b379578588920cA78FbF26c0B4956C, live on Arc mainnet and testnet), and calls contracts from it. Your account signs each one with its hash-based FORS+C key, sent through the relays. Breaking classical signatures gives an attacker nothing.
- Use an account for this alone. Accounts → Add account, then from another account Send it 2 USDC from your notes. On chain the pool paid it, so its contracts aren't tied to you. On testnet, turn auto-deposit off (the screen offers it) so the USDC stays as gas.
- Build the bytecode. Through the factory, a constructor's
msg.senderis the factory, so pass the owner as a constructor argument: the account address the screen shows.BYTECODE=$(forge inspect MyContract bytecode) ARGS=$(cast abi-encode "constructor(address)" 0xYourOpaqueAccount) echo "$BYTECODE${ARGS#0x}" # paste this into Contracts → Deploy - Deploy. The screen shows the address before you send. Each new salt gives a new address.
- Manage it. Call a contract takes the address and calldata, signed by the same key:
cast calldata "setFee(uint256)" 5 # paste into Contracts → Calldata
Deployers
Plain addresses for forge, hardhat and scripts, funded privately.
Accounts → Deployers → New deployer derives a fresh Arc address from your account. Fund privately opens Send with it filled in; Show key (after your password) gives PRIVATE_KEY=0x… for a project's .env. Your phrase brings every deployer back.
forge create src/MyContract.sol:MyContract --rpc-url https://rpc.testnet.arc.io --private-key $PRIVATE_KEY
A deployer is an ordinary secp256k1 key, so it is not post-quantum once it has signed. Keep only gas on it, use one per project, and don't send leftovers back to your account. For a contract that needs an owner, use Contracts instead, or pass that account as the owner.
Connect a dapp
- XRP Ledger, in the extension: Opaque answers as GemWallet and Crossmark, so dapps that support either work unchanged. Approvals open in the extension's own window.
- xrpl-connect: an adapter for xrpl-connect opens Opaque's connect page.
- WalletConnect: paste a dapp's
wc:link, or scan its QR code, at /testnet/xrpl-connect. The relay socket goes through Opaque's service, so WalletConnect sees Opaque's server, not you.
Supported: signAndSubmit (DEX offers, AMM deposits and more), sign-only for dapps that submit themselves, and signMessage. Opaque never connects to MetaMask or reads a browser wallet.
Privacy: who sees what
What is hidden, what is public, and the limits of this build.
| What | Who can see it |
|---|---|
| Which note paid | Nobody: the proof shows one of eight same-size notes, not which |
| That you deposited, and how much | Everyone: a deposit is public, from your address |
| The recipient and amount of a payment | Everyone, on chain, as a payment from the pool |
| Your IP address | The first relay only. Services, the credential authority and LI.FI see the last relay |
| Your note secrets, phrase and keys | Only your device. The passkey backup is ciphertext |
| The opened payment (recipient and spend) | Arc: a Chainlink CRE workflow in a secure enclave. XRP Ledger testnet: an ordinary server process |
Limits today
- One relay operator. Opaque runs all six relays. They hide you from the services, but Opaque, running every hop, could link them.
- A ring of eight. On testnet many notes in a pool are test deposits. Keep Wait for privacy on, and don't send right after depositing the same amount.
- Outside the wallet. Your own RPC calls (forge, hardhat) reveal your IP to the RPC provider. Contract source and verification metadata can identify you.
- Linking by habit. Reusing a deployer across projects, or sending a deployer's leftovers back to your account, links them.
Post-quantum: what holds
| Part | How | Quantum-safe |
|---|---|---|
| Your Arc account | ERC-4337 account; FORS+C (hash-based) signatures checked on chain by PQKeyRegistry, keys rotated before they wear out | Yes |
| Contracts from the Contracts screen | Deployed and owned by that account | Yes |
| Notes and the ring proof | Hash commitments; a hash-based (MPC-in-the-head) proof | Yes |
| XRP Ledger pool and app accounts | Each key signs once and is replaced; only its hash is public before then | By timing |
| Deployers | Ordinary secp256k1 keys | Not after first use |
By timing means an attacker with a quantum computer gets about one ledger close (a few seconds) to break a key before it's retired. That's a strong practical defense, not a mathematical guarantee.
Fees
| Network | Paying from notes | Other costs |
|---|---|---|
| Arc | None: the recipient gets the full note | Gas for deposits, swaps and contract calls, in USDC, from your account's address |
| XRP Ledger (XRP) | 0.2001 XRP per note, kept by the pool for ledger costs | AMM fee on swaps; whole notes only, so a remainder under one unit is carried |
| XRP Ledger (tokens) | 0.5 per note | Same as above |
Networks and addresses
| Network | Chain | Explorer |
|---|---|---|
| Arc Mainnet | 5042, https://rpc.mainnet.arc.io | explorer.arc.io |
| Arc Testnet | 5042002, https://rpc.testnet.arc.io | testnet.arcscan.app |
| XRP Ledger Testnet | network 1, wss://s.altnet.rippletest.net:51233 | testnet.xrpl.org |
Arc contracts
| Contract | Mainnet | Testnet |
|---|---|---|
| USDC | 0x3600000000000000000000000000000000000000 (6 decimals; also the 18-decimal gas token) | |
| PQKeyRegistry | 0x33a9d3e188835e8287f55a1a8cb7bd6554ed2faf | 0x6eb5b42373191121d31dfc4b5c8571c4eaf58e8f |
| PQ account factory | 0x5f8bd9531a08b6c06eeb41d9f617038e38d40206 | 0x13beaec42922e3f63fa0dbe5bba270edf46ab214 |
| Pool, 1 USDC | 0xb753058ec7a8cf29d96288a5e5ae5c01c23221ea | 0x7e01b8a883b4dacc9063326a2e7d3eef0d679b63 |
| Pool, 10 USDC | 0xe3d1c899d2700559403341db5dc26577d2669026 | 0x2770e5c2f491fcf0c8a411f6c2c8064214e1e284 |
| Pool, 100 USDC | 0x91c5d56b77b2e1453647d37ca6350194a4c42bd4 | 0x0d8dff9b4903a1b550037bac773e9757f1f4d823 |
| CREATE2 factory | 0x4e59b44847b379578588920cA78FbF26c0B4956C | |
XRP Ledger testnet pools
| Asset | Pool account | Note sizes |
|---|---|---|
| XRP | rw5r7pX37iAtLEh1VZdciHaFpduftUtjYe | 1, 2, 5 … 1,000,000 |
| RLUSD | rPNFdbacMgCk6diE8wtyBxh3ggro2QLWK5 | 1, 2, 5 … 1,000,000 |
| USDC | rfyGTnEH8fLeucbBxxpy24dsgXoFQnHJMZ | 1, 2, 5 … 1,000,000 |
| tMPT | r9J16ivxW5WcED3u5rscm4AhW6W2fPo9L9 | 1, 2, 5 … 1,000,000 |
Each XRPL pool's master key is off; three attesters sign its payouts, any two together.
Note sizes on the XRP Ledger run 1, 2 and 5 of every power of ten, from 1 to 1,000,000. A deposit splits so that every whole amount up to it can be paid exactly: each note is at most half of what is left. 95 XRP becomes 20, 20, 20, 10, 10, 5, 5, 2, 2 and 1 (10 notes), 2,400 XRP 15 notes and 1,000,000 XRP 24. Notes are spent whole with no change, so the fewest, biggest notes would leave most amounts unpayable: 50 + 20 + 20 + 5 can't make 10. Arc deposits split the same way. A size takes deposits once seven others are in it, so a note always has a ring to hide in. Until a large size has seven deposits, a big deposit splits into the largest sizes that do. On testnet, the new sizes start with seven decoy deposits from Opaque.
Questions
If I clear my browser, do I lose my money?
No, if you have your recovery phrase or a passkey backup. Your notes are found again on chain from the phrase. Only an old Browser account (from before recovery phrases) lives solely in the browser: on the XRP Ledger the passkey backup holds its notes; on Arc, its backup file does.
Why can I only send whole amounts?
Each note is spent whole, with no change: change would need a new note, linking the payment to you. The wallet picks the fewest notes that make the amount exactly.
Is my balance private?
Your notes' total is known only to your wallet. What sits at your address, before you deposit it, is public like any address.
Can Opaque freeze or take my funds?
Opaque never holds your keys or note secrets. A payment needs a credential for its recipient; the credential authority could refuse one (for example, a sanctioned address) but can't spend your notes.
