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:
Are derived deterministically from seeds and a program ID
Do not have a corresponding private key
Can only be signed by the program that derived them
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