Skip to main content
This example demonstrates how to use the DLOB’s L3 order retrieval methods and explains why providing accurate oracle prices is critical for correctly rendering orders.

What It Does

The DLOB matching example:
  • Builds a live DLOB from user accounts
  • Demonstrates the 4 main L3 order retrieval methods
  • Shows how oracle prices affect order rendering
  • Explains different order types (maker/taker, bid/ask)
  • Subscribes to live updates via gRPC

Why Oracle Price Matters

The oracle price is essential for rendering DLOB orders because:
  1. Floating Limit Orders: Prices defined as offsets from oracle
  2. Oracle Orders: Dynamically adjust based on current oracle price
  3. Trigger Orders: Evaluate whether to trigger based on oracle price
Example:
  • A floating limit order with oracle_offset = -100 will be priced at (oracle_price - 100)
  • If oracle is 50,000,theorderappearsat50,000, the order appears at 49,900
  • If you provide wrong oracle (51,000),orderappearsat51,000), order appears at 50,900 (WRONG!)

Complete Source Code

use drift_rs::{
    dlob::builder::DLOBBuilder,
    types::{MarketId, MarketType},
    Context, DriftClient, GrpcSubscribeOpts, RpcClient,
};
use solana_commitment_config::CommitmentLevel;
use solana_keypair::Keypair;

#[tokio::main]
async fn main() {
    let _ = dotenv::dotenv();
    let _ = env_logger::init();

    // Initialize drift client
    let rpc_url = std::env::var("RPC_URL")
        .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
    let drift = DriftClient::new(
        Context::MainNet,
        RpcClient::new(rpc_url),
        Keypair::new().into(),
    )
    .await
    .expect("initialized client");

    // Sync initial user accounts to populate DLOB
    let account_map = drift.backend().account_map();
    account_map
        .sync_user_accounts(vec![
            drift_rs::memcmp::get_user_with_order_filter()
        ])
        .await
        .expect("synced user accounts");

    // Build DLOB with initial state
    let dlob_builder = DLOBBuilder::new(account_map);
    let dlob = dlob_builder.dlob();

    // Start gRPC subscription for live updates
    let grpc_url = std::env::var("GRPC_URL")
        .expect("GRPC_URL must be set in .env");
    let grpc_x_token = std::env::var("GRPC_X_TOKEN")
        .expect("GRPC_X_TOKEN must be set in .env");

    let grpc_handle = tokio::spawn(async move {
        drift
            .grpc_subscribe(
                grpc_url,
                grpc_x_token,
                GrpcSubscribeOpts::default()
                    .commitment(CommitmentLevel::Processed)
                    .usermap_on()
                    .on_user_account(dlob_builder.account_update_handler(account_map))
                    .on_slot(dlob_builder.slot_update_handler()),
                true,
            )
            .await
    });

    // Wait for initial sync
    tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;

    // Fetch fresh oracle price - CRITICAL for accurate order pricing
    let market_index = 0u16;
    let market_type = MarketType::Perp;
    
    // This is the most up-to-date oracle price from gRPC
    // However, more advanced setups may query Pyth price feeds directly
    let oracle_data = drift
        .try_get_oracle_price_data_and_slot(MarketId::perp(market_index))
        .expect("oracle data exists");
    let oracle_price = oracle_data.data.price as u64;

    println!(
        "Oracle Price: ${}.{:08}", 
        oracle_price / 100_000_000, 
        oracle_price % 100_000_000
    );
    println!("Slot: {}", oracle_data.slot);
    println!();

    let perp_market = drift
        .backend()
        .perp_market(market_index)
        .map(|m| m.clone());
    
    // This is typically the oracle_price but can also be 
    // a median price based on funding
    let trigger_price = oracle_price;

    // Demonstrate the 4 L3 methods
    
    // 1. Maker bids - resting limit buy orders
    let maker_bids = dlob.get_maker_bids_l3(
        market_index, 
        market_type, 
        oracle_price
    );
    println!("Maker Bids (top 5):");
    for (i, order) in maker_bids.iter().take(5).enumerate() {
        let price_usd = (order.price as f64) / 100_000_000.0;
        println!(
            "  {}. ${:.2} @ {} (user: {}, kind: {:?})",
            i + 1, 
            price_usd, 
            order.size, 
            &order.user.to_string()[..8], 
            order.kind
        );
    }

    // 2. Maker asks - resting limit sell orders
    let maker_asks = dlob.get_maker_asks_l3(
        market_index, 
        market_type, 
        oracle_price
    );
    println!("\nMaker Asks (top 5):");
    for (i, order) in maker_asks.iter().take(5).enumerate() {
        let price_usd = (order.price as f64) / 100_000_000.0;
        println!(
            "  {}. ${:.2} @ {} (user: {}, kind: {:?})",
            i + 1, 
            price_usd, 
            order.size, 
            &order.user.to_string()[..8], 
            order.kind
        );
    }

    // 3. Taker bids - aggressive market buy orders
    let taker_bids = dlob.get_taker_bids_l3(
        market_index,
        market_type,
        oracle_price,
        trigger_price,
        perp_market.as_ref(),
    );
    println!("\nTaker Bids (top 5):");
    for (i, order) in taker_bids.iter().take(5).enumerate() {
        let price_usd = (order.price as f64) / 100_000_000.0;
        println!(
            "  {}. ${:.2} @ {} (user: {}, kind: {:?})",
            i + 1, 
            price_usd, 
            order.size, 
            &order.user.to_string()[..8], 
            order.kind
        );
    }

    // 4. Taker asks - aggressive market sell orders
    let taker_asks = dlob.get_taker_asks_l3(
        market_index,
        market_type,
        oracle_price,
        trigger_price,
        perp_market.as_ref(),
    );
    println!("\nTaker Asks (top 5):");
    for (i, order) in taker_asks.iter().take(5).enumerate() {
        let price_usd = (order.price as f64) / 100_000_000.0;
        println!(
            "  {}. ${:.2} @ {} (user: {}, kind: {:?})",
            i + 1, 
            price_usd, 
            order.size, 
            &order.user.to_string()[..8], 
            order.kind
        );
    }

    println!(
        "\nTotal: {} maker bids, {} maker asks, {} taker bids, {} taker asks",
        maker_bids.len(), 
        maker_asks.len(), 
        taker_bids.len(), 
        taker_asks.len()
    );

    println!("\nKeeping gRPC subscription running. Press Ctrl+C to exit...");
    let _ = grpc_handle.await;
}

