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_signed with 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] where order_id is 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

PropertyWallet keypairPDA
Private key existsYes (user or server custody)No — off-curve address
Signs transactions directlyYes, via walletOnly through owning program + seeds
Address derivationRandom from keygenDeterministic from seeds + program ID
Typical roleUser identity, fee payerProtocol vaults, config, authorities
Explorer appearanceLinked to wallet appsOwner = 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_signed during 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