Free guide
Program Derived Addresses, explained
An address computed from seeds instead of minted from a keypair, guaranteed to have no private key — and how a program signs for it anyway.
An address with no private key
Every keypair-backed account on Solana sits at a point on the ed25519 curve, with a private
key somewhere in the world that can sign for it. A Program Derived Address
is the opposite by construction. It is computed from two inputs: a program's ID and a list of
seeds, arbitrary bytes the program chooses. A string label like
b"page_visits" plus a user's public key is the classic pair. The derivation
deliberately searches for a result that falls off the curve, where no private key
can exist. The search counter, a single byte called the bump, becomes part
of the recipe: reproducing the address later means supplying the same seeds, the same program
ID, and the same bump.
What determinism buys you
Two consequences fall out of that derivation, and every PDA pattern in the ecosystem is one of them wearing different clothes.
- Anyone can recompute the address. A client never asks a program "where is user X's counter?" It derives the answer itself, offline, from public inputs. So can another program, or an indexer, or you with a pen.
- The deriving program can sign for it. No private key means no ordinary signature, ever. But the runtime lets the program that owns the derivation authorize a cross-program invocation on the PDA's behalf by presenting the same seeds. Every program-owned vault that pays rent or releases tokens works this way.
The recipe, in Anchor
The official program-derived-addresses example keeps one page-visit counter
per user. Its state file is small enough to read whole, and it establishes the first habit
worth copying: the seed label lives in one constant, not repeated as a string literal in
every instruction.
// state/page_visits.rs
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)] // automatically calculate the space required for the struct
pub struct PageVisits {
pub page_visits: u32,
pub bump: u8,
}
impl PageVisits {
pub const SEED_PREFIX: &'static [u8; 11] = b"page_visits";
pub fn increment(&mut self) {
self.page_visits = self.page_visits.checked_add(1).unwrap();
}
}
Creating a user's counter is the standard Anchor init, with two additions that
turn a fresh keypair address into a derived one:
// instructions/create.rs
#[derive(Accounts)]
pub struct CreatePageVisits<'info> {
#[account(mut)]
payer: Signer<'info>,
#[account(
init,
space = 8 + PageVisits::INIT_SPACE,
payer = payer,
seeds = [
PageVisits::SEED_PREFIX,
payer.key().as_ref(),
],
bump,
)]
page_visits: Account<'info, PageVisits>,
system_program: Program<'info, System>,
}
From
solana-foundation/program-examples,
MIT licensed, © Solana Foundation, pinned to commit 9389865.
seeds = [PageVisits::SEED_PREFIX, payer.key().as_ref()] is the address recipe:
the constant label plus this specific payer's public key. Anchor derives the
address, confirms nothing already lives there, creates the account, and hands the bump it
found to the handler in ctx.bumps. The example stores that bump in the account,
the second habit worth copying: later instructions supply
bump = page_visits.bump and skip the search entirely.
The client already knows the address
The clearest demonstration of a PDA's core property is in the example's test suite, where the address is derived once, at the top of the file, before any instruction has run:
const [pageVisitPDA] = PublicKey.findProgramAddressSync(
[Buffer.from("page_visits"), payer.publicKey.toBuffer()],
program.programId,
);
Every subsequent call, the create and each increment, reuses that precomputed address. The client never asks the program what it created. It already knew, because the derivation is public and deterministic. When your frontend needs a user's account, this one call replaces a round trip, a lookup table, and a registry that would otherwise have to exist.
Where PDAs go from here
A per-user counter is the teaching case. The same derivation, with different seeds, is how an escrow holds both sides of a swap, how an AMM pool owns its token vaults, and how Token-2022 attaches metadata to a mint. The seeds change; the two properties never do.