Key Concepts
| Term | Definition |
|---|
| Program | A Solana smart contract. Stateless - all state lives in separate accounts passed as inputs to each instruction |
| Instruction | The equivalent of a function call on a Solana program. Each transaction contains one or more instructions |
| Account | An on-chain storage unit. Holds state (data) and is owned by a program |
| PDA (Program Derived Address) | A deterministic account address derived from a program ID and a set of seeds. No private key, only the program can sign for it |
| Slab | A fixed-size pre-allocated memory region used as the backing store for the order book. Enables O(log n) insert, delete, and lookup without dynamic allocation |
| Critbit tree | A binary trie used to implement the order book. Keys are order prices, supports efficient traversal to find best bid/ask |
| CU (Compute Unit) | Solana's unit of computation. Each transaction has a budget. Complex operations like order matching consume many CUs |
| Rent exemption | Accounts must hold enough SOL to be exempt from rent (storage fees). Accounts below the threshold are garbage collected |
| Zero-copy | An Anchor pattern that avoids deserialising large accounts into memory. Required for the order book to avoid stack overflow |
| Keeper | A bot that calls permissionless protocol instructions (settle_funding, liquidate_position). Does not participate in order matching - matching runs inline on-chain within place_order |
| AUM (Assets Under Management) | Total value of USDC currently held in a vault. Used to compute NAV per share: AUM / total_shares. Increases when depositors deposit or positions generate profit, decreases on withdrawals or losses |
| VAA (Verified Action Approval) | A signed price attestation produced by the Pyth oracle network and served by Hermes. |
1. Summary
Perps requires at least two Anchor programs deployed on Solana:
- Perps Program - core protocol logic: order book, position management, margin, funding rate, and liquidations.
- Vault Program - capital management: vault creation, deposits, withdrawals, NAV calculation, and LP share minting.
The Vault Program interacts with the Perps Program to place orders on behalf of vault strategies. Both programs use the Pyth oracle for price validation on every instruction that affects margin or positions.
Neither program implements any oracle logic of its own. Both integrate, via CPI, with the Pyth Receiver Program, a third program already deployed on-chain by Pyth Network.
2. Program Architecture
| Program | Responsibility | Why separate |
|---|
| Perps Program | Order book, matching, positions, funding, liquidations | Core protocol, independent of capital management |
| Vault Program | Deposits, withdrawals, LP shares, strategy management | Protocol launches with a liquidity vault and a liquidation vault. External managers can also create their own vaults |
| Pyth Receiver Program | Verifies the signed price attestation (VAA) and serves it to callers via CPI | Oracle logic is a solved problem |
3. Perps Program
3.1 Accounts
| Account | Description | Notes |
|---|
GlobalConfig | Protocol-wide parameters: admin, pending_admin, treasury, fee rates, max leverage, oracle max_age, max_conf_ratio, min_vault_lockup_period, vault_creation_fee, protocol_paused flag, protocol_hard_frozen flag | One per deployment |
Market | Per-market config: symbol, tick size, lot size, funding interval, Pyth price feed ID, max position size, initial margin %, maintenance margin %, status (active / paused / deprecated) | One per tradeable market (SOL-PERP, BTC-PERP, ETH-PERP) |
OrderBook | Slab-based critbit tree containing all live orders for a market | Zero-copy account, pre-allocated fixed buffer |
FundingState | Per market: cumulative funding index, last funding timestamp, last mark/oracle price | Used to compute funding payments |
UserAccount | Per user (or vault): owner: Pubkey, index: u8, account_type: AccountType, deposited margin, available margin, open order count | PDA derived from [owner, "user_account", index]. Stores account_type: enum { Primary, Secondary, Vault } as the canonical type indicator — any reader (indexer, other programs, RPC clients) determines the type from this field alone, without inspecting index. index is stored solely for PDA re-derivation and verification; it carries no business logic meaning. The program enforces that a Secondary account can only be created if a Primary already exists for the same owner — initialize_user_account receives the primary PDA as an optional account and checks !data_is_empty(). Vault trading accounts (account_type = Vault, owner = Vault PDA) are created by the Vault Program via CPI inside create_vault, never through initialize_user_account |
Position | Per user per market: side (long/short), size, entry price, liquidation price, funding index at open | PDA derived from [user_pubkey, market_pubkey, "position"] |
3.2 Common Validations
Every state-changing instruction checks these conditions before executing any logic. If any check fails, the instruction is rejected.
The protocol has two freeze levels stored in GlobalConfig:
| Flag | Set by | Meaning |
|---|
protocol_paused | pause_protocol or hard_freeze_protocol | Blocks all activity except withdraw_margin. Hard freeze always sets this flag too |
protocol_hard_frozen | hard_freeze_protocol only | Additionally blocks withdraw_margin. Last resort only |
Most instructions only need to check protocol_paused. Withdrawals need the additional protocol_hard_frozen check.
| Check | Condition | Applies to |
|---|
| Freeze check | GlobalConfig.protocol_paused == false | All instructions |
| Hard freeze check | GlobalConfig.protocol_hard_frozen == false | withdraw_margin and vault_withdraw only |
| Market is active | Market.status == Active | All market instructions (place_order, cancel_order, close_position, settle_funding, liquidate_position) |
| Oracle price is fresh | publish_time + max_age > clock::get() | All instructions that require a VAA |
| Oracle confidence within range | conf / price < max_conf_ratio | All instructions that require a VAA |
hard_freeze_protocol sets both flags. pause_protocol sets only protocol_paused. unfreeze_protocol clears both. unpause_protocol clears only protocol_paused.
3.3 User Instructions
| Instruction | Caller | VAA required | Description |
|---|
initialize_user_account | User | No | Create a UserAccount PDA for a given index, sets account_type = Primary if index == 0, else Secondary. If index > 0, the primary account (index == 0) must already be initialized — the instruction receives it as an optional account and rejects with PrimaryAccountRequired if absent or uninitialised. Vault trading accounts are never created through this instruction |
deposit_margin | User | No | Transfer USDC from user wallet to UserAccount |
withdraw_margin | User | Yes | Withdraw available margin, VAA validates that remaining margin covers open positions |
adjust_position_margin | User | Yes | Add or remove margin from a specific open position. Add: moves USDC from available margin in UserAccount to the position, raising the liquidation price. Remove: moves USDC from the position back to available margin, lowering the liquidation price. VAA validates that the result stays within margin requirements |
place_order | User | Yes | Place an order and attempt inline matching against the opposite side of the slab (price-time priority). Any unfilled remainder enters the slab as a resting limit order. Validates margin at mark price. Order type is passed as a parameter (see below) |
cancel_order | User | No | Remove an order from the slab, release reserved margin |
close_position | User | Yes | Close an open position by placing an opposing market order. Fills at the best available book price, settles realized PnL to UserAccount. VAA is required to validate remaining margin on any other open positions after settlement |
Order types supported by place_order:
| Type | Behaviour | Prototype |
|---|
Limit | Enters the slab at the specified price, waits for a match | Yes |
Market | Executes immediately at the best available price, any unfilled remainder is canceled | Yes |
IOC (Immediate-or-Cancel) | Like market but with a price cap/floor, canceled if the full size cannot be filled within the price limit | Post-prototype |
3.4 Permissionless Instructions
| Instruction | VAA required | Description |
|---|
settle_funding | Yes | Apply cumulative funding payments to all open positions for a market |
liquidate_position | Yes | Liquidate a position whose margin ratio is below maintenance threshold, pay liquidation fee to caller |
3.5 Admin Instructions
Note: For the prototype, all admin instructions are immediate. A timelock/governance mechanism (propose + delay + execute) will be added in a future version before mainnet.
Admin address: The admin field in GlobalConfig is a plain Pubkey. The program only checks that the signer of an admin instruction matches that address - it has no knowledge of what controls it. The address can be a regular wallet (useful for development), a multi-sig program (e.g. Squads), or any future governance system. Membership management, approval thresholds, and proposal flow are all external to the protocol. To change the admin address, use transfer_admin.
| Instruction | Description |
|---|
initialize_market | Create a new market with full config and an empty pre-allocated order book |
update_market | Update any market parameter: tick size, lot size, funding interval, max position size, margin requirements, price feed ID |
pause_market | Halt new orders and position opens for a market, existing positions and cancels still allowed |
unpause_market | Resume normal operation for a paused market |
deprecate_market | Mark a market as deprecated, only close/cancel allowed, no new orders or deposits |
update_global_config | Update protocol-wide parameters: fee rates, max leverage, oracle staleness limit, confidence ratio limit |
pause_protocol | Soft freeze, suspend all new activity across all markets and vaults, withdrawals still allowed |
unpause_protocol | Lift soft freeze, resume normal operation |
hard_freeze_protocol | Hard freeze, suspend all instructions including withdrawals. Last resort for active exploits |
unfreeze_protocol | Lift hard freeze, resume normal operation |
transfer_admin | Initiate a two-step transfer of the admin address by setting pending_admin, used to migrate to a new wallet, multi-sig, or governance system |
accept_admin | pending_admin signs and accepts authority, completing the transfer |
4. Vault Program
4.1 Accounts
| Account | Description |
|---|
Vault | Config: manager address, strategy type, performance_fee % (set at creation, immutable), lockup_period (must be >= min_vault_lockup_period), min_deposit, min_owner_share (minimum % of total shares the manager must hold), total AUM, total shares outstanding, status (active / frozen / deprecated) |
VaultShare | SPL token mint - LP token representing a proportional claim on vault assets. NAV per share = AUM / total shares |
VaultUserDeposit | Per depositor: shares held, last_deposit_time. Lock is checked at withdraw: now >= last_deposit_time + vault.lockup_period. PDA derived from [vault_pubkey, user_pubkey] |
4.2 Common Validations
| Check | Condition | Applies to |
|---|
| Protocol not paused | GlobalConfig.protocol_paused == false | All vault instructions except vault_withdraw |
| Vault is active | Vault.status == Active | vault_deposit, place_order_as_vault |
| Vault not deprecated | Vault.status != Deprecated | All except vault_withdraw |
vault_withdraw is always allowed regardless of protocol pause or vault status. Depositors must always be able to exit.
4.3 Public Instructions
| Instruction | Caller | Description |
|---|
create_vault | Anyone (pays vault_creation_fee) | Create a new vault - manager sets performance fee, lockup period (>= min_vault_lockup_period), and min deposit. All fixed at creation |
4.4 User Instructions
| Instruction | Caller | Description |
|---|
vault_deposit | User | Pull USDC from the user's UserAccount (available margin) into the vault. Mints LP shares proportional to current NAV per share, updates last_deposit_time. Rejected if vault is frozen, amount < min_deposit, or if the deposit would dilute the manager's share below min_owner_share |
vault_withdraw | User | Burn LP shares and return USDC proportional to current NAV per share back to the user's UserAccount (available margin). Performance fee deducted from profit. Rejected if now < last_deposit_time + vault.lockup_period. If the caller is the manager, also rejected if the resulting share would fall below min_owner_share |
4.5 Manager Instructions
| Instruction | Caller | Description |
|---|
place_order_as_vault | Vault manager | CPI into Perps Program to place an order on behalf of the vault |
freeze_vault | Vault manager | Halt new deposits, existing depositors can still withdraw |
unfreeze_vault | Vault manager | Resume normal vault operation |
4.6 Admin Instructions (Vault Program)
Note: For the prototype, all admin instructions are immediate. A timelock/governance mechanism will be added in a future version before mainnet.
| Instruction | Description |
|---|
force_freeze_vault | Immediately freeze any vault regardless of manager (emergency only) |
force_unfreeze_vault | Lift an admin-imposed freeze |
deprecate_vault | Mark vault as deprecated, only withdrawals allowed, no new deposits |
update_creation_fee | Update the protocol fee charged on vault creation |
collect_fees | Collect accrued protocol fees to the treasury account defined in GlobalConfig |
5. Order Book Design
The order book is implemented as a critbit tree backed by a slab allocator. All order data lives in a single fixed-size account allocated at market creation - no dynamic resizing, no heap allocation, minimal compute unit cost.
| Property | Value | Reason |
|---|
| Data structure | Critbit tree (binary trie on price) | O(log n) insert, delete, best-bid/ask |
| Storage | Zero-copy pre-allocated slab | Avoids stack overflow on large accounts |
| Order slots | ~10,000 per market (configurable at init) | Fixed cost, slab never grows after creation |
| Order fields | order_id, owner, side, price, size, timestamp | Minimum needed for price-time priority |
| Matching priority | Best price first, FIFO within same price level | Standard CLOB behaviour |
| Matching model | Inline within place_order, matching runs on-chain in the same transaction, no off-chain keeper needed | No centralised matcher, fully on-chain execution |
6. Oracle Integration
Artifi does not implement any oracle logic itself. Both the Perps Program and the Vault Program integrate, via CPI, with the Pyth Receiver Program, a program already deployed on-chain by Pyth Network.
Every instruction that affects margin or positions requires a valid Pyth price account as input. The program validates the price before using it.
| Validation | Rule | If violated |
|---|
| Staleness | publish_time + max_age > clock::get() | Instruction rejected |
| Confidence | conf / price < max_conf_ratio (e.g. 5%) | Instruction rejected |
| Instruction | Oracle use |
|---|
place_order | Validate margin at mark price + compute PnL and margin impact for each inline fill |
adjust_position_margin | Validate that margin ratio stays within bounds after the adjustment |
withdraw_margin | Ensure margin ratio remains above minimum after withdrawal |
close_position | Validate remaining margin on other open positions after settlement |
settle_funding | Compute funding payment from (mark - index) / index |
liquidate_position | Verify position is actually below maintenance margin at current mark price |
For the full transaction flow, see technical-documents.md.