The 4 L3 Methods

1. Maker Bids

Resting limit buy orders:
let maker_bids = dlob.get_maker_bids_l3(
    market_index,
    market_type,
    oracle_price,
);
  • Orders waiting to buy
  • Add liquidity to the book
  • Earn maker fees when filled
  • Sorted by price (highest first)

2. Maker Asks

Resting limit sell orders:
let maker_asks = dlob.get_maker_asks_l3(
    market_index,
    market_type,
    oracle_price,
);
  • Orders waiting to sell
  • Add liquidity to the book
  • Earn maker fees when filled
  • Sorted by price (lowest first)

3. Taker Bids

Aggressive market buy orders:
let taker_bids = dlob.get_taker_bids_l3(
    market_index,
    market_type,
    oracle_price,
    trigger_price,
    perp_market.as_ref(),
);
  • Market orders to buy
  • Remove liquidity from book
  • Pay taker fees
  • Includes trigger orders that would execute

4. Taker Asks

Aggressive market sell orders:
let taker_asks = dlob.get_taker_asks_l3(
    market_index,
    market_type,
    oracle_price,
    trigger_price,
    perp_market.as_ref(),
);
  • Market orders to sell
  • Remove liquidity from book
  • Pay taker fees
  • Includes trigger orders that would execute

Oracle Price Importance

Getting Fresh Oracle Prices

// From gRPC subscription (recommended)
let oracle_data = drift
    .try_get_oracle_price_data_and_slot(MarketId::perp(market_index))
    .expect("oracle data exists");
let oracle_price = oracle_data.data.price as u64;
Why fresh prices matter:
  • Floating limit orders: price = oracle_price + offset
  • Oracle orders: dynamically adjust with oracle price
  • Trigger orders: evaluate based on oracle price
  • Without fresh prices: Stale/incorrect order prices and missed opportunities

Advanced: Direct Pyth Queries

For even fresher prices:
// Query Pyth directly
let pyth_price = query_pyth_price_feed(pyth_feed_address).await?;
let oracle_price = pyth_price.price;

Order Types in L3 Data

L3Order Structure

