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.
#[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
}| Offset | Field | Type | Size | Role |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Anchor |
| 8 | authority | Pubkey | 32 | Who seeded the config |
| 40 | shain_mint | Pubkey | 32 | The SHAIN SPL mint |
| 72 | treasury_ata | Pubkey | 32 | Treasury ATA address |
| 104 | session_duration | i64 | 8 | Seconds, default 86400 |
| 112 | session_fee | u64 | 8 | Base units |
| 120 | min_holding | u64 | 8 | Base units |
| 128 | total_sessions | u64 | 8 | Lifetime counter |
| 136 | total_fees_collected | u64 | 8 | Running accrual |
| 144 | bump | u8 | 1 | Cached PDA bump |
| 145 | treasury_bump | u8 | 1 | Cached treasury bump |
ShainSession
Per-holder. Seeds: [b"shain_session", user.key().as_ref()]. Total size: 8 + 65 B = 73 B.
#[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
}
}| Offset | Field | Type | Size | Role |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Anchor |
| 8 | owner | Pubkey | 32 | Must match signer |
| 40 | started_at | i64 | 8 | Unix seconds |
| 48 | expires_at | i64 | 8 | started_at + duration |
| 56 | actions_count | u64 | 8 | gated_action counter |
| 64 | total_sessions | u64 | 8 | Lifetime per holder |
| 72 | bump | u8 | 1 | Cached PDA bump |
Decoding from raw data
If you must decode without the SDK:
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),
};
}