TLDR:Documentation Index
Fetch the complete documentation index at: https://docs.chainstack.com/llms.txt
Use this file to discover all available pages before exploring further.
- A Program Derived Address (PDA) is a 32-byte address derived deterministically from seeds and a program ID. No private key exists for it — only the deriving program can sign on its behalf.
- PDAs enable deterministic addressing (same seeds = same address), program-owned authority (vault patterns), and user-scoped state (one account per user).
- A Cross-Program Invocation (CPI) is when one program calls an instruction on another program during execution. Use
invokewhen no PDA signing is needed,invoke_signedwhen the calling program must sign as a PDA. - Anchor simplifies both with
seeds/bumpconstraints for PDAs andCpiContextfor CPIs.
Program derived addresses
On Solana, programs are stateless. All data lives in accounts, and accounts are identified by their 32-byte address. A regular keypair account has a public key (the address) and a private key (used to sign transactions). A PDA is different: it’s an address that is guaranteed to not lie on the Ed25519 curve, which means no private key exists for it. Only the program that derived the PDA can authorize operations on it. This design enables two things that would be impossible with keypair accounts:- Deterministic addressing — the same seeds and program ID always produce the same address. You can derive a user’s vault address from their wallet pubkey without storing it anywhere.
- Program-controlled authority — the program can sign on behalf of the PDA during a cross-program invocation, making the PDA act as a trustless escrow, vault, or authority account.
How derivation works
PDA derivation uses SHA-256. The inputs are:- Optional seeds — up to 16 byte arrays (max 32 bytes each) that you define. These could be strings, public keys, integers, or a combination.
- Program ID — the address of the program that owns the PDA.
- The string
"ProgramDerivedAddress"— a constant marker appended by the runtime. - Bump seed — a single byte (0–255) appended to the seeds to push the hash result off the Ed25519 curve.
findProgramAddress starts at bump 255 and decrements until it finds a valid PDA. Always use the canonical bump — using a non-canonical bump creates a second valid address for the same seeds, which can lead to vulnerabilities.
Limits
| Limit | Value |
|---|---|
| Max seeds per derivation | 16 |
| Max bytes per seed | 32 |
| Bump range | 0–255 |
create_program_address cost | 1,500 CUs |
find_program_address worst-case cost | 1,500 + (1,500 × iterations) |
| Max PDA signers per CPI | 16 |
Deriving a PDA client-side
Deriving a PDA is a read-only operation. It computes an address but does not create an account.Common seed patterns
| Pattern | Seeds | Use case |
|---|---|---|
| Global singleton | ["global"] | Single config account for the entire program |
| Per-user account | ["user", user_pubkey] | One account per user per program |
| Per-user-per-token | ["vault", user_pubkey, mint_pubkey] | Token vaults scoped to both user and mint |
| Sequential record | ["order", user_pubkey, &order_id.to_le_bytes()] | Numbered records per user |
Real-world example: Associated Token Accounts
The Associated Token Account (ATA) program is the most widely used PDA on Solana. Every ATA address is derived from three seeds:Token-2022 mints use
TOKEN_2022_PROGRAM_ID instead of TOKEN_PROGRAM_ID in the seeds. This produces a different ATA address for the same wallet and mint. Always use the correct token program ID for the mint you’re working with.Verifying a PDA on chain
To verify that an on-chain account is the expected PDA, re-derive the address from the known seeds and program ID, then compare it to the account’s address. Fetching account info alone does not prove PDA validity.Get your own node endpoint today
Start for free and get your app to production levels immediately. No credit card required.You can sign up with your GitHub, X, Google, or Microsoft account.Creating PDA accounts in Anchor
Deriving a PDA and creating an account at that PDA are separate operations. Derivation just computes the address. Creating the account allocates on-chain space and pays rent. In Anchor, you use theinit constraint with seeds and bump to create a PDA account. Under the hood, Anchor calls the System Program’s create_account instruction via invoke_signed.
init— tells Anchor to create the account. Requirespayerandspace.seeds = [b"user_data", user.key().as_ref()]— the PDA seeds. The address is deterministic from the user’s wallet.bump(without a value) — Anchor finds the canonical bump automatically.bump = user_account.bump— on subsequent instructions, use the stored bump to save compute (avoids re-runningfind_program_address).space = 8 + UserAccount::INIT_SPACE— 8 bytes for the Anchor discriminator + the struct size. TheInitSpacederive macro calculates the struct size automatically.
Cross-program invocations
A Cross-Program Invocation (CPI) is when one program calls an instruction on another program. This is what makes Solana programs composable — your program can transfer tokens, create accounts, or call any instruction on any deployed program. Solana provides two functions:| Function | When to use | PDA signing |
|---|---|---|
invoke | All signers already signed the transaction | No |
invoke_signed | The calling program needs to sign as a PDA | Yes, via signer seeds |
invoke just calls invoke_signed with an empty signer seeds array. Both use the same runtime syscall.
CPI without PDA signing
The simplest CPI: your program calls another program, and all required signers are already present in the transaction. Example: a program that wraps a SOL transfer through the System Program.CpiContext::new takes the target program and the accounts struct for the instruction. The transfer helper constructs and sends the CPI.
CPI with PDA signing
When your program owns a PDA (such as a vault), it can sign for that PDA during a CPI. This is the core pattern for escrows, vaults, and program-controlled token accounts. Example: a program-controlled vault that holds SOL and allows the program to release it.- Build
signer_seedsfrom the PDA’s seeds plus the bump. - Use
CpiContext::new(...).with_signer(signer_seeds)instead of justCpiContext::new(...). - The runtime verifies the seeds produce the PDA address, then adds it as a valid signer for the inner instruction.
CPI to the Token Program
A common real-world CPI is transferring SPL tokens from a PDA-owned token account. This pattern is used in escrows, vesting contracts, and AMMs.token::authority = vault_authority— ensures the token account is controlled by the program’s PDA, not an arbitrary authority.token::mint = token_mint— binds the vault to a specific mint. Without this, an attacker could pass a vault token account for a different mint.admin: Signer— ensures only an authorized caller can trigger the release. Without caller authorization, anyone could drain the vault.
CPI limits
| Limit | Value |
|---|---|
| Max instruction stack depth | 5 (9 with SIMD-0268) |
| CPI invocation cost | 1,000 CUs (946 with SIMD-0339) |
| Max PDA signers per CPI | 16 |
| Max CPI instruction data | 10 KiB |
| Max return data | 1,024 bytes |
| Max CPI account infos | 128 (255 with SIMD-0339) |
| Serialization cost | 1 CU per 250 bytes |
Indirect reentrancy is not allowed. If program A calls program B, and B tries to call back into A, the transaction fails with
ReentrancyNotAllowed. Direct self-recursion (A calling A) is allowed up to the stack depth limit.Calling a PDA from a client
Once a program creates a PDA account, clients interact with it by deriving the same address and including it in transactions.Common gotchas
These are the issues developers hit most frequently, based on community discussions across Solana and Anchor developer channels.SOL transfer from data-bearing PDAs
This is the single most common PDA mistake. If your PDA stores data (has a#[account] struct), it is owned by your program, not the System Program. Calling system_program::transfer to move SOL out of it fails with:
system_program::transfer with invoke_signed only for PDAs that store no data (declared as SystemAccount in Anchor).
Wrong bump field name in ctx.bumps
The bump field name in ctx.bumps must match the account field name in your #[derive(Accounts)] struct, not the seed prefix:
UnbalancedInstruction after lamport changes
If you manually modify lamports on accounts (add to one, subtract from another) and then do a CPI, you getUnbalancedInstruction. The runtime checks that the total lamport sum across all instruction accounts stays constant when setting up the CPI.
The fix: include all accounts whose lamports were modified in the CPI instruction accounts, even if the target program doesn’t need them.
Validating PDAs from other programs
When your instruction receives a PDA owned by another program (Metaplex metadata, Raydium pool, etc.), useseeds::program to validate it:
seeds::program, Anchor derives the PDA using your program’s ID, which produces a different address.
Security considerations
When working with PDAs and CPIs, follow these practices:- Always use canonical bumps — the bump returned by
findProgramAddress/find_program_address. Non-canonical bumps create alternative valid PDAs for the same seeds, enabling account substitution attacks. - Store and reuse bumps — save the bump in account data at init time, then use
bump = account.bumpin subsequent instructions. This saves compute and avoids re-derivation. - Validate PDA ownership — use Anchor’s
seeds+bumpconstraints to verify accounts are the expected PDAs. Without this, an attacker could pass an arbitrary account. - Check CPI account authority — for token CPIs, use
token::authority = expected_pdato ensure the token account is actually controlled by your program’s PDA. - Avoid seed collisions — use distinct prefixes for different account types (
b"vault",b"metadata",b"config") and include discriminating keys (user pubkey, mint pubkey) to prevent collisions. - Mind the CPI depth — the max instruction stack depth is 5, meaning the entry instruction plus 4 nested CPIs. All nested CPIs share a single compute budget. Design program architecture to minimize CPI chains.
- Watch for reentrancy — Solana blocks indirect reentrancy (A → B → A), but direct self-calls are allowed. If your program calls itself, ensure state is updated before the CPI to prevent double-processing.
Further reading
- Solana documentation: Program derived addresses
- Solana documentation: Cross-program invocations
- Anchor documentation: PDA constraints
- Anchor documentation: CPIs
- Solana program examples: PDA
- Solana program examples: CPI
Reference repos
These are the source repositories we worked against while writing this guide. They stay closer to reality than docs — check them first when something here looks off.- solana-developers/program-examples — canonical PDA and CPI worked examples; account derivations and seed patterns verified here
- solana-foundation/anchor —
find_program_addressandinvoke_signedimplementation; check here when bump or CPI authority rules look off - solana-foundation/solana-bootcamp-2026 — bootcamp projects that exercise PDA and CPI patterns end-to-end