Skip to main content

Overview

The DLOBBuilder provides a convenient way to construct and manage an event-driven DLOB instance. It handles:
  • Initializing the DLOB from existing user accounts
  • Creating update handlers for gRPC subscriptions
  • Managing the notifier channel for incremental updates
  • Bootstrapping orderbook state

Structure

pub struct DLOBBuilder<'a> {
    dlob: &'a DLOB,
    notifier: DLOBNotifier,
}
The builder maintains a reference to the underlying DLOB and a notifier for sending updates.

Construction

From AccountMap

The most common way to initialize a DLOB is from an existing AccountMap:
use drift_rs::dlob::builder::DLOBBuilder;

// Initialize from account map
let builder = DLOBBuilder::new(&account_map);

// Access the underlying DLOB
let dlob = builder.dlob();
This constructor:
  1. Creates a new DLOB instance
  2. Spawns the notifier channel
  3. Iterates through all User accounts in the AccountMap
  4. Inserts all open orders into the DLOB

From User Iterator

Alternatively, you can initialize from an iterator of User accounts:
let users: Vec<User> = /* fetch users */;
let slot = /* current slot */;

let builder = DLOBBuilder::new_with_users(
    users.iter(),
    slot
);
This is useful when you have a custom source of user accounts or want more control over initialization.

gRPC Integration

The builder provides handlers that integrate seamlessly with Drift’s gRPC subscription system:

Complete Example

use drift_rs::{
    dlob::builder::DLOBBuilder,
    types::MarketId,
    MarketType,
};

// Initialize builder
let builder = DLOBBuilder::new(&account_map);

// Define markets to track
let markets = vec![
    MarketId::new(0, MarketType::Perp),
    MarketId::new(1, MarketType::Perp),
];

// Setup gRPC subscription
let _res = drift
    .grpc_subscribe(
        grpc_url,
        grpc_x_token,
        GrpcSubscribeOpts::default()
            .commitment(CommitmentLevel::Processed)
            .usermap_on()
            // User account update handler
            .on_user_account(
                builder.account_update_handler(&account_map)
            )
            // Slot update handler
            .on_slot(
                builder.slot_update_handler(drift.clone(), markets)
            ),
        true, // sync all accounts on startup
    )
    .await;

// Access the DLOB
let dlob = builder.dlob();
let l3_book = dlob.get_l3_snapshot(0, MarketType::Perp);

Methods

new

new
fn
Initialize a new DLOBBuilder from an AccountMapParameters:
  • account_map: &AccountMap - Account map with initial User accounts to bootstrap orderbook
Returns: DLOBBuilder<'a>This method:
  • Creates a new DLOB instance (leaked for ‘static lifetime)
  • Spawns the notifier channel
  • Iterates through all User accounts
  • Inserts all open orders into the DLOB

new_with_users

new_with_users
fn
Initialize a new DLOBBuilder from an iterator of User accountsParameters:
  • users: impl Iterator<Item = &'u User> - Iterator of User accounts
  • slot: u64 - Slot when users were retrieved
Returns: DLOBBuilder<'a>Useful for custom user account sources or when you want more control over initialization.

dlob

dlob
fn
Get a reference to the underlying DLOB instanceReturns: &'a DLOBUse this to access all DLOB methods like get_l3_snapshot, find_crosses_for_taker_order, etc.

account_update_handler

account_update_handler
fn
Create a handler for gRPC user account updatesParameters:
  • account_map: &'b AccountMap - Account map for comparing old/new states
Returns: impl Fn(&AccountUpdate) + Send + Sync + 'bThis handler:
  • Deserializes account updates from gRPC
  • Compares old and new user states
  • Generates order deltas (creates/removes)
  • Sends deltas to the DLOB notifier
Example:
GrpcSubscribeOpts::default()
    .on_user_account(
        builder.account_update_handler(&account_map)
    )

slot_update_handler

slot_update_handler
fn
Create a handler for gRPC slot updatesParameters:
  • drift: DriftClient - Drift client for fetching oracle prices
  • markets: Vec<MarketId> - Markets to update on each slot
Returns: impl Fn(u64) + Send + Sync + 'staticThis handler:
  • Receives new slot numbers from gRPC
  • Fetches current oracle prices for specified markets
  • Sends slot and oracle updates to the DLOB notifier
  • Triggers orderbook updates (expiring auctions, updating snapshots)
