Skip to main content
Drift protocol uses a subaccount system where each user can have multiple trading accounts (subaccounts) under a single authority. Each subaccount tracks positions, orders, and collateral independently.

Account Structure

Authority vs Subaccounts

Each wallet (authority) can control multiple subaccounts:
use drift_sdk::Wallet;

// Derive subaccount addresses
let authority = wallet.authority();

// Subaccount 0 (default)
let subaccount_0 = Wallet::derive_user_account(&authority, 0);

// Additional subaccounts (up to 65,535)
let subaccount_1 = Wallet::derive_user_account(&authority, 1);
let subaccount_2 = Wallet::derive_user_account(&authority, 2);

// UserStats account (one per authority)
let user_stats = Wallet::derive_stats_account(&authority);

User Account

The User account holds positions, orders, and account state:
pub struct User {
    pub authority: Pubkey,
    pub delegate: Pubkey,
    pub name: [u8; 32],
    pub sub_account_id: u16,
    
    // Positions
    pub spot_positions: [SpotPosition; 8],
    pub perp_positions: [PerpPosition; 8],
    
    // Orders
    pub orders: [Order; 32],
    
    // Status flags
    pub status: u8,
    pub is_margin_trading_enabled: bool,
    pub idle: bool,
    
    // Liquidity tracking
    pub open_orders: u8,
    pub has_open_order: bool,
    pub open_auctions: u8,
    pub has_open_auction: bool,
    
    // ... additional fields
}

Accessing User Accounts

// Get user account
let user = client.get_user_account(&subaccount_0).await?;

println!("Authority: {}", user.authority);
println!("Sub-account: {}", user.sub_account_id);
println!("Open orders: {}", user.open_orders);
println!("Margin trading: {}", user.is_margin_trading_enabled);

// Get with slot information
let user_with_slot = client.get_user_account_with_slot(&subaccount_0).await?;
println!("User data from slot: {}", user_with_slot.slot);

Account Name

Subaccounts can have human-readable names:
// Name is stored as [u8; 32]
let name_bytes = user.name;

// Convert to string (strip null bytes)
let name = String::from_utf8_lossy(&name_bytes)
    .trim_end_matches('\0')
    .to_string();
println!("Account name: {}", name);

Delegated Access

Accounts can delegate trading permissions:
let user = client.get_user_account(&subaccount_0).await?;

if user.delegate != Pubkey::default() {
    println!("Account has delegate: {}", user.delegate);
}

// When building transactions as a delegate
let tx = client.init_tx(&subaccount_0, true).await?;  // delegated = true

UserStats Account

The UserStats account tracks aggregate statistics per authority:
pub struct UserStats {
    pub authority: Pubkey,
    pub referrer: Pubkey,
    pub referrer_status: u8,
    
    // Trading statistics
    pub fees: UserFees,
    pub taker_volume_30d: u64,
    pub maker_volume_30d: u64,
    pub filler_volume_30d: u64,
    
    // Liquidation tracking
    pub last_maker_volume_30d_ts: i64,
    pub last_taker_volume_30d_ts: i64,
    pub last_filler_volume_30d_ts: i64,
    
    pub if_staked_quote_asset_amount: u64,
    pub number_of_sub_accounts: u16,
    pub number_of_sub_accounts_created: u16,
    
    // ... additional fields
}

Accessing UserStats

// Get UserStats by authority
let stats = client.get_user_stats(&authority).await?;

println!("Number of subaccounts: {}", stats.number_of_sub_accounts);
println!("30d taker volume: {}", stats.taker_volume_30d);
println!("30d maker volume: {}", stats.maker_volume_30d);

// Check referrer status
if stats.is_referrer() {
    println!("User is a referrer");
}
if stats.is_referred() {
    println!("User was referred by: {}", stats.referrer);
}

Spot Positions

Spot positions represent token holdings:
pub struct SpotPosition {
    pub market_index: u16,
    pub balance_type: SpotBalanceType,
    pub scaled_balance: u64,
    pub open_bids: i64,
    pub open_asks: i64,
    pub cumulative_deposits: i64,
    pub open_orders: u8,
    // ... additional fields
}

pub enum SpotBalanceType {
    Deposit,
    Borrow,
}

Accessing Spot Positions

let user = client.get_user_account(&subaccount_0).await?;

// Get all active spot positions
let spot_positions: Vec<SpotPosition> = user.spot_positions
    .iter()
    .filter(|p| !p.is_available())
    .copied()
    .collect();

for position in spot_positions {
    println!("Spot market {}: balance {}", 
        position.market_index,
        position.scaled_balance
    );
}

// Get specific spot position
let usdc_position = client.spot_position(&subaccount_0, 0).await?;
if let Some(pos) = usdc_position {
    println!("USDC balance: {}", pos.scaled_balance);
    println!("Balance type: {:?}", pos.balance_type);
}

Spot Position Helpers

let position = user.spot_positions[0];

// Check if position slot is available (unused)
let is_empty = position.is_available();

// Check balance type
let is_deposit = matches!(position.balance_type, SpotBalanceType::Deposit);
let is_borrow = matches!(position.balance_type, SpotBalanceType::Borrow);

Perp Positions

Perp positions represent leveraged futures:
pub struct PerpPosition {
    pub market_index: u16,
    pub base_asset_amount: i64,
    pub quote_asset_amount: i64,
    pub quote_entry_amount: i64,
    pub quote_break_even_amount: i64,
    pub open_bids: i64,
    pub open_asks: i64,
    pub settled_pnl: i64,
    pub lp_shares: u64,
    pub last_cumulative_funding_rate: i64,
    pub last_funding_rate_ts: i64,
    pub open_orders: u8,
    // ... additional fields
}

Accessing Perp Positions

