Guide
Solana program derived addresses explained
On most blockchains, an address implies someone holds a private key. Solana breaks that assumption with program derived addresses (PDAs) — on-chain accounts whose addresses are computed from a program ID plus optional seeds, with no corresponding secret key anywhere. Programs use PDAs to custody SOL and tokens, store protocol state, and act as authorities during cross-program invocations. If you have ever wondered why a swap router sends tokens to a random-looking address that your wallet cannot sign for, you were probably looking at a PDA.
Why PDAs exist
Solana stores all state in accounts keyed by public keys. A program that needs to hold escrowed SOL, mint NFTs under its own authority, or maintain a global config must own those accounts. But a program is just bytecode — it cannot hold a hardware wallet or export a seed phrase.
PDAs solve this by letting the runtime derive an address that:
- Is deterministic — anyone can recompute the same address from the same inputs.
- Has no private key — it lies off the Ed25519 curve, so no keypair can sign for it directly.
- Can still authorize actions when the owning program calls
invoke_signedwith the correct seeds.
Think of a PDA as a program-controlled safe: only the program that derived it (and knows the seed recipe) can open it, and only inside a transaction that program is executing. Users never import PDAs into Phantom — they interact with them indirectly through instructions the program exposes.
Seeds, bumps, and findProgramAddress
A PDA is found by hashing together:
- One or more seed byte strings (UTF-8 labels, pubkeys, counters, anything the program defines).
- The program ID that will own the resulting account.
- A single-byte bump (255 down to 0) that forces the result off-curve.
Client SDKs expose this as findProgramAddress(seeds, programId), which returns
[address, bump]. The bump is stored in on-chain account data so later
instructions do not need to brute-force it again. Conventionally programs pick the
canonical bump — the highest valid value (starting at 255) — and persist
it at account creation.
Designing seed schemes
Good seed design is a security property. Common patterns:
- Global singleton: seeds like
["config"]for one protocol-wide settings account. - Per-user state: seeds like
["vault", user_pubkey]so each wallet gets an isolated PDA. - Per-asset escrow: seeds like
["escrow", order_id]whereorder_idis a unique nonce or hash.
Collisions are impossible across different seed tuples for the same program, but reusing
seeds for different account types is a common audit finding. Namespace seed prefixes
("vault" vs "metadata") so a bug in one instruction cannot
overwrite unrelated state.
How programs sign as a PDA
Normal transactions require Ed25519 signatures from every account marked is_signer.
PDAs cannot produce those signatures — instead, the runtime treats a program as a signer
when it calls invoke_signed and passes the exact seed slice plus bump that
produced the PDA.
This is how a lending protocol moves collateral from a user-owned token account into a
program vault PDA, or how an NFT program mints to a metadata PDA it created at init.
The outer transaction is signed by the user; inner CPIs signed by the program use PDA
seeds. See our
CPI guide for how
invoke vs invoke_signed differ and why account metadata must
list the PDA as a signer even though no wallet popup appears.
What users see in explorers
On Solscan, PDAs look like any other address — but the "owner" field shows the program ID, and there is no associated wallet. When you read a transaction, multiple accounts with the same program owner and odd-looking addresses are often PDAs participating in one atomic flow (pool vault, oracle config, user position account).
Common PDA patterns in production
Token vaults and escrows
DeFi programs keep pooled assets in PDA-owned SPL token accounts. The associated token account address itself may be a PDA (ATA program) or a PDA-chosen vault with seeds referencing the pool ID. Escrow programs lock buyer and seller funds until release conditions are met; only the escrow program's logic can move them out.
Metaplex metadata and editions
NFT standards derive metadata and master edition accounts as PDAs from mint addresses plus fixed seed strings. That guarantees one canonical metadata account per mint without a central registry. Compressed NFTs push merkle proofs instead, but legacy collections still surface PDAs constantly in wallet portfolios.
Program upgrade and config authorities
Some deployments store upgrade authority or fee recipient pubkeys in PDA config accounts rather than hard-coding them in bytecode. Governance multisigs later point those fields to new addresses through signed admin instructions.
Deterministic client addressing
Frontends call the same findProgramAddress offline to show users where funds
will land before they sign. Mismatches between client seeds and on-chain expectations are a
frequent source of "account not initialized" errors — always mirror the program's seed
constants exactly, including string encoding and pubkey byte order.
Security and footguns
- Missing bump validation — programs must verify the passed bump reproduces the claimed PDA; otherwise attackers substitute addresses they control.
- Signer privilege escalation — marking the wrong account as a PDA signer in CPI lets a malicious program drain vaults. Auditors focus heavily on seed checks before
invoke_signed. - Rent and closure — PDA accounts still need rent-exempt lamports like any data account. Closing a PDA returns lamports to a destination the program specifies; buggy close paths can brick user funds. Our account model guide covers rent minimums.
- Seed brute-force is not a attack vector — finding a PDA collision for someone else's seeds is computationally infeasible; risks are logic bugs, not guessing addresses.
- PDAs are not privacy — addresses are public and deterministic. Do not use predictable seeds to hide sensitive relationships on-chain.
PDAs vs regular keypairs — quick comparison
| Property | Wallet keypair | PDA |
|---|---|---|
| Private key exists | Yes (user or server custody) | No — off-curve address |
| Signs transactions directly | Yes, via wallet | Only through owning program + seeds |
| Address derivation | Random from keygen | Deterministic from seeds + program ID |
| Typical role | User identity, fee payer | Protocol vaults, config, authorities |
| Explorer appearance | Linked to wallet apps | Owner = program; no wallet UI |
Building and testing with PDAs
Anchor abstracts much of this with #[account(seeds = ..., bump)] constraints
that auto-validate addresses at runtime. Raw Rust programs call
Pubkey::find_program_address and thread bumps through account structs.
Before mainnet, use
transaction simulation
on devnet to confirm seed tuples match — simulation errors like "seeds constraint violated"
almost always mean client and program disagree on seed ordering or bump storage.
When deploying your own program, PDAs become the boundary between user keys and protocol logic. Document seed recipes in your SDK; integrators who guess wrong will file support tickets even if your on-chain code is correct.
Key takeaways
- PDAs are deterministic addresses without private keys, owned by a specific program.
- They are derived from seeds + program ID + bump using
findProgramAddress. - Programs authorize PDA actions via
invoke_signedduring CPI, not wallet signatures. - Vaults, escrows, NFT metadata, and config accounts are the everyday use cases users touch indirectly.
- Correct seed validation and bump checks are critical — most PDA exploits are logic errors, not cryptography breaks.
Related reading
- Solana account model — lamports, owners, rent, and how PDAs fit the universal account layout
- Cross-program invocation (CPI) — invoke_signed, signer arrays, and composable DeFi flows
- SPL token accounts — mints, ATAs, and why token balances live in separate addresses
- Deploy a Solana program — from build artifacts to upgradeable program IDs that own PDAs