Working with Pubkeys & Accounts
Pubkey identifies accounts and programs. AccountInfo is the runtime handle for lamports, data, owner, and signer flags. Correct slicing and validation prevent privilege escalation.
Recipe
use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
fn assert_signer(ai: &AccountInfo) -> ProgramResult {
if !ai.is_signer { return Err(ProgramError::MissingRequiredSignature); }
Ok(())
}When to reach for this:
- Validating client-supplied account lists in native programs.
- Comparing owners, PDAs, and program IDs.
- Iterating accounts with
next_account_info.
Working Example
use solana_program::{
account_info::{next_account_info, AccountInfo},
program_error::ProgramError,
pubkey::Pubkey,
};
pub fn process_transfer(accounts: &[AccountInfo], expected_owner: &Pubkey) -> ProgramResult {
let iter = &mut accounts.iter();
let payer = next_account_info(iter)?;
let vault = next_account_info(iter)?;
if !payer.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != expected_owner {
return Err(ProgramError::IncorrectProgramId);
}
if !vault.is_writable {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}What this demonstrates:
- Signer and writable flags are enforced explicitly.
- Owner check binds the vault to your program.
next_account_infoadvances the account iterator in client order.
Deep Dive
AccountInfo Fields
| Field | Meaning |
|---|---|
key | Account address |
lamports | SOL balance ref |
data | Byte payload ref |
owner | Program that may write data |
is_signer | Signed this tx |
is_writable | Mutable in this tx |
Pubkey Tips
- Compare with
==on references. - PDAs have
is_on_curve == falseoff-chain; on-chain treat as normal keys.
Rust Notes
let (pda, bump) = Pubkey::find_program_address(&[b"vault", owner.as_ref()], program_id);Gotchas
- Account order drift - Client reordering passes wrong account to check.. Fix: Document fixed order; use Anchor constraints or explicit indices.
- Unchecked PDA - Derive bump on-chain and compare to stored canonical bump.. Fix: See Canonical Bumps page.
- Duplicate accounts - Same key passed twice can bypass debit/credit checks.. Fix: Reject duplicate keys in sensitive instructions.
- Owner vs key - Checking
keywhen you meantownerallows impersonation.. Fix: Name variables clearly and lint reviews. - Lamports mut without signer - Debiting SOL requires signer or PDA authority.. Fix: Validate signer before
**lamportschanges.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Anchor Account<'info, T> | Auto owner/discriminator checks | Manual control |
Pinocchio AccountView | Lower CU account parsing | Ecosystem maturity |
| Account maps off-chain | Client builds account metas | On-chain must still verify |
FAQs
What is AccountInfo lifetime?
Tied to the instruction - do not store references across CPI.
Can key be a PDA?
Yes - PDAs are pubkeys off the ed25519 curve.
How to compare Pubkey?
Use ==; no constant-time requirement for pubkeys.
Read lamports?
Use **account.lamports or borrow helpers.
Who sets is_writable?
Transaction builder - program must assert for safety.
Empty account data?
Uninitialized accounts may be zero length until allocated.
System program owner?
New accounts are owned by System Program until assigned.
Can programs sign?
Only via PDAs with invoke_signed.
Pubkey from string?
Off-chain Pubkey::from_str - on-chain receive as account key.
Account slicing?
Use iterator pattern; avoid hard-coded len without checks.
Executable flag?
Program accounts are executable; data accounts are not.
Rent exempt?
Check Rent::get()?.is_exempt when creating accounts.
Related
- Manual Account Validation - Full checklist
- Deriving PDAs - PDA addresses
- PDA Basics - Why PDAs exist
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.