Quickstart
The shortest accurate path from zero to a blind transfer, using the real API and
the real flow the dApp uses. Read How it works for the mental
model and Concepts for why each step exists. The helpers
referenced here (executePlan, spendableNotes, session keys) are in
Recipes.
Early access. Protocol15 runs on devnet only, and the
@p15/*packages are not published to npm yet. Today you consume them from local checkouts withfile:paths. The import lines below are the published names so the code matches the future release. The API does not change.
1. Install
npm install @p15/stealth @p15/blind-mode @p15/compliance @solana/kit2. Derive keys
Every key derives from one ed25519 signature. No seed phrases. The spendScalar
is spend authority and never leaves the client.
import { deriveKeys, keyDerivationMessage, encodeMetaAddress } from "@p15/stealth";
const epoch = 0;
const keys = deriveKeys(await wallet.signMessage(keyDerivationMessage(walletAddress, epoch)), epoch);
const myMetaAddress = encodeMetaAddress({
scanPublic: keys.scanPublic,
spendPublic: keys.spendPublic,
});3. Build the backend
import { getBackend, RemoteProver } from "@p15/blind-mode";
const backend = getBackend("noir-v1", { prover: new RemoteProver(process.env.PROVER_URL!) });
const rentFor = (space: bigint) => rpc.getMinimumBalanceForRentExemption(space).send();4. Make funds private
The one explicit funding step, and the one public moment on the way in. For SOL you wrap into a wSOL ATA and deposit in the same transaction, so make-private stays a single wallet prompt.
import { getTransferSolInstruction } from "@solana-program/system";
import { getCreateAssociatedTokenIdempotentInstruction, getSyncNativeInstruction } from "@solana-program/token";
import { lamports } from "@solana/kit";
const plan = await backend.planFund({
owner: walletSigner,
feePayer: walletSigner,
mint, // wSOL mint for SOL
tokenProgram,
amount: 1_000_000_000n, // base units (lamports for SOL)
scanSecret: keys.scanSecret,
spendScalar: keys.spendScalar,
rentFor,
});
// Wrap SOL, then deposit, in one wallet-signed transaction:
const ixs = [
getCreateAssociatedTokenIdempotentInstruction({ payer: walletSigner, ata: wsolAta, owner: walletAddress, mint }),
getTransferSolInstruction({ source: walletSigner, destination: wsolAta, amount: lamports(1_000_000_000n) }),
getSyncNativeInstruction({ account: wsolAta }),
...plan.transactions[0].instructions,
];
await sendWithWallet(ixs);5. Send a blind transfer
Two wallet prompts total: one already spent funding the session key (step 4 of Recipes), one is implicit in the session signing. Pick an input note from your scanned, marker-checked notes.
const notes = await spendableNotes(keys); // Recipes
const inputNote = pickInputNote(notes, 250_000_000n); // Recipes
const plan = await backend.planSend(
{
feePayer: sessionSigner,
senderScanSecret: keys.scanSecret,
senderSpendScalar: keys.spendScalar,
senderScanPublic: keys.scanPublic,
mint,
amount: 250_000_000n,
recipientMetaAddress: theirMetaAddress,
rentFor,
},
inputNote,
);
await executePlan(plan, { rpc, rpcSubscriptions, sessionSigner, walletSigner });There is no claim step: an incoming payment is already a spendable note for the recipient.
6. Receive
The recipient scans one fixed anchor and re-derives their notes. spendableNotes
already does this; the underlying call is:
import { scanAnnouncements } from "@p15/stealth";
const found = await scanAnnouncements(rpc, keys.scanSecret, { indexerUrl });
// direction 0 = incoming, 1 = your own self-receipt7. Withdraw (optional)
Move a private note back to public SOL. Three transactions (the range proof is a solo tx), and a public moment.
const plan = await backend.planWithdraw(
{ feePayer: sessionSigner, owner: walletSigner, scanSecret: keys.scanSecret,
spendScalar: keys.spendScalar, mint, tokenProgram, amount, destination, rentFor },
inputNote,
);
await executePlan(plan, { rpc, rpcSubscriptions, sessionSigner, walletSigner });Next
- Integrate into a wallet — the full surface.
- Integrate into an app — accept payments.
- Auditing & disclosure — prove transactions selectively.
- SDK reference and Programs — the ground truth.