PDAs & Seed Derivation (Beginner Friendly)
Learn Program Derived Addresses with Anchor: deterministic account addresses, seed design, bumps, and safe PDA-based account creation patterns.
PDAs are one of the most important Solana patterns.
If normal keypairs are user-controlled addresses, PDAs are program-controlled addresses derived from predictable seeds.
Why PDAs Exist
You use PDAs when you need predictable addresses for state.
Examples:
- One profile per user
- One review per
(movie, user) - One vault per escrow deal
With PDAs, you can always re-derive the same address from the same seed inputs.
PDA Mental Model
A PDA is derived from:
- Seed bytes (
b"review", user pubkey, etc.) - Program ID
- Bump (single byte Anchor finds for a valid curve-off address)
Anchor makes this practical through account constraints.
Example: Movie Review PDA
#[derive(Accounts)]
#[instruction(movie: String)]
pub struct AddReview<'info> {
#[account(mut)]
pub reviewer: Signer<'info>,
#[account(
init,
payer = reviewer,
space = 8 + MovieReview::INIT_SPACE,
seeds = [b"review", reviewer.key().as_ref(), movie.as_bytes()],
bump,
)]
pub review: Account<'info, MovieReview>,
pub system_program: Program<'info, System>,
}#[account]
#[derive(InitSpace)]
pub struct MovieReview {
pub reviewer: Pubkey,
#[max_len(50)]
pub movie: String,
#[max_len(280)]
pub text: String,
pub rating: u8,
}Seed order matters. review + user + movie is different from review + movie + user.
Updating a PDA Account
Use the same seed formula in update/delete contexts.
#[derive(Accounts)]
#[instruction(movie: String)]
pub struct UpdateReview<'info> {
pub reviewer: Signer<'info>,
#[account(
mut,
seeds = [b"review", reviewer.key().as_ref(), movie.as_bytes()],
bump,
has_one = reviewer,
)]
pub review: Account<'info, MovieReview>,
}Seed Design Rules
- Include a namespace seed first (example:
b"review") - Include identity keys when you need per-user separation
- Include business keys only if they are stable enough
- Keep seed count and size reasonable
Common PDA Mistakes
- Using mutable text as a seed when the value can change later
- Forgetting to reuse the same seed formula in later instructions
- Missing
has_onechecks and trusting only signer presence - Storing data in one PDA but validating another seed set
Security Checklist (PDA Edition)
- Is every PDA derived from deterministic, intentional seeds?
- Does every mutating instruction validate ownership (
has_one, signer)? - Do tests include wrong-seed and wrong-authority attempts?
Try This Next
- Build a
UserProfilePDA with seedb"profile" + user_pubkey. - Add
closeinstruction to reclaim rent when a profile is deleted. - Write a failing test for wrong PDA seed order and confirm rejection.