Understanding Program Derived Addresses in Solana blockchain
A deep dive into Program Derived Addresses (PDAs) in Solana - one of the most powerful and unique features of the Solana blockchain.
Introduction
If you're building on Solana, you've probably heard about Program Derived Addresses (PDAs). They're one of the most powerful and unique features of the Solana blockchain, but they can also be quite confusing at first. In this post, I'll break down what PDAs are, why they exist, and how to use them effectively in your Solana programs.
What are Program Derived Addresses?
Program Derived Addresses (PDAs) are addresses that are deterministically derived from a program ID and a set of seeds. Unlike regular Solana addresses (which have a corresponding private key), PDAs don't have private keys - they exist "off the curve" of the Ed25519 elliptic curve.
// Example: Deriving a PDA
let (pda, bump) = Pubkey::find_program_address(
&[b"user_stats", user.key().as_ref()],
program_id
);Why Do PDAs Exist?
PDAs solve a critical problem in blockchain development: how can a program securely own and control accounts?
In traditional blockchain systems, only entities with private keys can sign transactions and control accounts. But programs don't have private keys - they're just code! PDAs solve this by creating addresses that:
- Are deterministically derived - You can always find the same address using the same seeds
- Don't have private keys - They exist off the Ed25519 curve
- Can only be "signed" by the program - The program can sign for these addresses programmatically
Key Concepts
1. Seeds
Seeds are the inputs used to derive a PDA. They can be:
- Static strings (like
b"vault") - User public keys
- Other identifiers
// Common seed patterns
let seeds = &[
b"user_account", // Static identifier
user.key().as_ref(), // User's public key
&index.to_le_bytes(), // Numeric identifier
];2. Bump Seed
Since PDAs must be off the curve, we use a "bump seed" to find a valid PDA. Solana tries values from 255 down to 0 until it finds an address that's off the curve.
// Finding a PDA with its bump
let (pda, bump) = Pubkey::find_program_address(seeds, program_id);
// Later, you can use this bump to verify
let pda = Pubkey::create_program_address(
&[seeds, &[bump]],
program_id
)?;Practical Example: User Profile System
Let's build a simple user profile system using PDAs:
use anchor_lang::prelude::*;
#[program]
pub mod user_profiles {
use super::*;
pub fn create_profile(
ctx: Context<CreateProfile>,
name: String,
) -> Result<()> {
let profile = &mut ctx.accounts.profile;
profile.owner = ctx.accounts.user.key();
profile.name = name;
profile.created_at = Clock::get()?.unix_timestamp;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateProfile<'info> {
#[account(
init,
payer = user,
space = 8 + 32 + 50 + 8,
seeds = [b"profile", user.key().as_ref()],
bump
)]
pub profile: Account<'info, UserProfile>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct UserProfile {
pub owner: Pubkey,
pub name: String,
pub created_at: i64,
}Benefits of PDAs
1. Deterministic Addresses
You can always find the same account using the same seeds:
// Frontend code to find the same PDA
const [profilePDA] = await PublicKey.findProgramAddress(
[Buffer.from("profile"), userPublicKey.toBuffer()],
programId
);2. No Private Key Management
Programs can sign for PDAs without needing to manage private keys:
// Program can sign for the PDA
let signer_seeds = &[
b"vault",
&[bump],
];
invoke_signed(
&instruction,
&accounts,
&[signer_seeds],
)?;3. Account Relationship Mapping
PDAs are perfect for creating relationships between accounts:
// User's token account PDA
seeds = [b"token_account", user.key().as_ref()]
// User's staking account PDA
seeds = [b"stake", user.key().as_ref(), mint.key().as_ref()]
// NFT metadata PDA
seeds = [b"metadata", mint.key().as_ref()]Common Patterns
Pattern 1: One-Per-User Accounts
seeds = [b"user_data", user.key().as_ref()]This creates one unique account per user.
Pattern 2: One-Per-Token Accounts
seeds = [b"token_vault", mint.key().as_ref()]This creates one unique account per token type.
Pattern 3: Multiple Accounts Per User
seeds = [b"user_nft", user.key().as_ref(), &index.to_le_bytes()]This allows multiple accounts per user, indexed by number.
Common Pitfalls
1. Seed Length Limits
Seeds must be ≤ 32 bytes each, and total seeds ≤ 32 seeds.
// avoid: seed too long
let seeds = &[very_long_string_over_32_bytes];
// prefer: hash long data
let hash = hash(very_long_string);
let seeds = &[hash.as_ref()];2. Forgetting the Bump
// avoid: missing bump
invoke_signed(
&instruction,
&accounts,
&[&[b"vault"]],
)?;
// prefer: include the bump
invoke_signed(
&instruction,
&accounts,
&[&[b"vault", &[bump]]],
)?;3. Seed Ordering
The order of seeds matters! Different orders create different PDAs.
// These create DIFFERENT PDAs:
seeds = [user.key(), mint.key()]
seeds = [mint.key(), user.key()]Testing PDAs
Here's how to test PDA derivation in your tests:
import { PublicKey } from '@solana/web3.js';
describe("PDA Tests", () => {
it("derives the same PDA consistently", async () => {
const [pda1] = await PublicKey.findProgramAddress(
[Buffer.from("profile"), userKeypair.publicKey.toBuffer()],
program.programId
);
const [pda2] = await PublicKey.findProgramAddress(
[Buffer.from("profile"), userKeypair.publicKey.toBuffer()],
program.programId
);
expect(pda1.toString()).toBe(pda2.toString());
});
});Real-World Example: NFT Staking
In my NFT Staking DApp, I use PDAs extensively:
// Stake account PDA - one per user per NFT
seeds = [
b"stake",
user.key().as_ref(),
nft_mint.key().as_ref()
]
// Reward vault PDA - one per staking pool
seeds = [
b"reward_vault",
pool.key().as_ref()
]This ensures each user can only stake each NFT once, and the program controls the reward distribution.
Conclusion
Program Derived Addresses are a fundamental building block of Solana development. They enable:
- Secure program-controlled accounts
- Deterministic address generation
- Clean account relationship management
Once you understand PDAs, you'll find they're essential for building robust Solana programs. They might seem complex at first, but with practice, they become second nature.
Resources
Have questions about PDAs or Solana development? Reach out on X.
enjoyed it?