Priv Protocol
Getting Started

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

  1. Sender generates an ephemeral keypair
  2. Sender uses ECDH with recipient's view public key
  3. Sender derives a unique stealth address
  4. 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-address

Privacy 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

  1. Sender creates escrow with tokens and claim hash
  2. Escrow holds tokens on-chain
  3. Recipient claims with the secret that matches the hash
  4. 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

  1. Recipient creates unsigned claim transaction
  2. Relayer validates the transaction
  3. Relayer adds their signature and pays gas
  4. 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 are URLs that contain everything needed to claim a payment. They enable sharing payments through any communication channel.

Payment links encode the escrow address and claim secret:

https://app.priv.dev/claim?e=<escrowAddress>&s=<claimSecret>
// 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...

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:

  1. Alice generates a meta-address and shares it publicly
  2. Bob wants to pay Alice 10 USDC privately
  3. Bob derives a stealth address for Alice using her view key
  4. Bob creates an escrow with the tokens and a claim hash
  5. Bob posts an announcement with the stealth address details
  6. Bob generates a payment link with escrow address and claim secret
  7. Bob shares the payment link with Alice (via email, chat, etc.)
  8. Alice clicks the link and claims the payment (relayer pays gas)
  9. 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:

On this page