ShainShain/docs
Shain/Docs/Architecture
Engine

Architecture

The on-chain shape of Shain: one program, two PDA types, one treasury ATA. No relayer, no mixer, no upgrade authority on mainnet.

At a glance

ComponentKindCountOwner
ProgramAnchor crate1Deployed on Solana
ShainConfigPDA (singleton)1Program
ShainSessionPDA (per holder)NProgram
Treasury ATASPL token account1Treasury PDA

Request flow

A holder session opens, is used, and expires in three round trips:

flowtext
holder  ──[start_session]──▶ program
                          ┃
                          ┗━─[token transfer fee]─▶ treasury ATA
                          ┗━ write ShainSession PDA

dapp   ──[gated_action]──▶ program
                          ┗━ assert session.active
                          ┗━ actions_count += 1

anyone ──[close_session]──▶ program
                          ┗━ assert !session.active
                          ┗━ close = user (rent refund)

Program boundaries

The program is a single Rust crate at programs/shain/ in the monorepo. It exposes four instructions and defines two account types. The on-chain module graph is flat:

programs/shain/src/text
lib.rs              ← #[program] dispatch + declare_id!
constants.rs        ← PDA seeds, defaults, limits
error.rs            ← ShainError enum
state.rs            ← ShainConfig + ShainSession accounts
instructions.rs     ← re-export barrel
instructions/
  initialize.rs
  start_session.rs
  gated_action.rs
  close_session.rs

Deliberate constraints:

  • No admin_update instruction. Config parameters are set once at initialize.
  • No pause bit. If the program is buggy, the only safe path is redeployment with a new program ID — no kill switch can be abused.
  • No upgrade authority on mainnet. The deployment retires the upgrade authority before the mainnet announcement.

PDA summary

AccountSeedsSpace
ShainConfig[b"shain_config"]8 + 154 B
Treasury authority[b"shain_treasury"]pass-through (no data)
ShainSession[b"shain_session", user.as_ref()]8 + 65 B

Seed bytes are constants in programs/shain/src/constants.rs, re-exported by the SDK at SHAIN_SEEDS. Never recompute them from a string — use the constants.

CPI surface

Shain performs two CPIs itself:

  • SPL Token transfer inside start_session, to move session_fee from the holder's ATA to the treasury ATA.
  • Associated Token Program create inside initialize, to mint the treasury ATA.

Downstream dapps CPI into Shain via gated_action. Shain does not CPI back into them; the integration is one-way.

Determinism

  • PDA addresses are deterministic from seeds. Given the program ID and a holder pubkey, everyone computes the same session PDA.
  • Session expiry is based on Clock::unix_timestamp. Clock drift between validator and integrator is negligible for a 24-hour window, but integrators should fetch getSlot/getBlockTime from the same RPC they use for sendTransaction.
  • Instruction discriminators are Anchor-derived. Do not hand- assemble them; use the IDL or the SDK.