Deriving PDAs
PDAs are derived from seeds and a program id. find_program_address returns the canonical off-curve address and bump seed required for invoke_signed.
Recipe
let (pda, bump) = Pubkey::find_program_address(
&[b"metadata", mint.key.as_ref()],
program_id,
);When to reach for this:
- Computing addresses in clients before submitting transactions.
- Verifying client-supplied PDAs on-chain.
- Designing multi-tenant seed schemes.
Working Example
pub fn verify_config(config: &AccountInfo, authority: &Pubkey, program_id: &Pubkey, stored_bump: u8) -> ProgramResult {
let (expected, bump) = Pubkey::find_program_address(&[b"config", authority.as_ref()], program_id);
if expected != *config.key || bump != stored_bump {
return Err(ProgramError::InvalidSeeds);
}
Ok(())
}What this demonstrates:
- Includes authority pubkey in seeds for per-user configs.
- Compares derived key and bump to stored values.
- Returns
InvalidSeedson mismatch.
Deep Dive
Algorithm
- Hash seeds + program id + bump until off-curve point found.
- Canonical bump is highest valid (255 downward).
Seed Limits
| Constraint | Value |
|---|---|
| Seeds count | <= 16 |
| Seed len | <= 32 bytes each |
Rust Notes
let bump_seed = [bump];
let signer_seeds: &[&[u8]] = &[b"vault", owner.as_ref(), &bump_seed];Gotchas
- Wrong program id in client - Derives different address.. Fix: Pass program id from deployed key.
- Missing bump in seeds -
invoke_signedfails.. Fix: Append&[bump]slice. - UTF-8 string seeds - Use
.as_bytes()consistently.. Fix: Document encoding. - u64 in seeds - Use fixed-endian bytes:
id.to_le_bytes().. Fix: Avoid ambiguous packing. - Re-derive every time - Wastes CU on-chain.. Fix: Store canonical bump.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| create_program_address | Known bump | Unknown bump |
| Anchor seeds macro | Ergonomic | Native explicit |
| Off-chain only derivation | Save CU | Must verify on-chain |
FAQs
find vs create?
find searches bump; create requires exact bump.
Same seeds different programs?
Different PDAs - by design.
Empty seeds allowed?
Discouraged - use constant prefix.
Pubkey seed order?
Document; changing order changes address.
Bump 255 always?
Not guaranteed - store actual bump.
Client TS derive?
@solana/kit getProgramDerivedAddress.
Test vectors?
Snapshot pda+bump in tests.
Seed collision?
Different semantics need different prefixes.
Program upgrade changes PDA?
No if program id unchanged.
Multiple PDAs per user?
Add disambiguator seed bytes.
Max seeds?
16 seeds per derivation.
On-chain search cost?
Avoid - use stored bump.
Related
- PDA Basics - Intro
- Canonical Bumps - Store bump
- Signing with PDAs - invoke_signed
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.