let user = client.get_user_account(&subaccount_0).await?;

// Get all open perp positions
let perp_positions: Vec<PerpPosition> = user.perp_positions
    .iter()
    .filter(|p| p.is_open_position())
    .copied()
    .collect();

for position in perp_positions {
    println!("Perp market {}: size {}", 
        position.market_index,
        position.base_asset_amount
    );
}

// Get specific perp position
let sol_position = client.perp_position(&subaccount_0, 0).await?;
if let Some(pos) = sol_position {
    let is_long = pos.base_asset_amount > 0;
    let direction = if is_long { "LONG" } else { "SHORT" };
    println!("SOL-PERP: {} {}", direction, pos.base_asset_amount.abs());
}

// Get unsettled positions (quote but no base)
let unsettled = client.unsettled_positions(&subaccount_0).await?;

Perp Position Helpers

let position = user.perp_positions[0];

// Check if position exists
let is_open = position.is_open_position();

// Check if position slot is available (unused)
let is_empty = position.is_available();

// Position direction
let is_long = position.base_asset_amount > 0;
let is_short = position.base_asset_amount < 0;

// LP shares
let has_lp = position.lp_shares > 0;

Orders

Users can have up to 32 open orders:
pub struct Order {
    pub slot: u64,
    pub price: u64,
    pub base_asset_amount: u64,
    pub base_asset_amount_filled: u64,
    pub quote_asset_amount_filled: u64,
    
    pub direction: PositionDirection,
    pub market_type: MarketType,
    pub market_index: u16,
    
    pub order_id: u32,
    pub user_order_id: u8,
    
    pub order_type: OrderType,
    pub status: OrderStatus,
    pub post_only: bool,
    pub reduce_only: bool,
    pub bit_flags: u8,
    
    // Trigger orders
    pub trigger_price: u64,
    pub trigger_condition: OrderTriggerCondition,
    
    // Auction parameters
    pub auction_start_price: i64,
    pub auction_end_price: i64,
    pub auction_duration: u8,
    
    // ... additional fields
}

pub enum OrderStatus {
    Init,
    Open,
    Filled,
    Canceled,
}

pub enum OrderType {
    Market,
    Limit,
    TriggerMarket,
    TriggerLimit,
    Oracle,
}

Querying Orders

// Get all open orders
let orders = client.all_orders(&subaccount_0).await?;

for order in orders {
    println!("Order {}: {} {:?} @ {}",
        order.order_id,
        order.market_index,
        order.direction,
        order.price
    );
}

// Get specific order by ID
let order = client.get_order_by_id(&subaccount_0, order_id).await?;
if let Some(order) = order {
    println!("Found order: {:?}", order.status);
}

// Get order by user-defined ID
let order = client.get_order_by_user_id(&subaccount_0, user_order_id).await?;

Order Filters

let user = client.get_user_account(&subaccount_0).await?;

// Filter by status
let open_orders: Vec<Order> = user.orders
    .iter()
    .filter(|o| o.status == OrderStatus::Open)
    .copied()
    .collect();

// Filter by market
let sol_orders: Vec<Order> = user.orders
    .iter()
    .filter(|o| o.market_index == 0 && o.market_type == MarketType::Perp)
    .copied()
    .collect();

// Filter by type
let limit_orders: Vec<Order> = user.orders
    .iter()
    .filter(|o| o.order_type == OrderType::Limit)
    .copied()
    .collect();

Order Flags

Orders have bit flags for additional metadata:
let order = user.orders[0];

// Check if order has a builder
if order.has_builder() {
    println!("Order placed via builder");
}

// Check if it's an oracle trigger market order
if order.is_oracle_trigger_market() {
    println!("Oracle trigger market order");
}

Account Subscriptions

Subscribe to user accounts for live updates:
// Subscribe to account
client.subscribe_account(&subaccount_0).await?;

// Now queries use cached data
let user = client.get_user_account(&subaccount_0).await?;

// Subscribe with callback
client.subscribe_account_with_callback(&subaccount_0, |update| {
    println!("Account updated at slot: {}", update.slot);
}).await?;

// Unsubscribe when done
client.unsubscribe_account(&subaccount_0)?;

Querying All Positions

Get all positions at once:
let (spot_positions, perp_positions) = client.all_positions(&subaccount_0).await?;

println!("Active spot positions: {}", spot_positions.len());
for pos in spot_positions {
    println!("  Market {}: {}", pos.market_index, pos.scaled_balance);
}

println!("Active perp positions: {}", perp_positions.len());
for pos in perp_positions {
    println!("  Market {}: {}", pos.market_index, pos.base_asset_amount);
}

Syncing Multiple Accounts

Load multiple user accounts efficiently:
use solana_rpc_client_api::filter::RpcFilterType;

// Sync all user accounts for an authority
let authority_filter = RpcFilterType::Memcmp(
    crate::memcmp::get_user_with_authority_filter(&authority)
);

client.sync_user_accounts(vec![authority_filter]).await?;

// Sync all UserStats accounts
client.sync_user_stats_accounts().await?;

Best Practices

Use Subaccounts for Isolation: Create separate subaccounts for different strategies or risk profiles. Each subaccount has independent margin and positions.
Subscribe for Live Updates: If monitoring an account actively, subscribe to it to get real-time updates via WebSocket or gRPC.
Check Position Slots: Position arrays have fixed sizes. Use is_available() to check if a slot is unused, and is_open_position() for perps.
Position arrays (spot_positions, perp_positions, orders) have maximum sizes (8, 8, and 32 respectively). Plan account usage accordingly.
UserStats is Global: The UserStats account is shared across all subaccounts for an authority. It tracks aggregate trading volume and referrer information.

Build docs developers (and LLMs) love