pub struct L3Order {
    pub price: u64,          // Computed price
    pub size: u64,           // Order size
    pub user: Pubkey,        // User account
    pub order_id: u32,       // Order ID
    pub max_ts: u64,         // Expiry timestamp
    pub kind: OrderKind,     // Order type
    // ...
}

Order Kinds

  • Limit: Standard limit order
  • Market: Market order
  • Oracle: Oracle-based pricing
  • TriggerMarket: Trigger at market
  • TriggerLimit: Trigger then limit
  • And more…

Building Matching Engines

Use L3 methods to build matching engines:
// Get opposing orders
let maker_asks = dlob.get_maker_asks_l3(market_index, market_type, oracle_price);
let taker_bids = dlob.get_taker_bids_l3(
    market_index, 
    market_type, 
    oracle_price,
    trigger_price,
    perp_market.as_ref()
);

// Match taker bids with maker asks
for taker_bid in taker_bids {
    for maker_ask in &maker_asks {
        if taker_bid.price >= maker_ask.price {
            // Match found!
            execute_fill(taker_bid, maker_ask).await?;
        }
    }
}

Trigger Price vs Oracle Price

let trigger_price = perp_market
    .get_trigger_price(oracle_price as i64, unix_now, true)
    .expect("got trigger price");
Oracle Price:
  • Direct from price feed
  • Used for most order calculations
Trigger Price:
  • Can be median of oracle and mark price
  • Used for evaluating trigger orders
  • Prevents manipulation

Running the Example

# Set environment variables
export RPC_URL="https://api.mainnet-beta.solana.com"
export GRPC_URL="your-grpc-endpoint"
export GRPC_X_TOKEN="your-token"

# Run
cargo run --example dlob-matching

Example Output

Oracle Price: $101.23456789
Slot: 234567890

Maker Bids (top 5):
  1. $101.00 @ 5000000000 (user: 7Xr2Kp9..., kind: Limit)
  2. $100.95 @ 3000000000 (user: 5Kp1Jt4..., kind: Limit)
  3. $100.90 @ 2000000000 (user: 2Np9Qm2..., kind: Limit)
  ...

Maker Asks (top 5):
  1. $101.50 @ 4000000000 (user: 9Yx3Zw8..., kind: Limit)
  2. $101.55 @ 6000000000 (user: 3Zw8Rk7..., kind: Limit)
  ...

Total: 150 maker bids, 180 maker asks, 5 taker bids, 8 taker asks

Use Cases

1. Matching Engine

Build a custom matching engine:
let takers = dlob.get_taker_bids_l3(...);
let makers = dlob.get_maker_asks_l3(...);

for (taker, maker) in match_orders(takers, makers) {
    fill_order(taker, maker).await?;
}

2. Market Making

Find best prices to quote:
let best_bid = dlob.get_maker_bids_l3(...).first();
let best_ask = dlob.get_maker_asks_l3(...).first();

let my_bid_price = best_bid.price - tick_size;
let my_ask_price = best_ask.price + tick_size;

3. Analytics

Analyze order book depth:
let total_bid_liquidity: u64 = dlob
    .get_maker_bids_l3(...)
    .iter()
    .map(|o| o.size)
    .sum();

let total_ask_liquidity: u64 = dlob
    .get_maker_asks_l3(...)
    .iter()
    .map(|o| o.size)
    .sum();

let imbalance = total_bid_liquidity as f64 / total_ask_liquidity as f64;

4. Order Routing

Route orders to best liquidity:
let available_makers = dlob.get_maker_asks_l3(...);
let best_execution = find_best_route(order_size, &available_makers);

Best Practices

  1. Always Use Fresh Oracle Prices: Stale prices = incorrect order rendering
  2. Handle Empty Results: Not all markets have active orders
  3. Update on Slot Changes: Refresh oracle prices regularly
  4. Filter by Market: Query specific markets you need
  5. Consider Trigger Price: Use correct price for trigger orders

Performance Tips

  1. Cache DLOB Instance: Reuse the same DLOB, don’t recreate
  2. Filter Markets: Only sync/query markets you need
  3. Use gRPC: Faster and more reliable than WebSocket
  4. Batch Updates: Process multiple account updates together

Next Steps

  • Build a matching engine using L3 data
  • Implement order routing algorithms
  • Create market depth visualizations
  • Add order book analytics

Build docs developers (and LLMs) love