ShainShain/docs
Shain/Docs/Account layout
Engine

Account layout

Byte-level layout of every on-chain account Shain reads or writes. Matches programs/shain/src/state.rs exactly.

Anchor discriminator

Every Anchor account is prefixed with an 8-byte discriminator derived from the account's type name. When you read an account manually, skip the first 8 bytes before deserializing. The SDK's decoders handle this automatically.

ShainConfig

Singleton. Seeds: [b"shain_config"]. Total size: 8 + 154 B = 162 B.

programs/shain/src/state.rsrust
#[account]
#[derive(InitSpace)]
pub struct ShainConfig {
    pub authority: Pubkey,            // 32
    pub shain_mint: Pubkey,           // 32
    pub treasury_ata: Pubkey,         // 32
    pub session_duration: i64,        //  8
    pub session_fee: u64,             //  8
    pub min_holding: u64,             //  8
    pub total_sessions: u64,          //  8
    pub total_fees_collected: u64,    //  8
    pub bump: u8,                     //  1
    pub treasury_bump: u8,            //  1
}
OffsetFieldTypeSizeRole
0discriminator[u8; 8]8Anchor
8authorityPubkey32Who seeded the config
40shain_mintPubkey32The SHAIN SPL mint
72treasury_ataPubkey32Treasury ATA address
104session_durationi648Seconds, default 86400
112session_feeu648Base units
120min_holdingu648Base units
128total_sessionsu648Lifetime counter
136total_fees_collectedu648Running accrual
144bumpu81Cached PDA bump
145treasury_bumpu81Cached treasury bump

ShainSession

Per-holder. Seeds: [b"shain_session", user.key().as_ref()]. Total size: 8 + 65 B = 73 B.

programs/shain/src/state.rsrust
#[account]
#[derive(InitSpace)]
pub struct ShainSession {
    pub owner: Pubkey,              // 32
    pub started_at: i64,            //  8
    pub expires_at: i64,            //  8
    pub actions_count: u64,         //  8
    pub total_sessions: u64,        //  8
    pub bump: u8,                   //  1
}

impl ShainSession {
    pub fn is_active(&self, now: i64) -> bool {
        now < self.expires_at
    }
}
OffsetFieldTypeSizeRole
0discriminator[u8; 8]8Anchor
8ownerPubkey32Must match signer
40started_ati648Unix seconds
48expires_ati648started_at + duration
56actions_countu648gated_action counter
64total_sessionsu648Lifetime per holder
72bumpu81Cached PDA bump

Decoding from raw data

If you must decode without the SDK:

manual-decode.tstypescript
import { PublicKey } from "@solana/web3.js";

function readI64LE(buf: Buffer, offset: number): bigint {
  const lo = BigInt(buf.readUInt32LE(offset));
  const hi = BigInt(buf.readInt32LE(offset + 4));
  return lo | (hi << 32n);
}

function readU64LE(buf: Buffer, offset: number): bigint {
  const lo = BigInt(buf.readUInt32LE(offset));
  const hi = BigInt(buf.readUInt32LE(offset + 4));
  return lo | (hi << 32n);
}

function decodeSession(data: Buffer) {
  return {
    owner: new PublicKey(data.subarray(8, 40)),
    startedAt: Number(readI64LE(data, 40)),
    expiresAt: Number(readI64LE(data, 48)),
    actionsCount: Number(readU64LE(data, 56)),
    totalSessions: Number(readU64LE(data, 64)),
    bump: data.readUInt8(72),
  };
}