Integrate into an app
An app (a storefront, payroll, a tipping widget) integrates Protocol15 to accept private payments and optionally to let users pay privately. This is a smaller surface than a wallet: mostly a receiving identity and a way to detect incoming payments. It builds on the Quickstart and Recipes.
Accept private payments
1. A receiving identity
The app derives its own key set the same way a user does, from a service wallet or a per-merchant wallet, and publishes the meta-address as its "pay me" target.
const keys = deriveKeys(await merchantWallet.signMessage(keyDerivationMessage(merchantAddress, epoch)), epoch);
const payTo = encodeMetaAddress({ scanPublic: keys.scanPublic, spendPublic: keys.spendPublic });Show payTo (text or QR) wherever you currently show an address. Every payment to
it lands at a fresh one-time location, so your receipts are not linkable on chain.
2. Detect incoming payments
Poll the announce anchor with your scanSecret and keep the incoming direction.
Decrypting a memo with your scan secret is itself the proof a payment is yours. For
a busy endpoint, run the indexer so each poll is one
request.
import { scanAnnouncements } from "@p15/stealth";
const found = await scanAnnouncements(rpc, keys.scanSecret, { indexerUrl });
for (const d of found.filter((a) => a.announcement.direction === 0)) {
reconcile({
amount: d.announcement.amount, // exact base units
signature: d.signature, // stable, use as idempotency key
at: d.blockTime,
memo: d.announcement.note, // optional payer-supplied memo
});
}The signature is a stable idempotency key, so re-scanning never double-counts.
3. Reconcile against an order
Amounts are exact, so match an incoming payment to an invoice by amount plus a
time window. For a stronger link, ask the payer to include a memo via
planSend({ note }) and read d.announcement.note.
Spending what you received
A received payment is a spendable note. To pay out or move funds, use
spendableNotes (Recipes) to list them, then planSend or
planWithdraw exactly as a wallet does. To move takings to a public balance for
accounting or off-ramp, planWithdraw into a public token account you control
(a public moment).
Let users pay your app privately
If your app also sends, derive the paying user's keys, build the backend, and call
planSend toward your merchant meta-address. Same flow as a wallet; see the
Quickstart step 5.
Compliance
When you need to prove a payment to an auditor or counterparty, issue a scoped
disclosure for just those transactions. It is signed,
chain-verifiable, and revocable, and never exposes your other activity. If your
jurisdiction requires pre-deposit screening, wire getScreeningProvider as an
async pre-check before accepting funds.
Notes
- One scan returns both received (
0) and sent (1) history; you usually only need received. - Amounts are
bigintbase units. - Decryption is always client-side; the indexer only ever sees ciphertext.