Solana PDA


The PDA is a unique address deterministically generated by the findProgramAddressSync function, using:

  • A seed (in this case, the string seed encoded as UTF-8).
  • The wallet's public key (wallet!.publicKey.toBytes()).
  • The program's ID (program.programId), which is the address of the example program on the blockchain.

How can we connect storage account to program account? The program identifies the myAccount storage account using the Program Derived Address (PDA).

The Solana program (example) is designed to recognize the PDA as the address of the Counter storage account. The PDA is passed to the program in the accounts object during transactions.

Solana’s runtime ensures that only the program with the matching program ID can modify the data in accounts derived from its PDAs, providing security and ownership guarantees.

Check in program account source code to avoid creating multiple storage account for one wallet:

use anchor_lang::prelude::*;

#[program]
pub mod example {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        
        // Check if the account is already initialized
        if counter.count != 0 || counter.to_account_info().data_len() > 0 {
            return Err(ProgramError::AccountAlreadyInitialized.into());
        }
        
        counter.authority = ctx.accounts.payer.key();
        counter.count = 0;
        
        msg!("Counter account created! Current count: {}", counter.count);
        Ok(())
    }
}

The Complete Code

After adding PDA to example program, we have:

use anchor_lang::prelude::*;

declare_id!("2ZhxsERLkcxYuuUnHWA9K7btyzT4SyWAAcr1XZuAHaRc");

#[program]
pub mod example {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        
        counter.authority = ctx.accounts.payer.key();
        counter.count = 0;
        
        msg!("Counter account created! Current count: {}", counter.count);
        Ok(())
    }

    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        msg!("Previous counter: {}", counter.count);

        // Optional: Validate that the signer is the authority
        if ctx.accounts.authority.key() != counter.authority {
            return Err(ProgramError::InvalidAccountData.into());
        }

        counter.count += 1;
        msg!("Counter incremented! Current count: {}", counter.count);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(
        init,
        payer = payer,
        space = 8 + 8 + 32, // Discriminator (8) + count (8) + authority (32)
        seeds = [b"counter", payer.key().as_ref()],
        bump
    )]
    pub counter: Account<'info, Counter>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(
        mut,
        seeds = [b"counter", authority.key().as_ref()],
        bump,
        has_one = authority
    )]
    pub counter: Account<'info, Counter>,

    #[account(mut)]
    pub authority: Signer<'info>,
}

#[account]
pub struct Counter {
    pub count: u64,
    pub authority: Pubkey,
}
All Rights Reserved