Core Concepts
Essential concepts every developer needs to understand Priv Protocol
Core Concepts
Understanding these core concepts is essential for building with Priv Protocol. Each concept builds on the others to create a complete private payment system.
Meta-Address
A meta-address is your identity in the Priv Protocol. It's like a bank account number that you can share publicly, but it provides complete privacy for your transactions.
Structure
A meta-address consists of two Ed25519 keypairs:
- Spend Keypair: Controls access to funds (like your private key)
- View Keypair: Enables scanning for payments (like a view key)
import { generateMetaAddress, encodeMetaAddress } from '@priv/sdk';
// Generate the two keypairs
const { spendKeypair, viewKeypair } = generateMetaAddress();
// Encode as a shareable address
const metaAddress = encodeMetaAddress(
spendKeypair.publicKey,
viewKeypair.publicKey
);
console.log(metaAddress);
// Output: priv:ma:a1b2c3d4e5f6...9e8d7c6b5a4f...Format
Meta-addresses use this format:
priv:ma:<spendPubkeyHex><viewPubkeyHex>priv:ma:- Protocol identifier<spendPubkeyHex>- 64-character hex string (32 bytes)<viewPubkeyHex>- 64-character hex string (32 bytes)
Key Responsibilities
- Spend Key: Signs transactions to claim payments. Keep this secret!
- View Key: Scans blockchain for payments. Can be shared with trusted services.
Sharing Your Meta-Address
Your meta-address is safe to share publicly. It's like giving someone your email address - they can send you payments, but they can't see your transaction history or steal your funds.
Stealth Addresses
Stealth addresses are one-time addresses that hide the connection between sender and recipient. Each payment uses a unique address that only the recipient can link back to themselves.
How It Works
- Sender generates an ephemeral keypair
- Sender uses ECDH with recipient's view public key
- Sender derives a unique stealth address
- Recipient scans with their view private key to find payments
import { deriveStealthAddress } from '@priv/sdk';
import { Keypair } from '@solana/web3.js';
// Sender generates ephemeral key
const ephemeralKeypair = Keypair.generate();
// Derive stealth address for recipient
const { stealthAddress, sharedSecret } = deriveStealthAddress(
ephemeralKeypair.secretKey,
recipientViewPubkey,
recipientSpendPubkey
);
console.log('Stealth address:', stealthAddress.toString());
// This address is unique and unlinkable to the recipient's meta-addressPrivacy Benefits
- Unlinkable: No one can tell that multiple stealth addresses belong to the same person
- Untraceable: Transaction graphs don't reveal payment flows
- Forward Secure: Even if keys are compromised, past transactions remain private
Discovery Process
Recipients find their payments by scanning announcements:
import { scanAnnouncements } from '@priv/sdk';
// Scan for payments sent to your meta-address
const payments = await scanAnnouncements(
connection,
viewKeypair.secretKey,
spendKeypair.publicKey
);
payments.forEach(payment => {
console.log(`Found payment: ${payment.amount} tokens`);
});Escrow
Escrows are on-chain vaults that hold tokens until they're claimed. They provide security guarantees and enable gasless claiming.
How Escrows Work
- Sender creates escrow with tokens and claim hash
- Escrow holds tokens on-chain
- Recipient claims with the secret that matches the hash
- Auto-refund returns tokens to sender if unclaimed
// Sender creates escrow
const claimSecret = generateClaimSecret();
const claimHash = hashClaimSecret(claimSecret);
const escrowAddress = await client.createEscrow({
amount: 1000000n,
tokenMint: USDC_MINT,
claimHash,
expirySlot: currentSlot + 432000 // ~30 days
});
// Recipient claims with secret
await client.claimPayment({
escrowAddress,
claimSecret
});Claim Hash System
Escrows use a hash-based claiming system:
import { generateClaimSecret, hashClaimSecret } from '@priv/sdk';
// Generate random 32-byte secret
const claimSecret = generateClaimSecret();
// Hash with protocol prefix: SHA-256("priv_claim" || secret)
const claimHash = hashClaimSecret(claimSecret);
console.log('Secret:', Buffer.from(claimSecret).toString('hex'));
console.log('Hash:', Buffer.from(claimHash).toString('hex'));Security Properties
- Atomic: Either the full amount is claimed or nothing happens
- Time-locked: Automatic refund after expiry (max 30 days)
- Trustless: No intermediaries can steal funds
- Verifiable: All operations are transparent on-chain
Expiry and Refunds
Escrows automatically refund unclaimed tokens:
// Check escrow status
const escrowAccount = await client.getEscrow(escrowAddress);
if (escrowAccount.isExpired) {
// Sender can reclaim their tokens
await client.refundEscrow(escrowAddress);
}Relayer
Relayers are services that pay gas fees for claim transactions. This enables truly gasless payments where recipients don't need SOL to claim their tokens.
How Relayers Work
- Recipient creates unsigned claim transaction
- Relayer validates the transaction
- Relayer adds their signature and pays gas
- Transaction executes with relayer's SOL
// Recipient creates unsigned transaction
const claimTx = await client.buildClaimTransaction({
escrowAddress,
claimSecret,
recipientAddress: wallet.publicKey
});
// Send to relayer for gas payment
const response = await fetch('/api/relayer/submit', {
method: 'POST',
body: JSON.stringify({
transaction: claimTx.serialize(),
escrowAddress: escrowAddress.toString()
})
});
const { signature } = await response.json();
console.log('Claim completed:', signature);Relayer Validation
Relayers validate transactions before signing:
- Valid claim instruction: Transaction must contain legitimate claim_payment instruction
- Correct escrow: Must target an existing, unexpired escrow
- Valid secret: Claim secret must hash to the escrow's claim hash
- No malicious instructions: No additional instructions that could drain relayer funds
Economic Model
Relayers are compensated through:
- Service fees: Small fee deducted from claimed amount
- MEV opportunities: Potential arbitrage from transaction ordering
- Protocol incentives: Future token rewards for providing infrastructure
Announcements
Announcements are on-chain records that help recipients discover payments. They contain the information needed to scan for stealth addresses.
Announcement Structure
Each announcement contains:
interface Announcement {
ephemeralPubkey: PublicKey; // For ECDH computation
stealthAddress: PublicKey; // Where tokens were sent
encryptedAmount: Uint8Array; // Amount encrypted for recipient
tokenMint: PublicKey; // Token type
timestamp: number; // When payment was made
}Scanning Process
Recipients scan announcements to find their payments:
// Get all announcements
const announcements = await client.getAnnouncements({
limit: 1000,
before: latestSlot
});
// Check each announcement
for (const announcement of announcements) {
const isForMe = await client.checkAnnouncement(
announcement,
viewKeypair.secretKey,
spendKeypair.publicKey
);
if (isForMe) {
console.log('Found payment!', announcement);
}
}Privacy Considerations
- Encrypted amounts: Only recipient can decrypt the payment amount
- No recipient info: Announcements don't reveal who the payment is for
- Plausible deniability: Anyone could be scanning any announcement
Payment Links
Payment links are URLs that contain everything needed to claim a payment. They enable sharing payments through any communication channel.
Link Structure
Payment links encode the escrow address and claim secret:
https://app.priv.dev/claim?e=<escrowAddress>&s=<claimSecret>Creating Payment Links
// Create payment with link
const { paymentUrl, escrowAddress } = await client.createPaymentLink({
recipientMetaAddress: 'priv:ma:...',
amount: 1000000n,
tokenMint: USDC_MINT,
message: 'Coffee payment'
});
console.log('Share this link:', paymentUrl);
// https://app.priv.dev/claim?e=ABC123...&s=DEF456...Claiming from Links
Recipients can claim directly from the URL:
// Parse URL parameters
const url = new URL(paymentUrl);
const escrowAddress = new PublicKey(url.searchParams.get('e')!);
const claimSecret = Buffer.from(url.searchParams.get('s')!, 'hex');
// Claim the payment
await client.claimPayment({ escrowAddress, claimSecret });Security Properties
- Self-contained: Links contain all information needed to claim
- One-time use: Each link can only be claimed once
- Expiring: Links become invalid when escrow expires
- Shareable: Safe to send through any communication channel
Putting It All Together
Here's how all concepts work together in a complete payment flow:
- Alice generates a meta-address and shares it publicly
- Bob wants to pay Alice 10 USDC privately
- Bob derives a stealth address for Alice using her view key
- Bob creates an escrow with the tokens and a claim hash
- Bob posts an announcement with the stealth address details
- Bob generates a payment link with escrow address and claim secret
- Bob shares the payment link with Alice (via email, chat, etc.)
- Alice clicks the link and claims the payment (relayer pays gas)
- Alice receives 10 USDC in her wallet with complete privacy
This flow provides:
- Privacy: No one can link the payment to Alice's identity
- Convenience: Alice doesn't need SOL to claim
- Security: Funds are held safely in audited smart contracts
- Flexibility: Payment can be shared through any channel
Next Steps
Now that you understand the core concepts, you're ready to:
- Build Your First Payment - Complete tutorial with code
- Explore Advanced Features - Multi-sig, conditional payments, and more
- Read the API Reference - Detailed documentation for all functions