Skip to main content
This example demonstrates how to subscribe to Drift protocol events in real-time using either WebSocket or gRPC connections. It processes events like order fills, cancellations, and other protocol activities.

What It Does

The event subscriber:
  • Connects to Drift protocol event streams
  • Processes events as they occur on-chain
  • Supports both WebSocket and gRPC subscriptions
  • Filters and handles specific event types
  • Demonstrates event data structure and usage

Complete Source Code

use clap::Parser;
use dotenv;
use drift_rs::event_subscriber::{DriftEvent, EventSubscriber};
use drift_rs::{PubsubClient, constants::PROGRAM_ID};
use env_logger;
use futures_util::StreamExt;
use std::env;
use std::sync::Arc;
use tokio;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    #[clap(long, action, help = "Use gRPC for event subscription")]
    grpc: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = env_logger::init();
    let _ = dotenv::dotenv().ok();
    let args = Args::parse();

    let mut event_subscriber = if args.grpc {
        let grpc_endpoint = env::var("GRPC_ENDPOINT")
            .expect("GRPC_ENDPOINT must be set");
        let grpc_x_token = env::var("GRPC_X_TOKEN")
            .expect("GRPC_X_TOKEN must be set");
        EventSubscriber::subscribe_grpc(
            grpc_endpoint, 
            grpc_x_token, 
            PROGRAM_ID
        )
            .await
            .expect("error subscribing to grpc events")
    } else {
        let ws_rpc_endpoint = env::var("WS_RPC_ENDPOINT")
            .expect("WS_RPC_ENDPOINT must be set");
        let client = PubsubClient::new(&ws_rpc_endpoint)
            .await
            .unwrap();
        EventSubscriber::subscribe(Arc::new(client), PROGRAM_ID)
            .await
            .expect("error subscribing to ws events")
    };

    println!("subscribed to events");
    let mut count = 0;
    
    while let Some(event) = event_subscriber.next().await {
        match event {
            DriftEvent::OrderFill {
                maker,
                maker_order_id,
                maker_side,
                taker,
                taker_order_id,
                taker_side,
                market_index,
                market_type,
                signature,
                bit_flags,
                ..
            } => {
                println!(
                    "order fill: market: {}-{} maker: {}-{}-{} taker: {}-{}-{} signature: {} bit_flags: {}",
                    market_type.as_str(),
                    market_index,
                    maker.unwrap_or_default().to_string().as_str(),
                    maker_order_id,
                    format!("{:?}", maker_side.unwrap_or_default()),
                    taker.unwrap_or_default().to_string().as_str(),
                    taker_order_id,
                    format!("{:?}", taker_side.unwrap_or_default()),
                    signature,
                    bit_flags
                );
                count += 1;
                if count > 100 {
                    break;
                }
            }
            _ => {}
        }
    }

    event_subscriber.unsubscribe();
    Ok(())
}

Event Types

The SDK supports multiple event types:

OrderFill

Fired when an order is matched and filled:
DriftEvent::OrderFill {
    maker,              // Maker's public key
    maker_order_id,     // Maker's order ID
    maker_side,         // Buy or Sell
    taker,              // Taker's public key
    taker_order_id,     // Taker's order ID
    taker_side,         // Buy or Sell
    base_asset_amount_filled,   // Amount filled
    quote_asset_amount_filled,  // Quote value
    market_index,       // Market identifier
    market_type,        // Perp or Spot
    oracle_price,       // Oracle price at fill
    signature,          // Transaction signature
    ts,                 // Timestamp
    ..
}

OrderCancel

Fired when an order is cancelled:
DriftEvent::OrderCancel {
    order_id,          // The cancelled order ID
    user,              // User public key
    signature,         // Transaction signature
    ..
}

OrderCreate

Fired when a new order is created:
DriftEvent::OrderCreate {
    order,             // Full order details
    user,              // User public key
    ts,                // Timestamp
    signature,         // Transaction signature
    ..
}

FundingPayment

Fired when funding is paid/received:
DriftEvent::FundingPayment {
    amount,            // Funding amount
    market_index,      // Market identifier
    user,              // User public key
    ts,                // Timestamp
    ..
}

Subscription Modes

WebSocket Mode

let ws_rpc_endpoint = "wss://api.mainnet-beta.solana.com";
let client = PubsubClient::new(&ws_rpc_endpoint).await?;
let mut subscriber = EventSubscriber::subscribe(
    Arc::new(client), 
    PROGRAM_ID
).await?;
Advantages:
  • Simple setup
  • No additional infrastructure
  • Works with standard RPC endpoints
Disadvantages:
  • Higher latency
  • May miss events during reconnections
  • Limited to single program

gRPC Mode

let grpc_endpoint = "your-grpc-endpoint";
let grpc_token = "your-token";
let mut subscriber = EventSubscriber::subscribe_grpc(
    grpc_endpoint,
    grpc_token,
    PROGRAM_ID
).await?;
Advantages:
  • Lower latency
  • More reliable
  • Better for high-frequency applications
Disadvantages:
  • Requires gRPC infrastructure
  • May require authentication

Processing Events

The subscriber implements Stream, so you can use standard async stream operations:
while let Some(event) = subscriber.next().await {
    match event {
        DriftEvent::OrderFill { .. } => {
            // Handle order fill
        },
        DriftEvent::OrderCancel { .. } => {
            // Handle order cancellation
        },
        _ => {
            // Ignore other events
        }
    }
}

Running the Example

WebSocket Mode

# Set environment variables
export WS_RPC_ENDPOINT="wss://api.mainnet-beta.solana.com"

# Run
cargo run --example event-subscriber

gRPC Mode

# Set environment variables
export GRPC_ENDPOINT="your-grpc-endpoint"
export GRPC_X_TOKEN="your-token"

# Run with --grpc flag
cargo run --example event-subscriber -- --grpc

Use Cases

Trading Bots

Monitor fills to track execution and update strategies:
DriftEvent::OrderFill { maker, taker, .. } => {
    if maker == my_pubkey {
        // My maker order was filled
        update_inventory();
        adjust_quotes();
    }
}

Analytics

Collect market data for analysis:
DriftEvent::OrderFill { 
    market_index,
    base_asset_amount_filled,
    quote_asset_amount_filled,
    ..
} => {
    let price = quote_asset_amount_filled / base_asset_amount_filled;
    record_trade(market_index, price, base_asset_amount_filled);
}

Monitoring

Track specific accounts or markets:
DriftEvent::OrderFill { market_index, .. } => {
    if market_index == 0 { // SOL-PERP
        alert_team("SOL-PERP fill detected");
    }
}

Best Practices

  1. Handle Disconnections: Implement reconnection logic
  2. Process Quickly: Don’t block the event loop with heavy processing
  3. Use Channels: Send events to separate processing tasks
  4. Filter Events: Only process events you need
  5. Log Errors: Don’t silently ignore processing errors

Example: Processing in Background

use tokio::sync::mpsc;

// Create channel for events
let (tx, mut rx) = mpsc::channel(1000);

// Spawn event processor
tokio::spawn(async move {
    while let Some(event) = rx.recv().await {
        // Process event in background
        process_event(event).await;
    }
});

// Stream events to channel
while let Some(event) = subscriber.next().await {
    tx.send(event).await.ok();
}

Next Steps

  • Filter events by specific accounts or markets
  • Implement reconnection logic for production
  • Build analytics dashboards from event data
  • Create alerting systems based on events

Build docs developers (and LLMs) love