Example:
let markets = vec![
    MarketId::new(0, MarketType::Perp),
    MarketId::new(1, MarketType::Perp),
];

GrpcSubscribeOpts::default()
    .on_slot(
        builder.slot_update_handler(drift.clone(), markets)
    )

load_user

load_user
fn
Manually load an individual user account to the DLOBParameters:
  • pubkey: Pubkey - User account public key
  • user: &User - User account data
  • slot: u64 - Current slot
Useful for:
  • Adding users outside of the normal gRPC flow
  • Testing and simulation
  • Custom initialization scenarios
Example:
builder.load_user(user_pubkey, &user_account, current_slot);

Implementation Details

User Account Updates

The account update handler uses the user_update method of DLOBNotifier:
pub fn user_update(
    &self,
    pubkey: Pubkey,
    old_user: Option<&User>,
    new_user: &User,
    slot: u64,
)
This method:
  1. Compares old and new user states (if old exists)
  2. Generates OrderDelta events for changes:
    • Create - New order or order became open
    • Remove - Order cancelled, filled, or changed
  3. Sends deltas to the DLOB event channel

Slot and Oracle Updates

The slot update handler sends SlotAndOracleUpdate events:
pub fn slot_and_oracle_update(
    &self,
    market: MarketId,
    slot: u64,
    oracle_price: u64,
)
This triggers the DLOB to:
  1. Update the orderbook to the new slot
  2. Expire completed auctions
  3. Move auction orders to resting orders if applicable
  4. Update L2/L3 snapshots with new oracle price

DLOB Lifecycle

The DLOB instance is leaked (given a 'static lifetime) because:
  1. It needs to live for the entire program duration
  2. Multiple threads need concurrent access
  3. The notifier thread holds a reference to it
let dlob = Box::leak(Box::new(DLOB::default()));

Error Handling

The builder’s handlers will panic if:
  • The DLOB event channel is closed (indicates DLOB thread dropped)
  • Oracle price cannot be fetched (in slot_update_handler)
These are considered fatal errors that should terminate the program.

Performance Considerations

Initialization

  • Bootstrap time scales with number of user accounts and orders
  • Consider using filters if you only need specific markets
  • Large orderbooks (1000s of orders) initialize in milliseconds

Update Throughput

  • Channel is bounded at 2048 events
  • High update rates may block if DLOB processing falls behind
  • Consider increasing channel size for high-frequency scenarios

Memory Usage

  • Each order consumes ~200 bytes in the DLOB
  • L3 snapshots double memory per orderbook
  • Metadata and event tracking add ~100 bytes per order (if enabled)

Common Patterns

Filtered Markets

Only track specific markets to reduce memory and processing:
let markets = vec![
    MarketId::new(0, MarketType::Perp),  // SOL-PERP
    MarketId::new(1, MarketType::Perp),  // BTC-PERP
];

let builder = DLOBBuilder::new(&account_map);
// Only oracle updates for specified markets

Custom User Loading

Load users from custom sources:
let builder = DLOBBuilder::new_with_users(
    users.iter().filter(|u| u.is_active()),
    current_slot
);

Manual Updates

Send updates manually without gRPC:
let builder = DLOBBuilder::new(&account_map);

// Manual user update
builder.load_user(pubkey, &user, slot);

// Access notifier for custom events
let notifier = /* get from builder internals */;
notifier.slot_and_oracle_update(market, slot, oracle_price);

Testing Example

#[test]
fn test_dlob_builder() {
    let account_map = AccountMap::new(/* ... */);
    let builder = DLOBBuilder::new(&account_map);
    
    // Load test user
    let user = create_test_user_with_orders();
    builder.load_user(test_pubkey, &user, 100);
    
    // Verify orders in DLOB
    let dlob = builder.dlob();
    let snapshot = dlob.get_l3_snapshot(0, MarketType::Perp);
    
    assert!(snapshot.bids(None, None, None).next().is_some());
}

See Also

  • DLOB Overview - Core DLOB concepts and usage
  • Order Matching - Order matching types and algorithms
  • DLOBNotifier - Low-level update channel (in mod.rs:220-294)

Build docs developers (and LLMs) love