Engine
PDA derivation
Seed bytes, bumps, and the determinism rules every integrator needs to match.
Seed table
| PDA | Seeds | Per-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
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
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:
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.