Skip to main content
The Drift SDK provides real-time updates through WebSocket subscriptions. This guide covers how to subscribe to market data, oracle prices, and account changes.

Understanding Subscriptions

By default, DriftClient queries data over RPC when you call methods like get_perp_market_account() or oracle_price(). After subscribing, these same methods transparently use cached WebSocket data, providing:
  • Lower latency
  • Reduced RPC load
  • Real-time updates
  • Lower costs

Subscribing to Markets

Subscribe to Specific Markets

use drift_rs::MarketId;

// Subscribe to specific markets
let markets = vec![
    MarketId::perp(0),  // SOL-PERP
    MarketId::perp(1),  // BTC-PERP
    MarketId::spot(0),  // USDC
    MarketId::spot(1),  // SOL
];

drift_client.subscribe_markets(&markets)
    .await
    .expect("Failed to subscribe to markets");

// Now queries use cached WebSocket data
let sol_perp_market = drift_client.get_perp_market_account(0).await?;
println!("SOL-PERP market: {:?}", sol_perp_market);

Subscribe to All Markets

// Subscribe to all active perp markets
drift_client.subscribe_all_perp_markets().await?;

// Subscribe to all active spot markets
drift_client.subscribe_all_spot_markets().await?;

// Subscribe to all active markets (both perp and spot)
drift_client.subscribe_all_markets().await?;

Market Subscriptions with Callbacks

Receive notifications when market data changes:
use drift_rs::types::AccountUpdate;
use anchor_lang::AccountDeserialize;
use drift_rs::types::accounts::PerpMarket;
use std::time::Instant;

let start_time = Instant::now();

let market_callback = move |update: &AccountUpdate| {
    // Deserialize the market data
    match PerpMarket::try_deserialize(&mut &update.data[..]) {
        Ok(market) => {
            let elapsed = start_time.elapsed().as_secs();
            println!(
                "[{}s] Market {} updated: base_asset_amount = {}",
                elapsed,
                market.market_index,
                market.amm.base_asset_amount_with_amm
            );
        }
        Err(e) => eprintln!("Failed to deserialize market: {}", e),
    }
};

let markets = vec![MarketId::perp(0)];
drift_client
    .subscribe_markets_with_callback(&markets, market_callback)
    .await?;

Subscribing to Oracles

Subscribe to Oracle Prices

use drift_rs::MarketId;

// Subscribe to oracle prices for specific markets
let markets = vec![
    MarketId::perp(0),  // SOL oracle
    MarketId::perp(1),  // BTC oracle
];

drift_client.subscribe_oracles(&markets).await?;

// Query oracle price (now uses cached data)
let sol_price = drift_client.oracle_price(MarketId::perp(0)).await?;
println!("SOL price: ${:.2}", sol_price as f64 / 1_000_000.0);

Subscribe to All Oracles

// Subscribe to all perp market oracles
drift_client.subscribe_all_perp_oracles().await?;

// Subscribe to all spot market oracles
drift_client.subscribe_all_spot_oracles().await?;

// Subscribe to all oracles
drift_client.subscribe_all_oracles().await?;

Oracle Subscriptions with Callbacks

let oracle_callback = move |update: &AccountUpdate| {
    println!(
        "Oracle updated: pubkey={}, slot={}, lamports={}",
        update.pubkey, update.slot, update.lamports
    );
};

let markets = vec![MarketId::perp(0)];
drift_client
    .subscribe_oracles_with_callback(&markets, oracle_callback)
    .await?;

Subscribing to User Accounts

Subscribe to a User Account

use drift_rs::Wallet;

// Subscribe to your own sub-account
let subaccount = wallet.default_sub_account();
drift_client.subscribe_account(&subaccount).await?;

// Query the account (uses cached data)
let user_account = drift_client.get_user_account(&subaccount).await?;
println!("User authority: {}", user_account.authority);

Polled Account Subscriptions

For accounts that change infrequently, use polling instead of WebSocket:
use std::time::Duration;

// Poll every 30 seconds
drift_client
    .subscribe_account_polled(
        &subaccount,
        Duration::from_secs(30)
    )
    .await?;

Subscribing to Blockhashes

For fast transaction building, subscribe to live blockhashes:
// Subscribe to blockhashes (polls every 2 seconds)
drift_client.subscribe_blockhashes().await?;

// Get latest blockhash from cache
let blockhash = drift_client.get_latest_blockhash().await?;
Always subscribe to blockhashes when building and sending transactions frequently. This eliminates the RPC round-trip for fetching recent blockhashes.

Complete Example: Market Monitor

use drift_rs::{Context, DriftClient, RpcClient, Wallet, MarketId};
use std::time::{Duration, Instant};
use tokio::time::timeout;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();
    dotenv::dotenv().ok();
    
    // Initialize client
    let rpc_url = std::env::var("RPC_URL")
        .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
    let dummy_wallet = Wallet::read_only(
        solana_sdk::pubkey!("11111111111111111111111111111111")
    );
    
    let client = DriftClient::new(
        Context::MainNet,
        RpcClient::new(rpc_url),
        dummy_wallet,
    )
    .await?;
    
    println!("✅ Connected to Drift protocol\n");
    
    // Get available markets
    let perp_markets = client.get_all_perp_market_ids();
    println!("📈 Found {} active perp markets", perp_markets.len());
    
    let start_time = Instant::now();
    
    // Set up callback for market updates
    let market_callback = move |update: &drift_rs::types::AccountUpdate| {
        use anchor_lang::AccountDeserialize;
        use drift_rs::types::accounts::PerpMarket;
        
        if let Ok(market) = PerpMarket::try_deserialize(&mut &update.data[..]) {
            let elapsed = start_time.elapsed().as_secs();
            println!(
                "[{}s] Market {}: base_asset_amount = {}",
                elapsed, market.market_index, market.amm.base_asset_amount_with_amm
            );
        }
    };
    
    // Subscribe to markets with callback
    client
        .subscribe_markets_with_callback(&perp_markets, market_callback)
        .await?;
    
    println!("⏰ Running for 30 seconds...\n");
    
    // Run for 30 seconds
    timeout(
        Duration::from_secs(30),
        tokio::signal::ctrl_c(),
    )
    .await
    .ok();
    
    // Cleanup
    client.unsubscribe().await?;
    println!("\n✅ Unsubscribed from all updates");
    
    Ok(())
}

Unsubscribing

To stop receiving updates and close WebSocket connections:
// Unsubscribe from all subscriptions
drift_client.unsubscribe().await?;

// After unsubscribing, queries will use RPC again
let market = drift_client.get_perp_market_account(0).await?;
After calling unsubscribe(), subsequent queries will fetch data over RPC, which is slower and more expensive than using cached WebSocket data.

Best Practices

  1. Subscribe Early: Set up subscriptions before your main application logic runs
  2. Use Callbacks Wisely: Keep callback functions fast and non-blocking
  3. Subscribe to What You Need: Only subscribe to markets/oracles you’ll actually use
  4. Unsubscribe on Exit: Always clean up subscriptions when your application exits

Next Steps

Build docs developers (and LLMs) love