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:
- Loads accounts passed in the instruction
- Validates account ownership and PDAs
- Loads oracle price data
- Applies protocol rules from State account
- Updates UserAccount (adds order, updates positions, etc.)
- Updates market state if needed (AMM, funding, etc.)
- 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).
Related
- Account Model - Detailed account structures
- SDK Internals - How the SDK subscribes to and caches these accounts