What is Program Derived Address(PDA)?1

A Program Derived Address is simply an account owned by the program, but has no private key. Instead it’s signature is obtained by a set of seeds and a bump (a nonce which makes sure it’s off curve). “Generating” a Program Address is different from “creating” it.

  • Generating One can generate a PDA using Pubkey::find_program_address in Rust or PublicKey.findProgramAddressSync in JavaScript;
  • Creating a PDA essentially means to initialize the address with space and set the state to it.

#+end_quote

How to Generate a PDA?

In frontend by using @solana/web3.js:

import * as web3 from "@solana/web3.js";
import { Buffer } from 'buffer';

const keypair = await connectWallet();
const programId = "Cj9VjDrohXuz914FNbaTMmEqYXkLkJKZn9FCq4UGGGwr";
const [pda, dump] = web3.PublicKey.findProgramAddressSync(
  [
    Buffer.from("seed1"),
    keypair.publicKey,
  ],
  new web3.PublicKey(programId),
);

Summary:

  1. Seeds can be used to distinguish different purpose of PDA for one program;
  2. Multipe items of bump shall be returned, but the first dump is called “canonical bump”;
  3. bump is needed when to use PDA sign tx on chain via program.

How the PDA be Created By a Program?2

  1. Change the owner of PDA to the specific program by AccountInfo.assign;
  2. Transfer some lamports from funding account to the PDA to pay the rent;
  3. Allocate data space by AccountInfo.realloc.
    // Assessing required lamports and creating transaction instruction
       let lamports_required = Rent::get()?.minimum_balance(ACCOUNT_DATA_LEN);
       let create_pda_account_ix = system_instruction::create_account(
           &funding_account.key,
           &pda_account.key,
           lamports_required,
           ACCOUNT_DATA_LEN.try_into().unwrap(),
           &program_id,
       );
       // Invoking the instruction but with PDAs as additional signer
       invoke_signed(
           &create_pda_account_ix,
           &[
               funding_account.clone(),
               pda_account.clone(),
               system_program.clone(),
           ],
           &[signers_seeds],
       )?;
    

How the PDA be Created By anchor?3

By using #[account(init)] macro attribute:


#[account]
pub struct UserStats {
    level: u16,
    name: String,
    bump: u8,
}

// validation struct
#[derive(Accounts)]
pub struct CreateUserStats<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    // space: 8 discriminator + 2 level + 4 name length + 200 name + 1 bump
    #[account(
        init,
        payer = user,
        space = 8 + 2 + 4 + 200 + 1, seeds = [b"user-stats", user.key().as_ref()], bump
    )]
    pub user_stats: Account<'info, UserStats>,
    pub system_program: Program<'info, System>,
}