Skip to main content

Overview

MarketMap<T> is a generic container that manages real-time market account data for either spot or perp markets. It provides subscription capabilities via WebSocket and periodic sync methods to keep market data up-to-date.

Generic Type

The MarketMap is generic over market types:
  • MarketMap<SpotMarket> - For spot markets
  • MarketMap<PerpMarket> - For perpetual futures markets
Both types must implement the Market trait.

Structure

pub struct MarketMap<T: AnchorDeserialize + Send> {
    pub marketmap: Arc<DashMap<u16, DataAndSlot<T>, ahash::RandomState>>,
    subscriptions: DashMap<u16, UnsubHandle, ahash::RandomState>,
    latest_slot: Arc<AtomicU64>,
    pubsub: Arc<PubsubClient>,
    commitment: CommitmentConfig,
}

Fields

marketmap
Arc<DashMap<u16, DataAndSlot<T>>>
Thread-safe concurrent hash map storing market data by market index. The DataAndSlot<T> wrapper includes:
  • data: T - The market account data (SpotMarket or PerpMarket)
  • slot: u64 - The Solana slot when this data was retrieved
subscriptions
DashMap<u16, UnsubHandle>
Map of active WebSocket subscriptions by market index
latest_slot
Arc<AtomicU64>
Atomic counter tracking the most recent slot seen across all subscriptions
pubsub
Arc<PubsubClient>
Shared WebSocket pubsub client for market account subscriptions
commitment
CommitmentConfig
Solana commitment level for account subscriptions (e.g., Confirmed, Finalized)

Constructor

new()

pub fn new(pubsub: Arc<PubsubClient>, commitment: CommitmentConfig) -> Self
Creates a new MarketMap instance. Parameters:
  • pubsub - Shared PubsubClient for WebSocket connections
  • commitment - Commitment level for account subscriptions
Returns: A new MarketMap<T> instance Example:
use drift::{MarketMap, PerpMarket};
use drift_pubsub_client::PubsubClient;
use solana_sdk::commitment_config::CommitmentConfig;
use std::sync::Arc;

let pubsub = Arc::new(PubsubClient::new(ws_url).await?);
let market_map = MarketMap::<PerpMarket>::new(
    pubsub,
    CommitmentConfig::confirmed(),
);

Data Access Methods

map()

pub fn map(&self) -> Arc<MapOf<u16, DataAndSlot<T>>>
Returns a reference to the internal concurrent map structure for direct access. Returns: Arc-wrapped reference to the DashMap

get()

pub fn get(&self, market_index: &u16) -> Option<DataAndSlot<T>>
Retrieve market data for a specific market index. Parameters:
  • market_index - The market index to look up
Returns: Some(DataAndSlot<T>) if the market exists, None otherwise Example:
if let Some(market_data) = market_map.get(&0) {
    println!("Retrieved at slot: {}", market_data.slot);
    println!("Market index: {}", market_data.data.market_index());
}

contains()

pub fn contains(&self, market_index: &u16) -> bool
Check if a market exists in the map. Returns: true if the market index exists

values()

pub fn values(&self) -> Vec<T>
Get all market data as a vector. Returns: Vector of all market structs (cloned)

oracles()

pub fn oracles(&self) -> Vec<(MarketId, Pubkey, OracleSource)>
Get oracle information for all markets in the map. Returns: Vector of tuples containing market ID, oracle pubkey, and oracle source for each market

len()

pub fn len(&self) -> usize
Returns the number of markets in the map.

get_latest_slot()

pub fn get_latest_slot(&self) -> u64
Get the most recent slot seen across all market updates. Returns: The latest slot number

Subscription Methods

subscribe()

pub async fn subscribe(&self, markets: &[MarketId]) -> SdkResult<()>
Subscribe to WebSocket updates for specified markets without a custom callback. Parameters:
  • markets - Slice of MarketId to subscribe to
Returns: SdkResult<()> Example:
use drift::MarketId;

// Subscribe to perp markets 0, 1, and 2
market_map.subscribe(&[
    MarketId::perp(0),
    MarketId::perp(1),
    MarketId::perp(2),
]).await?;

subscribe_with_callback()

pub async fn subscribe_with_callback<F>(
    &self,
    markets: &[MarketId],
    on_account: F,
) -> SdkResult<()>
where
    F: Fn(&AccountUpdate) + Send + Sync + 'static + Clone
Subscribe to market updates with a custom callback function. Parameters:
  • markets - Slice of MarketId to subscribe to
  • on_account - Callback function invoked on each account update
Returns: SdkResult<()> Example:
market_map.subscribe_with_callback(
    &[MarketId::perp(0)],
    |update| {
        println!("Market updated at slot {}", update.slot);
    },
).await?;

is_subscribed()

