Skip to Content
DevelopersConceptsProgram Structure

Program Structure

The Drift protocol is a Solana program (smart contract) that manages user accounts, positions, orders, and markets. Understanding the onchain data model helps you work effectively with any Drift SDK (TypeScript, Python, Rust) and understand how state updates propagate.

Core accounts

Drift uses several account types, each serving a specific purpose:

State account

Single global account holding protocol-wide configuration:

  • Oracle guards - Stale price thresholds and validity checks
  • Fee structures - Separate fee tiers for perpetual and spot markets
  • Sequencer settings - Transaction ordering parameters
  • Admin controls - Protocol admin addresses and permissions
  • Emergency flags - Circuit breakers and pause mechanisms

The State account is a singleton, there’s only one per deployment. It’s read by almost every instruction to apply protocol-level rules.

View TypeScript interface

interface State { admin: PublicKey; whitelistMint: PublicKey; discountMint: PublicKey; signer: PublicKey; srmVault: PublicKey; perpFeeStructure: FeeStructure; spotFeeStructure: FeeStructure; oracleGuardRails: OracleGuardRails; numberOfAuthorities: BN; numberOfSubAccounts: BN; lpCooldownTime: BN; liquidationMarginBufferRatio: number; settlementDuration: number; numberOfMarkets: number; numberOfSpotMarkets: number; signerNonce: number; minPerpAuctionDuration: number; defaultMarketOrderTimeInForce: number; defaultSpotAuctionDuration: number; exchangeStatus: number; liquidationDuration: number; initialPctToLiquidate: number; }

Market accounts

PerpMarketAccount One per perp market

Manages perpetual futures markets with:

  • AMM state - Base/quote reserves, sqrt_k, and liquidity parameters
  • Funding rates - Tracks funding rate history and calculations
  • Oracle integration - Price feeds from Pyth, Switchboard, etc.
  • Fee configuration - Market-specific fee structures
  • Risk parameters - Margin ratios, open interest limits, contract tier
  • Market status - Active, paused, or other operational states

View TypeScript interface

interface PerpMarketAccount { pubkey: PublicKey; marketIndex: number; amm: AMM; pnlPool: PoolBalance; name: number[]; insuranceClaim: InsuranceClaim; unrealizedPnlMaxImbalance: BN; expiryTs: BN; expiryPrice: BN; nextFillRecordId: BN; nextFundingRateRecordId: BN; nextCurveRecordId: BN; imfFactor: number; unrealizedPnlImfFactor: number; liquidatorFee: number; ifLiquidationFee: number; marginRatioInitial: number; marginRatioMaintenance: number; unrealizedPnlInitialAssetWeight: number; unrealizedPnlMaintenanceAssetWeight: number; numberOfUsersWithBase: number; numberOfUsers: number; status: MarketStatus; contractTier: ContractTier; contractType: ContractType; pausedOperations: number; }

SpotMarketAccount One per spot market

Manages spot markets and lending pools with:

  • Interest rates - Dynamic deposit/borrow rates based on utilization
  • Utilization tracking - Current and optimal utilization targets
  • Insurance fund - Insurance fund stake and coverage
  • Oracle integration - Price feeds for the spot asset
  • Deposit/borrow limits - Maximum deposit amounts and position sizes
  • Asset weights - Collateral weights for risk calculations

View TypeScript interface

interface SpotMarketAccount { pubkey: PublicKey; oracle: PublicKey; mint: PublicKey; vault: PublicKey; name: number[]; historicalOracleData: HistoricalOracleData; historicalIndexData: HistoricalIndexData; revenuePool: PoolBalance; spotFeePool: PoolBalance; insuranceFund: InsuranceFund; totalSpotFee: BN; depositBalance: BN; borrowBalance: BN; cumulativeDepositInterest: BN; cumulativeBorrowInterest: BN; totalSocialLoss: BN; totalQuoteSocialLoss: BN; withdrawGuardThreshold: BN; maxTokenDeposits: BN; depositTokenTwap: BN; borrowTokenTwap: BN; utilizationTwap: BN; lastInterestTs: BN; lastTwapTs: BN; expiryTs: BN; orderStepSize: BN; orderTickSize: BN; minOrderSize: BN; maxPositionSize: BN; nextFillRecordId: BN; nextDepositRecordId: BN; initialAssetWeight: number; maintenanceAssetWeight: number; initialLiabilityWeight: number; maintenanceLiabilityWeight: number; imfFactor: number; liquidatorFee: number; ifLiquidationFee: number; optimalUtilization: number; optimalBorrowRate: number; maxBorrowRate: number; decimals: number; marketIndex: number; ordersEnabled: boolean; oracleSource: OracleSource; status: MarketStatus; assetTier: AssetTier; pausedOperations: number; ifPausedOperations: number; feeAdjustment: number; }

Markets are identified by numeric indices (market 0, market 1, etc.). The SDK caches market accounts for fast lookups.

User accounts

