Private Pay
Pay has two tabs: Send, which moves one of your shielded notes to someone else, and Incoming, which lists notes and residual-order proceeds waiting for you to claim. Funds never leave the shielded pool on a send — the recipient gets a note, not a transfer.
Publishing your payment key
Before anyone can pay you, you need to publish your payment key on
OwnerKeyRegistry. Click Register on this page. This writes your
ownerKey — the public value derived from your spending key — to the
registry.
It’s a one-time on-chain transaction per wallet. Until it lands, nobody can send you a private payment, because they have no key to build a note against. Once registered, the page shows Published — ready to receive.
Publishing is safe. An ownerKey lets someone build a note for you. It
does not let anyone spend one, and it doesn’t reveal your spending key.
Before you can pay someone else
The recipient must have published their own payment key the same way. A
wallet address alone isn’t enough to build a note for someone; deriving
their ownerKey requires their own signature.
If the address you enter hasn’t registered, the app refuses rather than inserting a leaf nobody can spend.
Sending
- Pick an asset pool.
- Select the shielded note to spend.
- Enter the recipient’s address.
- Confirm.
| Step | What happens |
|---|---|
| Generate ZK Proof | The pay circuit runs in your browser (Noir / UltraHonk) |
| Submit Payment | pay() — nullifier spent, output commitment inserted |
| Confirm On-Chain | Wait for the Paid event |
| Notify Recipient | Announce the note’s private data via StealthAnnouncer |
| Settlement | Done |
The whole note moves. Pay is a straight 1-in-1-out passthrough — there’s no change output.
What’s private, what isn’t
| Value | Visibility |
|---|---|
| Amount | Private |
| Asset | Public |
| Which note you spent | Private (only the nullifier is revealed) |
| Recipient | Not on the pay() call itself |
The amount stays private because pay never sums anything — the spent note’s
amount moves to the output note unchanged. That meant the circuit only needed
a real output-commitment check, not the value-conservation and range-proof
machinery a multi-input/multi-output system would require.
The circuit genuinely recomputes the output commitment from the spent note’s own amount and asserts equality. That check matters: without it, anyone could pair a proof for spending a trivial note with an arbitrary, uncapped output commitment. An earlier version of the circuit had exactly that hole — a self-referential assertion that was always true — and it’s covered by a dedicated regression test now.
No compliance gate here
pay is not sanction-screened, on purpose. There is no plaintext recipient to
screen — the output is a commitment against an ownerKey, not an address.
Screening applies at withdrawal, when funds
re-enter the open.
The notify step
After the payment confirms, the app announces the note’s private data —
assetId, amount, blinding, commitment — through StealthAnnouncer so
the recipient can find and spend it.
That announcement is in the clear. As explained in
Note Delivery, it adds no privacy loss beyond
what pay() already puts on-chain: the blinding is the only genuinely new
value, and on its own it doesn’t let anyone but the key holder spend the note.
Claiming incoming payments
The Incoming tab lists notes someone sent you with pay, discovered
through StealthAnnouncer announcements addressed to your ownerKey.
Claiming saves one to your shielded notes, after which you can spend it like
any other.
The note already exists on-chain the moment the sender’s transaction confirmed — claiming doesn’t create anything or cost gas. It reads the announced private values, verifies they reconstruct a commitment you actually own, and writes the note into your local wallet so it becomes spendable.
Nothing is lost if you don’t claim promptly. The announcement stays in the chain’s event log, and your wallet finds it on the next scan.
A dark-pool match’s proceeds arrive the same way and show up in this same Incoming tab. A partially-filled order’s leftover, though, is a residual order rather than a note — that’s claimed on the Swap page instead, in My Orders.