Program Derived Address (PDA)
Program Derived Addresses (PDAs) are a way to derive a new address from a seed
and a program id
. This is useful for creating new accounts that are tied to a specific program.
For example, in the escrow program, the escrow
account is created as a PDA. This ensures that the escrow
account is tied to the escrow program and cannot be controlled by any other program or entity.
To define an account as a PDA with @solanaturbine/poseidon
, you can use the derive
method for every account by specifying your seed
within an array ([]
) as the first parameter.
seed
can be a string, a number, a Pubkey, or even the combination of them.
// ...
export default class EscrowProgram {
static PROGRAM_ID = new Pubkey("11111111111111111111111111111111");
make() {
// Wrap the seed with an array
auth.derive(["auth"]); // seed: string("auth")
vault.derive(["vault", escrow.key]); // seed: string("vault") + Pubkey(escrow.key)
escrow.derive(["escrow", maker.key, seed.toBytes()]); // seed: string("escrow") + Pubkey(maker.key) + number(seed.toBytes())
escrow.authBump = auth.getBump();
escrow.vaultBump = vault.getBump();
escrow.escrowBump = escrow.getBump();
}
}
The magic behind PDA is that it uses the program id
as the base address and the seed
(as we created above) with a bump
(a number between 0 to 255) as the offset to derive a new address, which is unique and off the Ed25519 curve, without a corresponding private key. This technique guarantees that the derived address is only controllable by the program that created it.
Normally, we'll store the bump
value in the state account to ensure that the program can always derive the same address and save the cost of bump calculation during the runtime. You can use the getBump
method to get the bump value for the account.
The corresponding Rust code will be generated as follows.
// ...
declare_id!("11111111111111111111111111111111");
#[program]
pub mod escrow_program {
use super::*;
pub fn make(
ctx: Context<MakeContext>,
) -> Result<()> {
ctx.accounts.escrow.auth_bump = ctx.bumps.auth;
ctx.accounts.escrow.vault_bump = ctx.bumps.vault;
ctx.accounts.escrow.escrow_bump = ctx.bumps.escrow;
Ok(())
}
}
#[derive(Accounts)]
#[instruction(seed:u64)]
pub struct MakeContext<'info> {
#[account(seeds = [b"auth"], bump)]
/// CHECK: This acc is safe
pub auth: UncheckedAccount<'info>,
#[account(
seeds = [b"vault", escrow.key().as_ref()],
bump,
)]
pub vault: Account<'info, TokenAccount>,
#[account(
seeds = [b"escrow", maker.key().as_ref(), seed.to_le_bytes().as_ref()],
bump,
)]
pub escrow: Account<'info, EscrowState>,
}
If you're creating a PDA with a given bump
, you can use the deriveWithBump
method with the bump
following the seed
instead. See the example below or the vault example for more details:
auth.deriveWithBump(["auth", state.key], state.authBump);
We highly recommend you to go through the official documentation to understand the concept of PDAs in Solana.