The drift-rs SDK provides a powerful subscription model that keeps local data synchronized with on-chain state via WebSocket or gRPC. This eliminates repeated RPC calls and ensures real-time updates.
Subscription Types
The SDK supports three subscription backends:
- WebSocket - Individual account subscriptions via Solana’s WebSocket API
- gRPC - High-throughput subscriptions via Yellowstone gRPC (Geyser)
- RPC Polling - Fallback polling for environments without WebSocket access
WebSocket Subscriptions
Market Subscriptions
Subscribe to spot and perp market accounts for live updates:
let markets = [MarketId::perp(0), MarketId::spot(1), MarketId::spot(2)];
// Subscribe to market account updates
client.subscribe_markets(&markets).await?;
// Subscribe to all markets
client.subscribe_all_markets().await?;
// Subscribe to just spot markets
client.subscribe_all_spot_markets().await?;
// Subscribe to just perp markets
client.subscribe_all_perp_markets().await?;
After subscribing, market queries use cached data:
// No RPC call - uses cached data
let sol_perp = client.get_perp_market_account(0).await?;
let usdc_spot = client.get_spot_market_account(0).await?;
// Try cached only (errors if not subscribed)
let btc_perp = client.try_get_perp_market_account(1)?;
Oracle Subscriptions
Subscribe to oracle price feeds by market:
let markets = [MarketId::perp(0), MarketId::spot(1)];
// Subscribe to oracle updates
client.subscribe_oracles(&markets).await?;
// Subscribe to all oracles
client.subscribe_all_oracles().await?;
// Now oracle queries use cached data
let price = client.oracle_price(MarketId::perp(0)).await?;
let oracle_data = client.try_get_oracle_price_data_and_slot(MarketId::perp(0))?;
Markets can share oracle pubkeys. The SDK automatically deduplicates subscriptions to ensure only one WebSocket subscription per unique oracle.
Account Subscriptions
Subscribe to individual User or UserStats accounts:
// Subscribe to a user account (subaccount)
client.subscribe_account(&user_pubkey).await?;
// Get data from cache
let user = client.get_user_account(&user_pubkey).await?;
// Unsubscribe when done
client.unsubscribe_account(&user_pubkey)?;
Blockhash Subscription
Subscribe to latest blockhashes for transaction signing:
// Start blockhash subscription
client.subscribe_blockhashes().await?;
// Get latest blockhash (cached, refreshed every 2 seconds)
let blockhash = client.get_latest_blockhash().await?;
Subscription with Callbacks
Register callbacks to react to updates:
let markets = [MarketId::perp(0)];
client.subscribe_markets_with_callback(&markets, |update| {
println!("Market updated: {:?} at slot {}", update.pubkey, update.slot);
}).await?;
client.subscribe_oracles_with_callback(&markets, |update| {
println!("Oracle updated: {:?}", update.pubkey);
}).await?;
gRPC Subscriptions (Yellowstone)
For high-performance applications, use gRPC subscriptions:
use drift_sdk::GrpcSubscribeOpts;
use solana_sdk::commitment_config::CommitmentLevel;
let opts = GrpcSubscribeOpts {
// Cache all perp and spot markets
oraclemap: true,
// Cache all User accounts
usermap: true,
// Cache all UserStats accounts
user_stats_map: true,
// Commitment level
commitment: Some(CommitmentLevel::Confirmed),
// Enable interslot updates for faster data
interslot_updates: true,
..Default::default()
};
// Start gRPC subscription
client.grpc_subscribe(
"http://grpc.endpoint.com:10000".to_string(),
"your-x-token".to_string(),
opts,
true // sync on startup
).await?;
// All queries now use gRPC-synced cache
let sol_perp = client.get_perp_market_account(0).await?;
let user = client.get_user_account(&user_pubkey).await?;
// Unsubscribe gRPC
client.grpc_unsubscribe();
gRPC and WebSocket subscriptions are mutually exclusive. Attempting to use WebSocket subscriptions after connecting via gRPC will return an error.
gRPC with Custom Callbacks
Register custom callbacks for gRPC updates:
use drift_sdk::grpc::AccountFilter;
use drift_sdk::types::accounts::User;
let mut opts = GrpcSubscribeOpts::default();
// Add custom account filter and callback
let filter = AccountFilter::partial()
.with_discriminator(User::DISCRIMINATOR);
opts.on_account = Some(vec![
(filter, Box::new(|update| {
println!("User account updated: {}", update.pubkey);
}))
]);
// Add slot update callback
opts.on_slot = Some(Box::new(|slot| {
println!("New slot: {}", slot);
}));
// Add transaction callback
opts.on_transaction = Some(Box::new(|tx_update| {
println!("Transaction: slot {}", tx_update.slot);
}));
client.grpc_subscribe(endpoint, token, opts, true).await?;
gRPC Connection Options
Configure connection settings:
use drift_sdk::grpc::GrpcConnectionOpts;
let connection_opts = GrpcConnectionOpts {
timeout_ms: Some(30_000),
connect_timeout_ms: Some(10_000),
max_decoding_message_size: 1024 * 1024 * 512, // 512MB
compression: true,
..Default::default()
};
let mut opts = GrpcSubscribeOpts::default();
opts.connection_opts = connection_opts;
RPC Polling Subscriptions
For environments without WebSocket support:
use std::time::Duration;
// Poll account every 5 seconds
client.subscribe_account_polled(
&user_pubkey,
Duration::from_secs(5)
).await?;
// Data is refreshed via polling
let user = client.get_user_account(&user_pubkey).await?;
Caching Behavior
MarketMap
Market accounts are cached in MarketMap<T> structures:
pub struct MarketMap<T> {
marketmap: Arc<DashMap<u16, DataAndSlot<T>>>,
subscriptions: DashMap<u16, UnsubHandle>,
latest_slot: Arc<AtomicU64>,
// ...
}
Key features:
- Thread-safe: Uses
DashMap for concurrent access
- Slot-aware: Tracks slot for each update
- Automatic updates: WebSocket streams update the map automatically
OracleMap
Oracle prices are cached by (Pubkey, OracleSource):
pub struct OracleMap {
oraclemap: Arc<DashMap<(Pubkey, u8), Oracle>>,
oracle_by_market: ReadOnlyView<MarketId, (Pubkey, OracleSource)>,
// ...
}
pub struct Oracle {
pub pubkey: Pubkey,
pub data: OraclePriceData,
pub source: OracleSource,
pub slot: u64,
pub raw: Vec<u8>,
}
Key features:
- Source-aware: Same oracle pubkey can be used with different sources (e.g., PythLazer vs PythLazer1M)
- Shared oracles: Markets sharing oracles only create one subscription
- Mixed sources: Handles markets using the same oracle with different precision
AccountMap
User accounts are cached in AccountMap:
pub struct AccountMap {
inner: Arc<DashMap<Pubkey, AccountSlot>>,
subscriptions: Arc<DashMap<Pubkey, AccountSub<Subscribed>>>,
// ...
}
struct AccountSlot {
raw: Arc<[u8]>,
slot: Slot,
write_version: u64,
}
Supports:
- WebSocket subscriptions
- RPC polling
- gRPC updates with write version tracking
Data Freshness
All subscriptions track the slot when data was last updated:
// Get data with slot information
let market = client.get_perp_market_account_and_slot(0).await?;
println!("Market data from slot: {}", market.slot);
let user = client.get_user_account_with_slot(&user_pubkey).await?;
println!("User data from slot: {}", user.slot);
let oracle = client.get_oracle_price_data_and_slot(MarketId::perp(0)).await?;
println!("Oracle price from slot: {}", oracle.slot);
Unsubscribing
Unsubscribe All
// Unsubscribe from all WebSocket subscriptions
client.unsubscribe().await?;
// Unsubscribe from gRPC
client.grpc_unsubscribe();
Unsubscribe Specific Accounts
// Unsubscribe from specific account
client.unsubscribe_account(&user_pubkey)?;
Best Practices
Use gRPC for Production: For high-frequency applications, gRPC provides better performance and lower latency than WebSocket subscriptions.
Subscribe to What You Need: Only subscribe to markets and accounts your application actively uses to minimize network overhead.
Handle Reconnections: The SDK automatically reconnects WebSocket subscriptions on failure. gRPC subscriptions retry up to 3 times.
Subscriptions are a no-op if already active. Multiple calls to subscribe_markets with the same markets won’t create duplicate subscriptions.