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
TheUser 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
TheUserStats 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.