ShainShain/docs
Shain/Docs/PDA derivation
Engine

PDA derivation

Seed bytes, bumps, and the determinism rules every integrator needs to match.

Seed table

PDASeedsPer-wallet?
ShainConfig[b"shain_config"]No (singleton)
Treasury authority[b"shain_treasury"]No (singleton)
ShainSession[b"shain_session", user.as_ref()]Yes

From the SDK

sdk/src/pda.tstypescript
import { PublicKey } from "@solana/web3.js";
import { SHAIN_SEEDS } from "@shain/sdk";

export function derivePdas(input: {
  programId: PublicKey;
  holder?: PublicKey;
}) {
  const { programId, holder } = input;

  const [config] = PublicKey.findProgramAddressSync(
    [SHAIN_SEEDS.config],
    programId,
  );

  const [treasury] = PublicKey.findProgramAddressSync(
    [SHAIN_SEEDS.treasury],
    programId,
  );

  const [session] = holder
    ? PublicKey.findProgramAddressSync(
        [SHAIN_SEEDS.session, holder.toBuffer()],
        programId,
      )
    : [undefined];

  return { config, treasury, session };
}

From a Rust client or test

client-siderust
use solana_program::pubkey::Pubkey;

const CONFIG: &[u8] = b"shain_config";
const TREASURY: &[u8] = b"shain_treasury";
const SESSION: &[u8] = b"shain_session";

pub fn session_pda(program_id: &Pubkey, user: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(&[SESSION, user.as_ref()], program_id)
}

pub fn config_pda(program_id: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(&[CONFIG], program_id)
}

Bumps

Each PDA caches its bump in the account data (bump, treasury_bump). The program always reads the cached bump rather than recomputing it, which saves compute units. Clients do not need to pass the bump explicitly — the Anchor-generated account structs accept PDAs by public key.

Treasury ATA

The treasury is an ATA, not a custom PDA. Its owner is the shain_treasury PDA and its mint is shain_mint. To derive it off-chain:

treasury-ata.tstypescript
import { PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { derivePdas } from "@shain/sdk";

function treasuryAta(programId: PublicKey, mint: PublicKey) {
  const { treasury } = derivePdas({ programId });
  return getAssociatedTokenAddressSync(mint, treasury, true);
  // true = allowOwnerOffCurve (PDAs are off-curve)
}

The allowOwnerOffCurve = true argument is critical — a PDA is by construction off the ed25519 curve, so getAssociatedTokenAddressSync will throw without that flag.