Skip to main content
Program Derived Addresses (PDAs) are deterministic addresses derived from a program ID and a set of seeds. PDAs enable programs to sign for accounts without requiring a private key.

What are PDAs?

PDAs are addresses that:
  1. Are derived deterministically from seeds and a program ID
  2. Do not have a corresponding private key
  3. Can only be signed by the program that derived them
  4. Are guaranteed to not lie on the ed25519 curve

Finding PDAs

To find a PDA, use Pubkey::find_program_address:
let (pda, bump) = Pubkey::find_program_address(
    &[b"vault", user.key().as_ref()],
    program_id
);
The bump is a value (0-255) that ensures the address doesn’t lie on the curve.

Using PDAs in Anchor

Anchor simplifies PDA usage with the seeds and bump constraints:

Initializing PDAs

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = user,
        space = 8 + 32 + 8,
        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>,
}
Anchor automatically:
  • Derives the PDA address
  • Finds the bump seed
  • Validates the derived address matches the provided account

Accessing PDAs

#[derive(Accounts)]
pub struct Access<'info> {
    #[account(
        seeds = [b"vault", user.key().as_ref()],
        bump
    )]
    pub vault: Account<'info, Vault>,
    pub user: Signer<'info>,
}
For existing PDAs, Anchor validates the address without the init constraint.

Storing the bump

Store the bump seed in your account to avoid recalculating it:
#[account]
pub struct Vault {
    pub authority: Pubkey,
    pub bump: u8,
}

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    let vault = &mut ctx.accounts.vault;
    vault.authority = ctx.accounts.user.key();
    vault.bump = ctx.bumps.vault;  // Access the bump
    Ok(())
}
Then use it in future instructions:
#[account(
    seeds = [b"vault", user.key().as_ref()],
    bump = vault.bump  // Use stored bump
)]
pub vault: Account<'info, Vault>,

Signing with PDAs

Programs can sign CPIs using PDA seeds:
pub fn transfer_from_vault(ctx: Context<TransferFromVault>, amount: u64) -> Result<()> {
    let seeds = &[
        b"vault",
        ctx.accounts.authority.key().as_ref(),
        &[ctx.accounts.vault.bump],
    ];
    let signer_seeds = &[&seeds[..]];

    let cpi_context = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        Transfer {
            from: ctx.accounts.vault_token_account.to_account_info(),
            to: ctx.accounts.destination.to_account_info(),
            authority: ctx.accounts.vault.to_account_info(),
        },
        signer_seeds,
    );

    transfer(cpi_context, amount)?;
    Ok(())
}

PDA use cases

1. Deterministic account addresses

Create predictable addresses for user-specific accounts:
// User profile PDA
seeds = [b"profile", user.key().as_ref()]

// Token vault PDA
seeds = [b"vault", mint.key().as_ref(), user.key().as_ref()]

2. Program authority

Use PDAs as authorities for tokens or other accounts:
#[derive(Accounts)]
pub struct InitializeVault<'info> {
    #[account(
        init,
        payer = authority,
        seeds = [b"vault"],
        bump,
        token::mint = mint,
        token::authority = vault
    )]
    pub vault: Account<'info, TokenAccount>,
    // ...
}

3. Account relationships

Enforce relationships between accounts:
// Game account PDA derived from player
seeds = [b"game", player.key().as_ref()]

// Item PDA derived from game and item ID
seeds = [b"item", game.key().as_ref(), &item_id.to_le_bytes()]

Multiple seeds

You can use multiple seeds for more specific PDAs:
#[account(
    init,
    payer = user,
    space = 8 + 32,
    seeds = [
        b"escrow",
        user.key().as_ref(),
        mint.key().as_ref(),
        &escrow_id.to_le_bytes()
    ],
    bump
)]
pub escrow: Account<'info, Escrow>,

Complete example

use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod pda_example {
    use super::*;

    pub fn initialize_vault(ctx: Context<InitializeVault>) -> Result<()> {
        let vault = &mut ctx.accounts.vault;
        vault.authority = ctx.accounts.authority.key();
        vault.bump = ctx.bumps.vault;
        msg!("Vault initialized at {}", vault.key());
        Ok(())
    }

    pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
        let authority_key = ctx.accounts.authority.key();
        let seeds = &[
            b"vault",
            authority_key.as_ref(),
            &[ctx.accounts.vault.bump],
        ];
        let signer_seeds = &[&seeds[..]];

        let cpi_context = CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            Transfer {
                from: ctx.accounts.vault_token_account.to_account_info(),
                to: ctx.accounts.user_token_account.to_account_info(),
                authority: ctx.accounts.vault.to_account_info(),
            },
            signer_seeds,
        );

        token::transfer(cpi_context, amount)?;
        msg!("Withdrew {} tokens from vault", amount);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct InitializeVault<'info> {
    #[account(
        init,
        payer = authority,
        space = 8 + 32 + 1,
        seeds = [b"vault", authority.key().as_ref()],
        bump
    )]
    pub vault: Account<'info, Vault>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Withdraw<'info> {
    #[account(
        seeds = [b"vault", authority.key().as_ref()],
        bump = vault.bump,
        has_one = authority
    )]
    pub vault: Account<'info, Vault>,
    #[account(mut)]
    pub vault_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub user_token_account: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
    pub token_program: Program<'info, Token>,
}

#[account]
pub struct Vault {
    pub authority: Pubkey,
    pub bump: u8,
}

Best practices

Store the bump seed in your account to avoid recalculating it on every instruction.
Be careful with seed choice - anyone can derive the same PDA with the same seeds, so ensure seeds uniquely identify the resource.
PDAs cannot sign transactions - they can only sign CPIs within a program.

Next steps

Cross-program invocations

Learn how to use PDAs with CPIs

SPL integrations

Use PDAs with token accounts

Build docs developers (and LLMs) love