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
Atomic counter tracking the most recent slot seen across all subscriptions
Shared WebSocket pubsub client for market account subscriptions
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:
- First tries
getProgramAccounts (most efficient but not all RPCs support it)
- Falls back to
getMultipleAccounts in batches of 64
- 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