Create a payment request
What this lets you do
Generate a shareable request so a payer can send you a specific amount without either of you revealing a wallet address. It carries a unique paymentId, a public tag derived from it, and a per-request key the payment is encrypted to. A payment stamped with the tag traces back to this specific request, so incoming private transfers reconcile to your invoice on their own.
Constraints & limits
- Public request shape. The shareable
PaymentRequestcarriespaymentId,noteAddressHashes,amount,tokenId,tag, andpaymentPubKey. It never carries the matchingnoteSecretvalues or the request private key. - Recipient-local recovery state. The SDK stores the request's
noteSecretsMaplocally and re-derivespaymentPrivKeyfrom yourviewingPrivateKey,ownerAddress, andpaymentIdwhen the request is loaded. Discovery usespaymentPrivKeyto decrypt a matching Note event, then uses the decodednoteAddressHashto look up the spendablenoteSecret. IfnoteSecretsMapis lost, you can identify the matched payment but cannot reconstruct the note. - Requires NotePayload v2. Payment-request matching reads the request's slot data (
noteAddressHash) from the encrypted note payload's v2 codec, so the sender's build must emit the v2 payload. - Tag is not authenticated by the protocol. A malicious sender could stamp a payment with someone else's
tag. The recipient sees the payment under "this request" but the sender chose the tag. Pair with off-chain signing if non-repudiation matters.
What it unlocks next
- Discover incoming payments by paymentId: discovery tag-matches Note events on the request's
tag, then decrypts each hit with the request'spaymentPrivKeyafter the payment lands. - Invoice-style UX: a recipient generates request #N, attaches it to their invoice software, scans periodically for matched payments.
- Bridge use cases where the payment needs to be tied to an off-chain identifier (order ID, contract reference) without exposing it publicly.
Why discovery is fast: each request has a public tag derived from its paymentId. Discovery uses that tag to find candidate Note events, then decrypts only the matching candidates with the request key. Ordinary discoverable transfers use your viewing key instead.
How to use it
const toHex = (value: bigint): `0x${string}` => `0x${value.toString(16)}`;
const paymentRequest = await session.generatePaymentRequest({
amount: toHex(parseUnits("0.5", 6)), // 0.5 USDC
tokenId: USDC_SEPOLIA,
});
Handling payment-request links. Wallets or frontends may bundle recovery material such as noteSecretsMap or the request private key into their payment-request links, so treat any such link as sensitive and only send it over a secure channel.
How the payer fulfills it
The payer calls prepareFulfillPaymentRequest, the wrapper that builds the transfer for a request you sent them. It encrypts the note to the request's paymentPubKey and sets the tag as the Note event hint, so only your request state can decrypt it.
// On the payer's side, after receiving the request:
const prepared = await session.prepareFulfillPaymentRequest({
paymentRequest,
inputCommitments: [noteToSpend.commitment],
});
const result = await session.relayTransfer(prepared.relayOptions[0]);
The result submits like an ordinary transfer.
Behind the scenes
session.generatePaymentRequest({amount, tokenId})viewingPrivateKey, your ownerAddress, and the request's paymentId. The derivation is deterministic rather than random, so loading the request re-derives the same keypair. Disclosing one request's paymentPrivKey does not expose your viewing key, other requests, or non-request incoming payments.