Getting Started
Quick Start
Build your first stealth payment in 5 minutes. This guide covers the complete flow: generate, send, scan, and spend.
Step 1: Generate a stealth identity
The receiver generates a StealthMetaAddress containing spending and viewing keypairs. The public portion is shared with senders.
receiver.rs
use cloak_sdk::StealthMetaAddress;
// Generate a new stealth meta-address
let meta = StealthMetaAddress::generate();
// Save keys securely (private keys never leave this file)
meta.save_to_file("~/.cloak/keys.json")?;
// Share the public portion with senders
let public_address = meta.to_public_string();
println!("Share this: {}", public_address);
// → "stealth1:sol:<spending_pubkey>:<viewing_pubkey>"Step 2: Create a stealth payment
The sender uses the receiver's public meta-address to derive a unique one-time stealth address and an ephemeral keypair via ECDH.
sender.rs
use cloak_sdk::{PublicMetaAddress, StealthPayment};
// Parse the receiver's public meta-address
let recipient = PublicMetaAddress::from_string("stealth1:sol:...")?;
// Create a stealth payment (1 SOL = 1_000_000_000 lamports)
let payment = StealthPayment::create(&recipient, 1_000_000_000)?;
// payment.stealth_address → where to send the SOL
// payment.ephemeral_pubkey → publish on-chain for detection
// payment.amount → the amount in lamportsStep 2b: Send with hidden amount (optional)
Use zero-knowledge proofs to hide the payment amount on-chain:
sender_private.rs
use cloak_sdk::zk::{self, AmountCommitment};
// Generate proving/verifying keys (one-time setup)
let (pk, _pvk) = zk::setup()?;
// Create a Pedersen commitment to the amount
let commitment = AmountCommitment::commit(1_000_000_000);
// Generate a Groth16 proof
let proof = zk::prove(&pk, 1_000_000_000, &commitment)?;
// commitment + proof go on-chain via send_stealth_private
// The amount stays completely privateStep 3: Scan for incoming payments
The receiver scans on-chain announcements to detect payments addressed to them using their viewing key.
receiver_scan.rs
use cloak_sdk::{Scanner, Announcement};
let scanner = Scanner::new(&meta);
// Scan a list of announcements from the chain
let announcements: Vec<Announcement> = fetch_announcements(rpc_url).await?;
let detected = scanner.scan_announcements_list(&announcements)?;
println!("Found {} payments!", detected.len());
for payment in &detected {
println!(" Address: {}", payment.stealth_address);
println!(" Amount: {:?}", payment.amount);
}Step 4: Spend from a stealth address
For each detected payment, the receiver derives the spending keypair and signs a transaction to claim the funds.
receiver_spend.rs
use cloak_sdk::StealthKeypair;
use solana_sdk::transaction::Transaction;
use solana_sdk::system_instruction;
for payment in detected {
// Derive the spending keypair
let keypair = StealthKeypair::derive(&meta, &payment.ephemeral_pubkey)?;
let solana_kp = keypair.to_solana_keypair()?;
// Verify the address matches
assert_eq!(solana_kp.pubkey(), payment.stealth_address);
// Create and sign a transfer transaction
let ix = system_instruction::transfer(
&payment.stealth_address,
&destination_wallet,
payment.amount.unwrap_or(0),
);
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&payment.stealth_address),
&[&solana_kp],
recent_blockhash,
);
client.send_and_confirm_transaction(&tx)?;
}Using the CLI
All of the above can be done from the terminal:
Terminal
# Generate stealth identity
cloak init
# Send a payment
cloak --program-id <PROGRAM_ID> send <meta-address> 0.5
# Send with hidden amount (zk-SNARK)
cloak --program-id <PROGRAM_ID> send <meta-address> 0.5 --private
# Send through relayer (hidden sender)
cloak --program-id <PROGRAM_ID> send <meta-address> 0.5 --relayer http://localhost:3000
# Scan for incoming payments
cloak --program-id <PROGRAM_ID> scan
# Spend from a stealth address
cloak --program-id <PROGRAM_ID> spend <stealth-address> <destination> all --ephemeral <hex>
# View payment history
cloak history --unspentComplete end-to-end example
e2e.rs
use cloak_sdk::{
StealthMetaAddress, PublicMetaAddress,
StealthPayment, StealthKeypair,
Scanner, Announcement,
};
fn main() -> anyhow::Result<()> {
// === RECEIVER SETUP ===
let meta = StealthMetaAddress::generate();
let public_addr = meta.to_public_string();
println!("Public address: {}", public_addr);
// === SENDER CREATES PAYMENT ===
let recipient = PublicMetaAddress::from_string(&public_addr)?;
let payment = StealthPayment::create(&recipient, 500_000_000)?;
println!("Send SOL to: {}", payment.stealth_address);
// === RECEIVER DETECTS PAYMENT ===
let announcement = Announcement {
stealth_address: payment.stealth_address,
ephemeral_pubkey: payment.ephemeral_pubkey,
amount: payment.amount,
timestamp: 0,
};
let scanner = Scanner::new(&meta);
let detected = scanner.scan_announcements_list(&[announcement])?;
assert_eq!(detected.len(), 1);
println!("Payment detected!");
// === RECEIVER DERIVES SPENDING KEY ===
let keypair = StealthKeypair::derive(&meta, &detected[0].ephemeral_pubkey)?;
assert_eq!(keypair.address(), payment.stealth_address);
println!("Spending key derived. Ready to claim funds.");
Ok(())
}