Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
PDAs are addresses that aren't on the ed25519 curve — meaning no private key exists for them. A program can 'sign' on behalf of its own PDAs because the runtime knows which PDA belongs to which program. This is the foundation of every cross-program flow, every escrow, every protocol that needs deterministic state addresses.
Derive a PDA and use it as program-controlled state.
seeds = [b"vault", userPubkey] and confirm the same address is derived on-chain via Anchor's find_program_address.invoke_signed) to release them.Use these three in order. Each builds on the one before.
In one paragraph, explain what a PDA is and what 'no private key' means.
Walk me through PDA derivation — what `findProgramAddress` does and what the 'bump' is for.
Given a multi-user escrow that needs deterministic PDAs per (user, counterparty) pair, design the seeds layout to ensure no collision and easy off-chain lookup.
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct CreateVault<'info> {
#[account(
init,
payer = user,
space = 8 + 8,
// PDA: address derived from seeds + program_id
// Off-chain: PublicKey.findProgramAddressSync(seeds, programId)
// Returns (address, bump). Bump = nonce that pushes off-curve.
seeds = [b"vault", user.key().as_ref()],
bump
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(
mut,
seeds = [b"vault", user.key().as_ref()],
bump = vault.bump, // saved at create time
)]
pub vault: Account<'info, Vault>,
pub user: Signer<'info>,
}
#[account]
pub struct Vault {
pub balance: u64,
pub bump: u8,
}
// Off-chain (TS):
// import { PublicKey } from "@solana/web3.js";
// const [vaultPDA, bump] = PublicKey.findProgramAddressSync(
// [Buffer.from("vault"), user.publicKey.toBuffer()],
// PROGRAM_ID,
// );