UserAccount Holds all trading state for a specific subaccount

Each UserAccount stores:

  • Perp positions - Market index, base amount, quote entry, last funding index
  • Spot positions - Deposits and borrows per market
  • Open orders - Up to 32 active orders per user
  • Margin settings - Leverage configuration and margin trading enabled flag
  • Permissions - Delegate addresses and access controls

View TypeScript interface

interface UserAccount { authority: PublicKey; delegate: PublicKey; name: number[]; spotPositions: SpotPosition[]; perpPositions: PerpPosition[]; orders: Order[]; lastAddPerpLpSharesTs: BN; totalDeposits: BN; totalWithdraws: BN; totalSocialLoss: BN; settledPerpPnl: BN; cumulativeSpotFees: BN; cumulativePerpFunding: BN; liquidationMarginFreed: BN; lastActiveSlot: BN; subAccountId: number; status: number; isMarginTradingEnabled: boolean; idle: boolean; openOrders: number; hasOpenOrder: boolean; openAuctions: number; hasOpenAuction: boolean; padding: number[]; }

UserStatsAccount Tracks aggregated stats across all subaccounts

Maintains lifetime statistics:

  • Fee tracking - Total fees paid across all markets
  • Volume metrics - Maker, taker, and filler volume (30-day rolling)
  • Referral data - Referrer information and referral status
  • Fuel points - Maker incentive points for fee discounts

View TypeScript interface

interface UserStatsAccount { authority: PublicKey; referrer: PublicKey; fees: UserFees; nextEpochTs: BN; makerVolume30d: BN; takerVolume30d: BN; fillerVolume30d: BN; lastMakerVolume30dTs: BN; lastTakerVolume30dTs: BN; lastFillerVolume30dTs: BN; ifStakedQuoteAssetAmount: BN; numberOfSubAccounts: number; numberOfSubAccountsCreated: number; isReferrer: boolean; disableUpdatePerpBidAskTwap: boolean; fuel: Fuel; }

Users are PDAs derived from: [authority, subaccount_id]. Each wallet can have multiple subaccounts (0, 1, 2…) sharing cross-margin.

Order accounting

Orders are stored directly in the UserAccount, not as separate accounts. This design reduces transaction overhead and allows up to 32 orders per user.

Each order contains:

  • Market identification - Market index and type (perp/spot)
  • Order parameters - Type (limit, market, oracle, trigger), direction, base amount, price
  • Order IDs - System order ID and user-defined order ID
  • Execution flags - Post-only, reduce-only, immediate-or-cancel (IOC)
  • Auction settings - JIT auction parameters (start/end slot, duration)

When an order fills, it’s marked as filled but not immediately removed, allowing order history tracking within the account.

View TypeScript interface

interface Order { slot: BN; price: BN; baseAssetAmount: BN; baseAssetAmountFilled: BN; quoteAssetAmountFilled: BN; triggerPrice: BN; auctionStartPrice: BN; auctionEndPrice: BN; maxTs: BN; oraclePriceOffset: number; orderId: number; marketIndex: number; status: OrderStatus; orderType: OrderType; marketType: MarketType; userOrderId: number; existingPositionDirection: PositionDirection; direction: PositionDirection; reduceOnly: boolean; postOnly: boolean; immediateOrCancel: boolean; triggerCondition: OrderTriggerCondition; auctionDuration: number; padding: number[]; }

PDAs (Program Derived Addresses)

Drift extensively uses PDAs for deterministic address generation:

User PDA: [b"user", authority.key(), subaccount_id as bytes] UserStats PDA: [b"user_stats", authority.key()] PerpMarket PDA: [b"perp_market", market_index as bytes] SpotMarket PDA: [b"spot_market", market_index as bytes]

Each SDK provides helpers to derive these addresses without onchain calls (e.g. getUserAccountPublicKey() in TypeScript, get_user_account_public_key() in Python).

Account relationships

State (1) ├── PerpMarket[0..N] ├── SpotMarket[0..M] └── Insurance Fund Wallet ├── UserStats (1 per wallet) └── UserAccount[0..N] (subaccounts) ├── PerpPosition[0..N] ├── SpotPosition[0..M] └── Order[0..32]

How instructions modify state

When you call an instruction (e.g., placePerpOrder), the program:

  1. Loads accounts passed in the instruction
  2. Validates account ownership and PDAs
  3. Loads oracle price data
  4. Applies protocol rules from State account
  5. Updates UserAccount (adds order, updates positions, etc.)
  6. Updates market state if needed (AMM, funding, etc.)
  7. Emits event logs for offchain indexing

The SDK handles account passing automatically , you rarely need to manually construct the account list.

Remaining accounts pattern

Many instructions use “remaining accounts” to dynamically pass oracle, market, and user accounts. This lets a single instruction handle variable market sets without requiring specific account slots. Each SDK provides a method to build this list based on which markets your positions touch (e.g. getRemainingAccounts() in TypeScript).

Last updated on