Protocol Specification
Sinai Standard Transfer Hook Protocol v0.1
Abstract
Sinai Standard is an open protocol for programmable compliance on Solana. It uses the Token-2022 Transfer Hook interface to enforce regulatory rules — allowlists, transaction taxes, hold periods, and concentration limits — entirely on-chain at the runtime level. Every transfer_checked call passes through one or more hook programs that validate the transfer against issuer-defined policies. No off-chain gatekeeper can be bypassed.
This document specifies the protocol's architecture, hook interfaces, state accounts, composition model, and token creation flow.
Protocol Overview
Design Principles
- On-chain enforcement. Compliance rules execute inside the Solana runtime during token transfers. There is no off-chain oracle or relay in the transfer path.
- Composable hooks. Each compliance rule is an independent program. A Router Hook composes multiple rules into a single transfer validation pipeline.
- Issuer sovereignty. Token issuers configure their own compliance parameters. The protocol provides the enforcement layer, not the policy.
Architecture
The protocol consists of six on-chain programs built on Anchor 0.31.1:
| Layer | Programs |
|---|---|
| Transfer Hooks | Allowlist Hook, Tax Hook, Hold Hook, Max Balance Hook |
| Composition | Router Hook |
| Token Lifecycle | Token Factory |
Token-2022 Integration
Sinai Standard relies on three Token-2022 extensions:
| Extension | Role |
|---|---|
TransferHook | Points the mint to a hook program (or Router) invoked on every transfer_checked |
PermanentDelegate | Grants the Tax Hook authority to collect fees post-transfer |
MetadataPointer | Stores token name, symbol, and URI on-chain |
The ConfidentialTransfer extension is optionally supported via Token-2022's native ElGamal encryption and ZK range proofs — no custom program is required.
Hook Interface
Entry Point
Every hook program exposes a single validation entry point:
This function is invoked automatically by the Token-2022 runtime during any transfer_checked call on a mint with the TransferHook extension enabled. The hook program cannot be bypassed by the sender or receiver.
ExtraAccountMetas PDA
Each hook program derives an ExtraAccountMetas PDA that tells the Token-2022 runtime which additional accounts the hook needs during execution:
- Seeds:
["extra-account-metas", mint] - Program: The hook program's own program ID
This PDA must be initialized before any transfer_checked call on the mint. If it is missing, the transaction fails.
The runtime reads the ExtraAccountMetas PDA, resolves the listed accounts, and passes them to the hook's execute instruction automatically.
Validation Semantics
- If the hook's
executeinstruction succeeds, the transfer proceeds. - If it returns an error, the entire transaction reverts — the transfer never settles.
- If
is_activeisfalseon the hook's config PDA, the hook permits all transfers (permissive kill switch).
Hooks Specification
Allowlist Hook
Purpose: Restricts transfers to an approved set of wallets, or blocks a denied set.
Modes:
| Mode | Behavior |
|---|---|
Allowlist | Only wallets on the list may send or receive |
Denylist | All wallets may transfer except those on the list |
State Account — AllowlistRegistry PDA
Seeds: ["allowlist", mint]
| Field | Type | Description |
|---|---|---|
authority | Pubkey | Admin who manages the registry |
mint | Pubkey | The token mint |
is_active | bool | Kill switch (false = all transfers pass) |
mode | enum | Allowlist or Denylist |
wallet_count | u32 | Number of wallets in the registry |
wallets | Vec<Pubkey> | Wallet addresses |
Validation Logic:
- If
is_activeisfalse, the transfer is permitted. - In
Allowlistmode: both source owner and destination owner must appear inwallets. - In
Denylistmode: neither source owner nor destination owner may appear inwallets.
Constraints:
- Maximum ~300 wallets per registry
- Batch operations limited to 20 wallets per transaction
- In
Allowlistmode, the issuer must add themselves to distribute tokens
Error Conditions:
- Transfer reverts if a wallet fails the allowlist/denylist check
Tax Hook
Purpose: Levies a percentage-based tax on every transfer, collected into an issuer-specified vault.
Two-Step Collection Design:
execute(during TransferHook CPI): validates the transfer and logs the taxable amount.collect_tax(separate instruction): uses the Permanent Delegate PDA to transfer the tax to the vault.
This separation keeps the transfer CPI within compute limits.
State Account — TaxConfig PDA
Seeds: ["tax-config", mint]
| Field | Type | Description |
|---|---|---|
authority | Pubkey | Admin who manages tax settings |
mint | Pubkey | The token mint |
tax_vault | Pubkey | Destination for collected taxes |
tax_bps | u16 | Tax rate in basis points |
max_tax_bps | u16 | Hard cap — immutable after creation |
is_active | bool | Kill switch |
exempt_count | u32 | Number of exempt wallets |
exempt_wallets | Vec<Pubkey> | Tax-exempt addresses |
State Account — TaxDelegate PDA
Seeds: ["tax-delegate", mint]
The Permanent Delegate authority used by collect_tax to debit sender accounts post-transfer.
Validation Logic:
- If
is_activeisfalse, the transfer is permitted (no tax logged). - If the source wallet is in
exempt_wallets, no tax is applied. - Tax calculation:
tax_amount = amount * tax_bps / 10000
Constraints:
tax_bpscan never exceedmax_tax_bpsmax_tax_bpsis set at initialization and cannot be changed
Error Conditions:
update_tax_ratereverts if the new rate exceedsmax_tax_bpscollect_taxreverts if the Permanent Delegate PDA is not the mint's delegate authority
Hold Hook
Purpose: Enforces a minimum holding period before tokens can be transferred out of a wallet.
State Account — HoldConfig PDA
Seeds: ["hold-config", mint]
| Field | Type | Description |
|---|---|---|
authority | Pubkey | Admin who manages hold settings |
mint | Pubkey | The token mint |
hold_period_seconds | i64 | Lock-up duration in seconds |
is_active | bool | Kill switch |
State Account — WalletLock PDA
Seeds: ["wallet-lock", mint, wallet]
| Field | Type | Description |
|---|---|---|
wallet | Pubkey | The locked wallet |
mint | Pubkey | The token mint |
acquired_at | i64 | Unix timestamp of acquisition |
unlock_at | i64 | acquired_at + hold_period_seconds |
Validation Logic:
- If
is_activeisfalse, the transfer is permitted. - If no WalletLock PDA exists for the source wallet, the transfer is permitted.
- If
current_timestampis at or pastunlock_at, the transfer is permitted. - Otherwise, the transfer reverts.
Constraints:
update_hold_periodonly affects futurerecord_acquisitioncalls — existing WalletLock PDAs retain their originalunlock_at
Error Conditions:
- Transfer reverts if the source wallet's lock-up has not expired
Max Balance Hook
Purpose: Caps the maximum token balance any single wallet can hold, preventing concentration.
State Account — MaxBalanceConfig PDA
Seeds: ["max-balance-config", mint]
| Field | Type | Description |
|---|---|---|
authority | Pubkey | Admin who manages the config |
mint | Pubkey | The token mint |
max_balance | u64 | Maximum allowed balance per wallet |
is_active | bool | Kill switch |
Validation Logic:
- If
is_activeisfalse, the transfer is permitted. - The program reads the destination token account's current balance at byte offset 64 (the
amountfield in a Token-2022 account). - If
destination_balance + transfer_amountis greater thanmax_balance, the transfer reverts.
Constraints:
max_balancemust be greater than zero- Wallets already above a newly lowered limit are not penalized but cannot receive more tokens
Error Codes:
| Code | Name | Description |
|---|---|---|
6000 | Unauthorized | Signer is not the config authority |
6001 | MaxBalanceExceeded | Transfer would push destination above max_balance |
6002 | InvalidMaxBalance | max_balance must be greater than zero |
Router Composition
Purpose
The Router Hook composes up to four sub-hooks into a single TransferHook entry point. A mint's TransferHook extension points to the Router, and the Router validates against each enabled sub-hook in sequence.
Execution Order
The Router executes sub-hooks in a fixed order:
- Allowlist Hook — validates source and destination are approved
- Tax Hook — logs the transfer for tax collection
- Hold Hook — verifies the source wallet's lock-up has expired
- Max Balance Hook — ensures destination balance plus transfer amount does not exceed the cap
To skip a hook, set its program ID to PublicKey.default (all zeros) in the RouterConfig.
State Account — RouterConfig PDA
Seeds: ["router-config", mint]
| Field | Type | Description |
|---|---|---|
authority | Pubkey | Admin who manages the router |
mint | Pubkey | The token mint |
is_active | bool | Kill switch |
allowlist_hook_program | Pubkey | Allowlist Hook program ID (or zero to skip) |
tax_hook_program | Pubkey | Tax Hook program ID (or zero to skip) |
hold_hook_program | Pubkey | Hold Hook program ID (or zero to skip) |
max_balance_hook_program | Pubkey | Max Balance Hook program ID (or zero to skip) |
Inline Deserialization
The Router does not use CPI to call sub-hooks. Instead, it reads each sub-hook's config PDA account data and deserializes the relevant fields inline using raw byte offsets. This design:
- Avoids CPI depth limits (Solana caps CPI at 4 levels;
transfer_checkedalready consumes one) - Reduces compute unit consumption
- Keeps all validation in a single transaction context
Each sub-hook's config and ExtraAccountMetas PDAs must still be initialized independently.
Failure Semantics
- If any enabled sub-hook's validation fails, the entire transfer reverts.
- Sub-hooks are evaluated in order; a failure in hook 1 short-circuits hooks 2–4.
- If
is_activeisfalseon the RouterConfig, all sub-hooks are bypassed entirely.
Hook Selection Logic
The SDK (AksumKit) automatically selects the correct mode when creating tokens:
| Hooks Specified | TransferHook Target |
|---|---|
| 0 hooks | No TransferHook extension |
| 1 hook | Direct hook program |
| 2+ hooks | Router Hook program |
Token Factory
Purpose
The Token Factory creates Token-2022 mints pre-configured with the extensions required for compliance enforcement.
Extensions
Every mint created by the Token Factory includes:
| Extension | Purpose |
|---|---|
MetadataPointer | On-chain token name, symbol, and URI |
TransferHook | Points to a hook program or Router for compliance checks |
PermanentDelegate | Grants the Tax Hook authority to collect fees post-transfer |
The ConfidentialTransfer extension is optionally added for privacy-preserving transfers.
State Account — TokenRecord PDA
Seeds: ["token-record", mint]
Records the issuer, mint address, associated hook program, and pause state.
State Account — FreezeAuthority PDA
Seeds: ["freeze-authority", mint]
Used by freeze_account and thaw_account instructions for individual account-level freezing.
Instructions
| Instruction | Description |
|---|---|
create_token | Creates a new Token-2022 mint with all extensions configured |
mint_supply | Mints additional supply (issuer only) |
pause_token | Global pause — halts all transfers (issuer only) |
resume_token | Resume transfers after a global pause |
freeze_account | Freeze an individual token account |
thaw_account | Unfreeze an individual token account |
PDA Reference
All PDA seeds used across the protocol:
| PDA | Seeds | Program |
|---|---|---|
| AllowlistRegistry | ["allowlist", mint] | Allowlist Hook |
| TaxConfig | ["tax-config", mint] | Tax Hook |
| TaxDelegate | ["tax-delegate", mint] | Tax Hook |
| HoldConfig | ["hold-config", mint] | Hold Hook |
| WalletLock | ["wallet-lock", mint, wallet] | Hold Hook |
| MaxBalanceConfig | ["max-balance-config", mint] | Max Balance Hook |
| RouterConfig | ["router-config", mint] | Router Hook |
| TokenRecord | ["token-record", mint] | Token Factory |
| FreezeAuthority | ["freeze-authority", mint] | Token Factory |
| ExtraAccountMetas | ["extra-account-metas", mint] | Each hook program (distinct per program ID) |
All hook programs use the seed "extra-account-metas" for their ExtraAccountMetas PDA, but each is derived against the respective program's own ID, producing distinct accounts.
Program IDs
All programs are deployed to Solana devnet.
| Program | Address |
|---|---|
| Token Factory | VXQL8u4NVUYG1zaejujwh5gr21iinmk4yYCXn1g9TXr |
| Allowlist Hook | Bo3Rd8qZeuxU1cmtCqKEFPRe5Uumx9tusjZ7B1hXtPgc |
| Tax Hook | ACJXvcH4uaBfBwSwcVG48zJ177ydEvtCqGRMKXv53goZ |
| Hold Hook | 8HkukxWoo27BnNwqCzCim4ueKaGEfqLW4LdSZkHkCWzS |
| Max Balance Hook | Ctx9ZtNzPFYyjqxdZSMYLgHdqNNpAS61G6ok1dYVBHWi |
| Router Hook | HHnt7Hfnp2fDftFNCFPqEhebgXGizuqubXqhiEi8C1of |
SSTS Verifiers (devnet):
| Verifier | Address |
|---|---|
| SSTS Allowlist | A4uS8AYfWfap2ceMccdah1BWzhDYbUuCaHzqqx6b1dRK |
| SSTS Hold | 67UG9Xy6nDjVUc9j7g9nHR8xhw1izpLVw8JRC4hVfJWH |
| SSTS Tax | 75sNs3nyLMdjBMC3yL497ExZaEVgZtAJY6YSLr68yyce |
| SSTS Max Balance | 4WsYgQL7TntXbHZfEvGBm3HSCcUaLQncwfqNoFea6dSQ |
Versioning
This is Protocol v0.1 (devnet). Programs are deployed to Solana devnet for testing and integration.
Mainnet deployment is pending a formal security audit. Program addresses will change for the mainnet release.
The protocol follows a versioned specification model. Breaking changes to hook interfaces, PDA layouts, or Router composition semantics will increment the major version number.