Raj

@rajgoesout

PDAs

Anchor → Pinocchio

Anchor Pinocchio
#[account(seeds = [...], bump)] Address::find_program_address()
Automatic PDA derivation Manual derivation and validation
ctx.bumps.account_name Store bump in state or re-derive
seeds::program = other_program Pass program_id explicitly

Derive PDA

use pinocchio::Address;

let (pda, bump) = Address::find_program_address(
    &[b"vault", authority.as_ref()],
    program_id,
);

Anchor equivalent:

#[account(
    seeds = [b"vault", authority.key().as_ref()],
    bump
)]
pub vault: Account<'info, Vault>,

Validate PDA

fn validate_pda(
    account: &AccountView,
    authority: &AccountView,
    program_id: &Address,
) -> Result<u8, ProgramError> {
    let (expected, bump) = Address::find_program_address(
        &[b"vault", authority.address().as_ref()],
        program_id,
    );

    if account.address() != &expected {
        return Err(ProgramError::InvalidAccountData);
    }

    Ok(bump)
}

Create PDA Account

Anchor handles PDA creation with init. In Pinocchio, derive seeds and call CreateAccount:

use pinocchio::cpi::{Signer, Seed};
use pinocchio_system::instructions::CreateAccount;
use pinocchio::sysvars::Rent;

fn create_pda(
    payer: &AccountView,
    pda_account: &AccountView,
    authority: &AccountView,
    program_id: &Address,
) -> ProgramResult {
    let (expected, bump) = Address::find_program_address(
        &[b"vault", authority.address().as_ref()],
        program_id,
    );

    if pda_account.address() != &expected {
        return Err(ProgramError::InvalidAccountData);
    }

    let rent = Rent::get()?;
    let bump_bytes = [bump];
    let seeds: [Seed; 3] = [
        Seed::from(b"vault"),
        Seed::from(authority.address().as_ref()),
        Seed::from(&bump_bytes),
    ];
    let signer = Signer::from(&seeds);

    CreateAccount {
        from: payer,
        to: pda_account,
        lamports: rent.try_minimum_balance(Vault::LEN)?,
        space: Vault::LEN as u64,
        owner: program_id,
    }.invoke_signed(&[signer])
}

Anchor equivalent:

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = payer,
        space = 8 + Vault::INIT_SPACE,
        seeds = [b"vault", authority.key().as_ref()],
        bump
    )]
    pub vault: Account<'info, Vault>,
    pub authority: AccountInfo<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

Store Bump in State

Avoid re-computing the bump by storing it:

#[repr(C)]
pub struct Vault {
    pub authority: [u8; 32],
    pub bump: u8,  // Store bump to avoid re-derivation
}

// On init
vault.bump = bump;

// Later, use stored bump for signing
let bump_bytes = [vault.bump];
let seeds: [Seed; 3] = [
    Seed::from(b"vault"),
    Seed::from(vault.authority.as_slice()),
    Seed::from(&bump_bytes),
];

Common PDA Patterns

// User-specific account (like Anchor's seeds = [b"vault", user.key()])
let (user_vault, _) = Address::find_program_address(
    &[b"vault", user.address().as_ref()],
    program_id,
);

// Config/global account (like Anchor's seeds = [b"config"])
let (config, _) = Address::find_program_address(
    &[b"config"],
    program_id,
);

// Multi-key derivation
let (escrow, _) = Address::find_program_address(
    &[b"escrow", maker.as_ref(), taker.as_ref(), mint.as_ref()],
    program_id,
);