PDAs in Anchor
Program Derived Addresses have no private key; your program signs for them with seeds and bump via invoke_signed or CPI with_signer.
Search across all documentation pages
Program Derived Addresses have no private key; your program signs for them with seeds and bump via invoke_signed or CPI with_signer.
#[account(
seeds = [b"escrow", maker.key().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,When to reach for this: Program must custody assets or store canonical state at a deterministic address.
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Escrow {
pub maker: Pubkey,
pub escrow_id: u64,
pub bump: u8,
}
// `escrow_id` is an instruction argument, so the accounts struct must
// declare it with #[instruction(..)] before seeds can reference it.
#[derive(Accounts)]
#[instruction(escrow_id: u64)]
pub struct InitEscrow<'info> {
#[account(
init,
payer = maker,
space = 8 + Escrow::INIT_SPACE,
seeds = [b"escrow", maker.key().as_ref(), escrow_id.to_le_bytes().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,
#[account(mut)]
pub maker: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn init_escrow(ctx: Context<InitEscrow>, escrow_id: u64) -> Result<()> {
let escrow = &mut ctx.accounts.escrow;
escrow.maker = ctx.accounts.maker.key();
escrow.escrow_id = escrow_id;
// Anchor does NOT persist the bump for you - write it yourself.
escrow.bump = ctx.bumps.escrow;
Ok(())
}What this demonstrates:
bump finds canonical off-curve bump; ctx.bumps.escrow exposes itEscrow::INIT_SPACE only exists because of #[derive(InitSpace)]#[instruction(..)]Pubkey::find_program_address(seeds, program_id) returns (address, bump). Anchor wraps this in constraints.
Only the program that owns the PDA seeds can sign unless using external program id in seeds (advanced).
| Alternative | Use When | Don't Use When |
|---|---|---|
| Keypair-owned accounts | External signing needed | Program custody |
| seeds::program for external PDAs | CPI to foreign program PDAs | Own program state |
0.32.1 throughout this section.
No. Use seeds constraints and CPI signing.
In your account struct field, set at initialization.
Use @solana/kit 7.0.0 with matching seed bytes.
Your Anchor program's declare_id address.
Not in seeds array; bump is separate parameter to find_program_address.
Compare logged keys; verify seeds and program id client-side.
Yes when holding data; fund with payer on init.
Yes with same signer seeds for each CPI.
See Related links for deeper pdas topics.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 16, 2026