Skip to main content
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:
FeatureWebSocketgRPC
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
PerformanceGoodExcellent
Resource UsageLowHigher
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?;

Block Metadata

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

  1. Use appropriate commitment: Processed for lowest latency, Confirmed for reliability
  2. Keep callbacks fast: Don’t block the gRPC event loop
  3. Enable only what you need: Usermap uses significant memory
  4. Monitor connection health: Implement reconnection logic for production
  5. 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

Build docs developers (and LLMs) love