gRPC subscriptions provide the highest performance way to receive real-time updates from Drift. This guide covers advanced gRPC configuration using GrpcSubscribeOpts.
Understanding gRPC vs WebSocket
Drift SDK supports two subscription methods:
| Feature | WebSocket | gRPC |
|---|
| Markets & Oracles | ✅ Yes | ✅ Yes |
| All User Accounts | ❌ No | ✅ Yes |
| Transaction Updates | ❌ No | ✅ Yes |
| Slot Updates | ❌ Limited | ✅ Full |
| Inter-slot Updates | ❌ No | ✅ Yes |
| Block Metadata | ❌ No | ✅ Yes |
| Performance | Good | Excellent |
| Resource Usage | Low | Higher |
gRPC requires a Yellowstone-compatible gRPC endpoint. Most premium RPC providers support this, including Helius, Triton, and others.
Basic gRPC Setup
Simple Subscription
use drift_rs::{DriftClient, GrpcSubscribeOpts};
use solana_commitment_config::CommitmentLevel;
// Initialize client
let drift = DriftClient::new(context, rpc_client, wallet).await?;
let grpc_url = std::env::var("GRPC_URL")?;
let grpc_token = std::env::var("GRPC_X_TOKEN")?; // Authentication token
// Subscribe with default options
drift.grpc_subscribe(
grpc_url,
grpc_token,
GrpcSubscribeOpts::default(),
true, // sync all accounts on startup
)
.await?;
println!("gRPC subscription active!");
Setting Commitment Level
let opts = GrpcSubscribeOpts::default()
.commitment(CommitmentLevel::Confirmed); // or Processed, Finalized
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Subscribing to User Accounts
Subscribe to All User Accounts (Usermap)
Cache all Drift user accounts for DLOB building or market analysis:
let opts = GrpcSubscribeOpts::default()
.usermap_on(); // Caches ALL user accounts (~2GB memory)
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
// Access cached user accounts
let some_user_pubkey = /* ... */;
let user_account = drift.try_get_account(&some_user_pubkey)?;
Enabling usermap caches all ~50k+ Drift user accounts and requires approximately 2GB of memory. Only enable this if you need access to all user accounts (e.g., for DLOB building).
Subscribe to Specific User Accounts
For lower memory usage, subscribe only to specific accounts:
let wallet = drift.wallet();
let user_accounts = vec![
wallet.sub_account(0),
wallet.sub_account(1),
wallet.sub_account(2),
];
let opts = GrpcSubscribeOpts::default()
.user_accounts(user_accounts);
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Subscribe to User Stats
let opts = GrpcSubscribeOpts::default()
.statsmap_on(); // Cache all UserStats accounts
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Custom Callbacks
Slot Updates
Receive notifications on every new slot:
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
let last_slot = Arc::new(AtomicU64::new(0));
let last_slot_clone = last_slot.clone();
let opts = GrpcSubscribeOpts::default()
.on_slot(move |slot| {
let prev = last_slot_clone.swap(slot, Ordering::Relaxed);
if slot > prev {
println!("New slot: {} (+{})", slot, slot - prev);
}
});
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Slot callbacks are called very frequently (every ~400ms on Solana). Keep your callback logic fast and non-blocking.
Account Updates
Receive notifications when any account updates:
use drift_rs::types::AccountUpdate;
let opts = GrpcSubscribeOpts::default()
.on_user_account(|update: &AccountUpdate| {
println!(
"User account {} updated at slot {}",
update.pubkey,
update.slot
);
});
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Custom Account Filters
Use AccountFilter to match specific account types:
use drift_rs::grpc::grpc_subscriber::AccountFilter;
use anchor_lang::Discriminator;
use drift_rs::types::accounts::User;
// Filter for User accounts only
let user_filter = AccountFilter::partial()
.with_discriminator(User::DISCRIMINATOR);
let opts = GrpcSubscribeOpts::default()
.on_account(user_filter, |update| {
// Deserialize user account
use anchor_lang::AccountDeserialize;
if let Ok(user) = User::try_deserialize(&mut &update.data[..]) {
println!("User {} updated", user.authority);
}
});
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Oracle Updates
let opts = GrpcSubscribeOpts::default()
.on_oracle_update(|update| {
println!(
"Oracle {} updated: {} bytes at slot {}",
update.pubkey,
update.data.len(),
update.slot
);
});
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Transaction Updates
Monitor transactions involving specific accounts:
use drift_rs::grpc::TransactionUpdate;
let watched_accounts = vec![
wallet.default_sub_account(),
/* other accounts to watch */
];
let opts = GrpcSubscribeOpts::default()
.transaction_include_accounts(watched_accounts)
.on_transaction(|tx: &TransactionUpdate| {
println!(
"Transaction in slot {}: is_vote={}",
tx.slot,
tx.is_vote
);
});
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
let opts = GrpcSubscribeOpts::default()
.subscribe_block_meta(true)
.on_block_meta(|meta| {
println!(
"Block {}: parent_slot={}, executed_tx_count={}",
meta.slot,
meta.parent_slot,
meta.executed_transaction_count
);
});
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Advanced Configuration
Inter-slot Updates
Receive updates between slots (not just at slot boundaries):
let opts = GrpcSubscribeOpts::default()
.interslot_updates_on(); // Receive more frequent updates
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Inter-slot updates increase callback frequency significantly. Use this when you need the absolute lowest latency data.
Connection Options
use drift_rs::grpc::grpc_subscriber::GrpcConnectionOpts;
use std::time::Duration;
let connection_opts = GrpcConnectionOpts {
connect_timeout: Duration::from_secs(10),
request_timeout: Duration::from_secs(10),
subscribe_timeout: Duration::from_secs(10),
..Default::default()
};
let opts = GrpcSubscribeOpts::default()
.connection_opts(connection_opts);
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Disable Oracle Caching
If you don’t need oracle data:
let opts = GrpcSubscribeOpts::default()
.oraclemap_off(); // Don't cache oracle updates
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
Complete Example: Market Maker with gRPC
use drift_rs::{
DriftClient, Context, RpcClient, Wallet, GrpcSubscribeOpts, MarketId,
types::{
OrderParams, OrderType, PositionDirection, MarketType, PostOnlyParam,
accounts::User,
},
TransactionBuilder,
math::constants::{BASE_PRECISION_U64, PRICE_PRECISION_U64},
};
use solana_commitment_config::CommitmentLevel;
use std::time::Duration;
use tokio::time::interval;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
dotenv::dotenv().ok();
let wallet: Wallet = drift_rs::utils::load_keypair_multi_format(
&std::env::var("PRIVATE_KEY")?,
)?
.into();
let rpc_url = std::env::var("RPC_URL")?;
let drift = DriftClient::new(
Context::MainNet,
RpcClient::new(rpc_url),
wallet.clone(),
)
.await?;
// Subscribe to blockhashes
drift.subscribe_blockhashes().await?;
let grpc_url = std::env::var("GRPC_URL")?;
let grpc_token = std::env::var("GRPC_X_TOKEN")?;
// Set up gRPC with usermap for fast transaction building
drift.grpc_subscribe(
grpc_url,
grpc_token,
GrpcSubscribeOpts::default()
.commitment(CommitmentLevel::Processed)
.usermap_on()
.on_slot(|slot| {
// Slot callback - keep it fast!
log::debug!("Slot: {}", slot);
}),
true, // sync on startup
)
.await?;
println!("gRPC subscription active");
let subaccount = wallet.sub_account(0);
let market_id = drift.market_lookup("sol-perp").unwrap();
let market_info = drift.try_get_perp_market_account(market_id.index())?;
// Requote every 400ms
let mut requote_interval = interval(Duration::from_millis(400));
loop {
requote_interval.tick().await;
// Get cached account data (fast!)
let subaccount_data: User = drift.try_get_account(&subaccount)?;
let oracle_data = drift.try_get_oracle_price_data_and_slot(market_id).unwrap();
let oracle_price = oracle_data.data.price as u64;
let spread = 1 * PRICE_PRECISION_U64; // $1 spread
let size = 5 * BASE_PRECISION_U64; // 5 SOL
let orders = vec![
OrderParams {
order_type: OrderType::Limit,
price: oracle_price.saturating_sub(spread / 2),
base_asset_amount: size,
direction: PositionDirection::Long,
market_type: MarketType::Perp,
market_index: market_info.market_index,
post_only: PostOnlyParam::MustPostOnly,
user_order_id: 1,
..Default::default()
},
OrderParams {
order_type: OrderType::Limit,
price: oracle_price.saturating_add(spread / 2),
base_asset_amount: size,
direction: PositionDirection::Short,
market_type: MarketType::Perp,
market_index: market_info.market_index,
post_only: PostOnlyParam::MustPostOnly,
user_order_id: 2,
..Default::default()
},
];
let tx = TransactionBuilder::new(
drift.program_data(),
subaccount,
std::borrow::Cow::Borrowed(&subaccount_data),
false,
)
.with_priority_fee(1_000, Some(100_000))
.cancel_orders((market_info.market_index, MarketType::Perp), None)
.place_orders(orders)
.build();
match drift.sign_and_send(tx).await {
Ok(sig) => println!("Orders placed: {}", sig),
Err(err) => eprintln!("Error: {}", err),
}
}
}
DLOB Integration
Combine gRPC with DLOB for complete order book visibility:
use drift_rs::dlob::builder::DLOBBuilder;
// Initialize DLOB
let account_map = drift.backend().account_map();
account_map
.sync_user_accounts(vec![
drift_rs::memcmp::get_user_with_order_filter()
])
.await?;
let dlob_builder = DLOBBuilder::new(account_map);
let markets = vec![MarketId::perp(0), MarketId::perp(1)];
// Subscribe with DLOB handlers
let opts = GrpcSubscribeOpts::default()
.commitment(CommitmentLevel::Confirmed)
.usermap_on()
.on_user_account(dlob_builder.account_update_handler(account_map))
.on_slot(dlob_builder.slot_update_handler(drift.clone(), markets));
drift.grpc_subscribe(grpc_url, grpc_token, opts, true).await?;
let dlob = dlob_builder.dlob();
println!("DLOB ready with live gRPC updates");
Unsubscribing
Clean up gRPC subscriptions:
// Stop gRPC subscription
drift.grpc_unsubscribe();
// After unsubscribing, WebSocket or RPC will be used
let market = drift.get_perp_market_account(0).await?;
Best Practices
- Use appropriate commitment:
Processed for lowest latency, Confirmed for reliability
- Keep callbacks fast: Don’t block the gRPC event loop
- Enable only what you need: Usermap uses significant memory
- Monitor connection health: Implement reconnection logic for production
- Handle sync carefully: Set
sync=true on startup to ensure consistency
gRPC subscriptions cannot be combined with WebSocket subscriptions for the same data. If you call grpc_subscribe(), subsequent calls to subscribe_markets() or similar WebSocket methods will fail.
Connection Management
Monitor and handle gRPC connection issues:
// Check if gRPC is active
let is_subscribed = drift.backend().is_grpc_subscribed();
if is_subscribed {
println!("gRPC subscription is active");
} else {
// Reconnect logic here
drift.grpc_subscribe(grpc_url, grpc_token, opts, false).await?;
}
Next Steps