For AI agents
These docs are built to be implemented by a coding agent with no human in the loop. Everything you need to integrate Protocol15 is here, with real function names and types. This page is the entry point: the machine-readable endpoints and a single self-contained brief you can follow top to bottom.
Machine-readable endpoints
- /llms.txt an index of every page with a one-line description and a link, following the llms.txt convention. Fetch this first to map the docs.
- /llms-full.txt every page concatenated as plain text in reading order. Fetch this to load the entire documentation set into context in one request.
Ground truth, in order
Read these, in this order, and you have the whole picture:
- How it works the model in plain language.
- Blind Mode, Privacy model, Stealth & announce what is hidden and how.
- Quickstart the minimal end-to-end code, and
Recipes for the helpers it references (
executePlan,spendableNotes, session keys). - Wallet integration or App integration the target surface.
- SDK reference: @p15/stealth, @p15/blind-mode, @p15/compliance every export and type.
- Programs the on-chain ground truth: accounts, instructions, PDAs.
Self-contained integration brief
Implement a Protocol15 integration by following this exactly.
Packages. @p15/stealth (keys, meta-address, scanning), @p15/blind-mode
(proof backend, transfer planning), @p15/compliance (disclosure, optional).
Transactions target @solana/kit. Network is devnet; packages are local
file: deps until publish.
Keys. Derive everything from one signature.
keys = deriveKeys(await wallet.signMessage(keyDerivationMessage(walletAddress, epoch)), epoch).
The signature must be the raw 64 bytes. keys.spendScalar is spend authority and
must never be persisted or sent anywhere. keys.scanSecret is read-only.
Publish encodeMetaAddress({ scanPublic, spendPublic }) as the payment identity.
Backend. const backend = getBackend("noir-v1", { prover: new RemoteProver(url) }).
The prover endpoint builds Groth16 proofs; it never receives the spend scalar.
Flows. Each returns a BackendPlan with transactions[] tagged session
or wallet. Send them in order, each signed by the tagged payer (see
executePlan in Recipes). Each proof must be its own transaction.
- Make private:
backend.planFund({ owner, feePayer, mint, amount, scanSecret, spendScalar, rentFor }). - Send:
backend.planSend({ feePayer, senderScanSecret, senderSpendScalar, senderScanPublic, mint, amount, recipientMetaAddress, rentFor }, inputNote). - Withdraw:
backend.planWithdraw({ feePayer, owner, scanSecret, spendScalar, mint, amount, destination, rentFor }, inputNote). - Balance: build the spendable-note set (below), then
backend.loadBalance(notes).
Scanning and spendable notes. scanAnnouncements(rpc, keys.scanSecret, { indexerUrl }) returns both directions. Do not trust the direction byte to
decide what is spendable. For each announcement, re-derive the note with
deriveNoteForReceive(amount, assetField(mint), scanSecret, spendScalar, ephemeralPublic) and keep it only if its on-chain marker at
findNotePda(commitmentBytes(commitment)) is alive (8-byte p15note\0
discriminator). A payment you sent re-derives to a phantom commitment with no
marker and drops out. See spendableNotes in Recipes. There is
no claim step: an incoming note is immediately spendable.
Hard invariants
Do not violate these. They are correctness, not style.
- Amounts are
bigintbase units. Never floats. SOL and pSOL use 9 decimals, so base units equal lamports. - The spend scalar never leaves the client. Not to the prover, not to a server, not to storage.
- Funding is a separate explicit step. Never auto-wrap public funds to cover a short private send; block instead. Coupling the deposit amount to the send amount de-anonymizes both.
- Prompt counts are fixed: make-private is 1 user signature, blind send is 2 prompts (fund session key, then sign spend), withdraw is 3 (the range proof is a solo transaction). Do not merge proof transactions.
- No claim step. An incoming payment is already spendable.
- Payments are final. No reversal, no sender reclaim. Validate the recipient
meta-address with
isMetaAddressbefore sending. - Auditor selection must match the mint, or on-chain proof verification fails.
What to confirm with a human
The packages are unpublished and devnet-only, mainnet is disabled by decision, and access to the live dApp is invite-gated. If a task implies mainnet, a public deployment, or publishing the packages, stop and confirm; those are out of scope by design.