pub fn is_subscribed(&self, market_index: u16) -> bool
Check if a market is currently subscribed to WebSocket updates. Parameters:
  • market_index - The market index to check
Returns: true if subscribed, false otherwise

unsubscribe()

pub fn unsubscribe(&self, markets: &[MarketId]) -> SdkResult<()>
Unsubscribe from market updates and remove markets from the map. Parameters:
  • markets - Slice of MarketId to unsubscribe from
Returns: SdkResult<()> Example:
market_map.unsubscribe(&[MarketId::perp(0), MarketId::perp(1)])?;

unsubscribe_all()

pub fn unsubscribe_all(&self) -> SdkResult<()>
Unsubscribe from all markets and clear the map. Returns: SdkResult<()>

Sync Method

sync()

pub async fn sync(&self, rpc: &RpcClient) -> SdkResult<()>
Manually fetch and update all market accounts via RPC. This method uses multiple fallback strategies:
  1. First tries getProgramAccounts (most efficient but not all RPCs support it)
  2. Falls back to getMultipleAccounts in batches of 64
  3. Finally tries individual getAccount calls
Useful for periodic updates when not using WebSocket subscriptions. Parameters:
  • rpc - RpcClient instance for fetching account data
Returns: SdkResult<()> Example:
use solana_rpc_client::nonblocking::rpc_client::RpcClient;

let rpc = RpcClient::new(rpc_url.to_string());
market_map.sync(&rpc).await?;

Market Trait

Both SpotMarket and PerpMarket implement the Market trait:
pub trait Market {
    const MARKET_TYPE: MarketType;
    fn market_index(&self) -> u16;
    fn oracle_info(&self) -> (MarketId, Pubkey, OracleSource);
}

For SpotMarket

  • MARKET_TYPE = MarketType::Spot
  • market_index() returns self.market_index
  • oracle_info() returns (MarketId::spot(index), self.oracle, self.oracle_source)

For PerpMarket

  • MARKET_TYPE = MarketType::Perp
  • market_index() returns self.market_index
  • oracle_info() returns (MarketId::perp(index), self.amm.oracle, self.amm.oracle_source)

Complete Usage Example

use drift::{
    MarketMap, PerpMarket, SpotMarket, MarketId,
};
use drift_pubsub_client::PubsubClient;
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize PubsubClient
    let ws_url = "wss://api.mainnet-beta.solana.com";
    let pubsub = Arc::new(PubsubClient::new(ws_url).await?);
    
    // Create market maps
    let perp_map = MarketMap::<PerpMarket>::new(
        Arc::clone(&pubsub),
        CommitmentConfig::confirmed(),
    );
    
    let spot_map = MarketMap::<SpotMarket>::new(
        pubsub,
        CommitmentConfig::confirmed(),
    );
    
    // Subscribe to perp markets
    perp_map.subscribe(&[
        MarketId::perp(0),  // SOL-PERP
        MarketId::perp(1),  // BTC-PERP
    ]).await?;
    
    // Subscribe to spot markets
    spot_map.subscribe(&[
        MarketId::spot(0),  // USDC
        MarketId::spot(1),  // SOL
    ]).await?;
    
    // Wait for data to populate
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    
    // Access market data
    if let Some(sol_perp) = perp_map.get(&0) {
        let market = &sol_perp.data;
        println!("SOL-PERP funding rate: {}", market.amm.last_funding_rate);
        println!("Retrieved at slot: {}", sol_perp.slot);
    }
    
    // Get all oracle info
    let perp_oracles = perp_map.oracles();
    for (market_id, oracle, source) in perp_oracles {
        println!("Market {:?} uses oracle {} ({:?})", market_id, oracle, source);
    }
    
    // Manually sync if needed
    let rpc = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
    perp_map.sync(&rpc).await?;
    
    // Unsubscribe when done
    perp_map.unsubscribe_all()?;
    spot_map.unsubscribe_all()?;
    
    Ok(())
}

Helper Function

get_market_accounts_with_fallback()

pub async fn get_market_accounts_with_fallback<T: Market + AnchorDeserialize>(
    rpc: &RpcClient,
) -> SdkResult<(Vec<T>, Slot)>
Standalone function to fetch all markets of a given type with automatic fallback strategies. Returns: Tuple of (market accounts vector, slot number) Example:
use drift::{get_market_accounts_with_fallback, PerpMarket};

let (perp_markets, slot) = get_market_accounts_with_fallback::<PerpMarket>(&rpc).await?;
println!("Found {} perp markets at slot {}", perp_markets.len(), slot);

Thread Safety

The MarketMap is designed to be thread-safe:
  • Uses Arc for shared ownership across threads
  • Uses DashMap for concurrent access without external locking
  • Uses AtomicU64 for lock-free slot tracking

See Also

Build docs developers (and LLMs) love