Skip to Content
GuidesPrivate Pay

Private Pay

Pay sends one of your shielded notes to someone else. Funds never leave the shielded pool — the recipient gets a note, not a transfer.

Before you can pay someone

The recipient must have published their payment key on OwnerKeyRegistry. 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. Point them at the Receive page.

Sending

  1. Pick an asset pool.
  2. Select the shielded note to spend.
  3. Enter the recipient’s address.
  4. Confirm.
StepWhat happens
Generate ZK ProofThe pay circuit runs in your browser (Noir / UltraHonk)
Submit Paymentpay() — nullifier spent, output commitment inserted
Confirm On-ChainWait for the Paid event
Notify RecipientAnnounce the note’s private data via StealthAnnouncer
SettlementDone

The whole note moves. Pay is a straight 1-in-1-out passthrough — there’s no change output.

What’s private, what isn’t

ValueVisibility
AmountPrivate
AssetPublic
Which note you spentPrivate (only the nullifier is revealed)
RecipientNot 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.