Skip to main content

Discoverable vs out-of-band notes

Three modes for delivering a note's secret to its recipient.

Discoverable mode

The recipient has registered a viewing key on the keystore. When a sender prepares a transfer, the SDK encrypts the note payload to the recipient's viewing public key (via a fresh ephemeral X25519 keypair and ECDH) and emits it in the on-chain Note event. The event's hint is a fresh random 32-byte value. It is not derived from anyone's keys and carries no targeting information. The recipient's discoverNotes() tries to decrypt Note events with their viewing private key, and a successful decrypt is the "this note is mine" signal.

  • UX win: no out-of-band channel needed. Send-and-forget.
  • Cost: the recipient paid the one-time keystore registration gas.
  • Public surface: the on-chain Note event carries a random 32-byte hint that reveals nothing about sender or recipient. There is no key under which the hint identifies the recipient: discovery is by trial ECDH-decryption, not hint matching.

Out-of-band (OoB) mode

The recipient hasn't registered. The SDK publishes no recipient Note event at all. Only the sender's own change note is announced on-chain. The recipient's noteSecret is returned to the sender off-chain, and the sender must hand it to the recipient directly via a secure channel.

  • UX win: no registration required of the recipient. Anyone with an Ethereum address can receive.
  • Cost: sender must deliver the secret to the recipient. The note isn't claimable until they do.
  • Public surface: only that "some transfer happened", with no recipient hint.

Payment-request mode (recipient per-request key)

A third mode used by payment requests. The recipient generates a per-request derived keypair and includes the derived pubkey inside the payment request itself. The keypair is deterministically derived (HKDF) from the recipient's viewing private key and the paymentId: the same paymentId always re-derives the same keypair, and the viewing private key must stay secret to keep payment-request keys secret. The sender encrypts to that pubkey and writes the request's tag (Poseidon([paymentId])) as the on-chain hint. The recipient finds payments by tag-matching Note events to their open requests, then decrypts each matched event with the request's paymentPrivKey.

  • UX win: the recipient doesn't need a keystore entry to receive a targeted payment, and the sender doesn't need any of the recipient's long-lived keys.
  • Cost: discovery uses request tags to narrow the event stream, so only matching events are decrypted. Prune claimed requests to keep the tag map small.

Choosing between them

The SDK auto-selects. If the recipient's viewing key is in the keystore it uses discoverable mode, and otherwise it falls back to OoB (opt in with allowOutOfBandFallback: true). Payment-request mode is opted into explicitly by generating a request.

ScenarioMode
Two regular users who've both registeredDiscoverable
The recipient hasn't registered, so you deliver the secret directlyOoB
Receiving against a payment requestPayment-request mode