Skip to main content

Overview

The DLOB provides sophisticated order matching capabilities that handle:
  • Auction orders crossing resting limit orders
  • Limit order crosses at the top of the book
  • vAMM crosses with the protocol’s virtual AMM
  • Trigger order matching with post-trigger price calculations
  • Partial fills with remaining order tracking

Core Matching Types

TakerOrder

Minimal taker order information for matching:
pub struct TakerOrder {
    pub price: u64,
    pub size: u64,
    pub market_index: u16,
    pub direction: Direction,
    pub market_type: MarketType,
}
Construction from OrderParams:
let taker = TakerOrder::from_order_params(order_params, price);
Fields:
  • price - Limit price of the taker order (in market quote precision)
  • size - Base asset amount to fill (in market base precision)
  • market_index - Index of the market (e.g., 0 for SOL-PERP)
  • direction - Long (buy) or Short (sell)
  • market_type - Perp or Spot

MakerCrosses

Result of matching a taker order against resting maker orders:
pub struct MakerCrosses {
    pub orders: ArrayVec<(L3Order, u64), 16>,  // (maker order, fill_size)
    pub slot: u64,                               // Slot when crosses were found
    pub has_vamm_cross: bool,                    // True if order crosses vAMM
    pub is_partial: bool,                        // True if taker not fully filled
    pub taker_direction: Direction,              // Direction of taker order
}
Methods:
is_empty
fn
Returns true if no crosses were found (neither maker orders nor vAMM)
if maker_crosses.is_empty() {
    println!("No fills available");
}
Fields:
  • orders - Up to 16 matched maker orders with their fill sizes
  • has_vamm_cross - Indicates if the order can fill against vAMM
  • is_partial - True if taker order cannot be fully filled
  • taker_direction - Direction to determine bid/ask side

CrossesAndTopMakers

Comprehensive result of finding all auction crosses at current slot:
pub struct CrossesAndTopMakers {
    pub top_maker_asks: ArrayVec<Pubkey, 3>,           // Best 3 ask makers
    pub top_maker_bids: ArrayVec<Pubkey, 3>,           // Best 3 bid makers  
    pub limit_crosses: Option<(L3Order, L3Order)>,     // Limit cross if any
    pub vamm_taker_ask: Option<L3Order>,               // Maker ask crossing vAMM
    pub vamm_taker_bid: Option<L3Order>,               // Maker bid crossing vAMM
    pub crosses: Vec<(L3Order, MakerCrosses)>,         // Taker auctions and makers
}
Use Cases:
  • Finding all fillable auction orders at once
  • Identifying top maker accounts for rewards
  • Detecting limit crosses for atomic matching
  • Finding vAMM arbitrage opportunities

CrossingRegion

Region where the orderbook is crossed (best bid >= best ask):
pub struct CrossingRegion {
    pub slot: u64,
    pub crossing_bids: Vec<L3Order>,
    pub crossing_asks: Vec<L3Order>,
}
A crossing region indicates market inefficiency or pending atomic matches.

Matching Methods

find_crosses_for_taker_order

Find all maker orders that would match against a given taker order:
pub fn find_crosses_for_taker_order(
    &self,
    current_slot: u64,
    oracle_price: u64,
    taker_order: TakerOrder,
    perp_market: Option<&PerpMarket>,
    depth: Option<usize>,
) -> MakerCrosses
current_slot
u64
Current blockchain slot for time-sensitive order logic (auctions, expiry)
oracle_price
u64
Current oracle price for:
  • Floating order price calculations
  • Trigger condition evaluation
  • vAMM fallback price
taker_order
TakerOrder
The taker order to match against the book
perp_market
Option<&PerpMarket>
PerpMarket struct for:
  • vAMM price calculations
  • Fallback price for auctions
  • Trigger price determination
Use None for spot markets or when only interested in maker orders
depth
Option<usize>
Maximum order depth to consider (default: 32)Higher values find more matches but increase computation
Returns: MakerCrosses containing matched orders Example:
let taker = TakerOrder {
    price: 50_000_000_000,  // $50k limit
    size: 1_000_000_000,    // 1.0 SOL
    market_index: 0,
    direction: Direction::Long,
    market_type: MarketType::Perp,
};

let crosses = dlob.find_crosses_for_taker_order(
    current_slot,
    oracle_price,
    taker,
    Some(&perp_market),
    Some(50),  // Check top 50 orders
);

if crosses.is_empty() {
    println!("No fills available");
} else if crosses.is_partial {
    println!("Partial fill: {} makers, vAMM: {}", 
        crosses.orders.len(), 
        crosses.has_vamm_cross
    );
} else {
    println!("Full fill possible with {} makers", crosses.orders.len());
}

for (maker, fill_size) in crosses.orders {
    println!("Fill {} @ {} from user {}", 
        fill_size, maker.price, maker.user
    );
}

find_crosses_for_auctions

Find all auction orders crossing resting limit orders at current slot:
pub fn find_crosses_for_auctions(
    &self,
    market_index: u16,
    market_type: MarketType,
    slot: u64,
    oracle_price: u64,
    perp_market: Option<&PerpMarket>,
    trigger_price: u64,
    depth: Option<usize>,
) -> CrossesAndTopMakers
market_index
u16
Market index to check for crosses
market_type
MarketType
Perp or Spot market type
slot
u64
Current slot for auction price calculations
oracle_price
u64
Oracle price for floating order calculations
perp_market
Option<&PerpMarket>
Market data for vAMM calculations (required for perp markets)
trigger_price
u64
Price for evaluating trigger conditions and post-trigger prices
depth
Option<usize>
Max order depth (default: 64)
Returns: CrossesAndTopMakers with all crosses and top maker info Example:
let result = dlob.find_crosses_for_auctions(
    0,  // SOL-PERP
    MarketType::Perp,
    current_slot,
    oracle_price,
    Some(&perp_market),
    oracle_price,  // Use oracle as trigger price
    Some(100),
);

// Check for limit crosses
if let Some((taker, maker)) = result.limit_crosses {
    println!("Limit cross: {} crossing {}", taker.user, maker.user);
}

// Check for vAMM crosses
if let Some(ask) = result.vamm_taker_ask {
    println!("Bid {} can fill against vAMM", ask.user);
}

// Process all auction crosses
for (taker, makers) in result.crosses {
    println!("Taker auction {} @ {} crossing {} makers",
        taker.order_id,
        taker.price,
        makers.orders.len()
    );
}

// Top makers for rewards
println!("Top bid makers: {:?}", result.top_maker_bids);
println!("Top ask makers: {:?}", result.top_maker_asks);

find_crossing_region

Find the region where orderbook is crossed:
pub fn find_crossing_region(
    &self,
    oracle_price: u64,
    market_index: u16,
    market_type: MarketType,
    perp_market: Option<&PerpMarket>,
) -> Option<CrossingRegion>
Returns: Some(CrossingRegion) if best bid >= best ask, None otherwise Example:
if let Some(region) = dlob.find_crossing_region(
    oracle_price,
    0,
    MarketType::Perp,
    Some(&perp_market)
) {
    println!("Book is crossed!");
    println!("Crossing bids: {}", region.crossing_bids.len());
    println!("Crossing asks: {}", region.crossing_asks.len());
    
    // Atomic match opportunity
    for bid in region.crossing_bids {
        println!("Bid {} @ {}", bid.size, bid.price);
    }
}

Matching Algorithm

Order Priority

Orders are matched in price-time priority:
  1. Best price first - Highest bids, lowest asks
  2. Time priority - Earlier orders (lower max_ts) at same price
  3. Order ID - Lower order IDs for deterministic ordering

Price Crossing Logic

For long takers (buyers):
taker_price > maker_ask_price  // Can cross
For short takers (sellers):
taker_price < maker_bid_price  // Can cross

Partial Fill Handling

The algorithm tracks remaining size:
let mut remaining_size = taker_size;

while let Some(maker) = makers.next() {
    let fill_size = remaining_size.min(maker.size);
    matches.push((maker, fill_size));
    remaining_size -= fill_size;
    
    if remaining_size == 0 {
        break;  // Fully filled
    }
}

let is_partial = remaining_size > 0;

Post-Only Order Matching

Post-only orders follow special crossing rules:
match (bid.is_post_only(), ask.is_post_only()) {
    (true, false) => Some((ask, bid)),   // Ask is taker
    (false, true) => Some((bid, ask)),   // Bid is taker
    (false, false) => {                   // Both can be taker
        if bid.max_ts < ask.max_ts {      // Older order is maker
            Some((bid, ask))
        } else {
            Some((ask, bid))
        }
    }
    (true, true) => None,                 // Both post-only, no cross
}

vAMM Cross Detection

For long takers:
has_vamm_cross = taker_size > vamm_min_order_size 
    && taker_price > vamm_ask_price
For short takers:
has_vamm_cross = taker_size > vamm_min_order_size 
    && taker_price < vamm_bid_price

Advanced Matching Scenarios

Auction Order Matching

Auction orders have dynamic prices that change with each slot:
// Market auction at slot N
let auction_price = order.get_price(current_slot, oracle_price, tick_size);

// Auction completes after duration
let is_complete = Order::is_auction_complete(
    current_slot,
    order.slot,
    order.auction_duration
);

if is_complete {
    // Convert to resting limit order
    let limit_order = auction.to_limit_order();
}

Trigger Order Matching

Trigger orders require evaluation at trigger price:
// Check if order would trigger
let would_trigger = match order.trigger_condition {
    OrderTriggerCondition::Above => trigger_price > order.trigger_price,
    OrderTriggerCondition::Below => trigger_price < order.trigger_price,
    _ => false,
};

if would_trigger {
    // Calculate post-trigger price
    let post_price = order.post_trigger_price(
        current_slot,
        oracle_price,
        &perp_market
    );
    
    // Match at post-trigger price
    find_crosses_for_price(post_price, ...);
}

Floating Order Matching

Floating orders have prices relative to oracle:
let floating_price = (oracle_price as i64 + order.offset_price as i64) as u64;
let standardized = standardize_price(floating_price, tick_size, direction);

Performance Considerations

Match Limits

  • Maximum 16 maker orders per taker (ArrayVec capacity)
  • Configurable depth parameter (default 32-64)
  • Early termination when taker fully filled

Time Complexity

  • Best case: O(1) - No crosses found immediately
  • Average case: O(k) - Where k is number of crossing orders
  • Worst case: O(depth) - Full depth search

Memory Usage

  • Stack-allocated for up to 16 matches (no heap allocation)
  • Efficient iterator-based approach
  • Peekable iterators avoid unnecessary advances

Common Patterns

Check Fillability

let crosses = dlob.find_crosses_for_taker_order(...);

if crosses.is_empty() {
    return Err("No liquidity available");
}

if crosses.is_partial {
    println!("Warning: Only partial fill available");
}

Calculate Expected Fill Price

let mut total_quote = 0u64;
let mut total_base = 0u64;

for (maker, fill_size) in crosses.orders {
    total_quote += maker.price * fill_size;
    total_base += fill_size;
}

let avg_price = total_quote / total_base;
println!("Average fill price: {}", avg_price);

Find Best Maker

let result = dlob.find_crosses_for_auctions(...);

if let Some(best_maker) = result.top_maker_bids.first() {
    println!("Best bid maker: {}", best_maker);
}

Multi-Market Matching

for market_id in markets {
    let crosses = dlob.find_crosses_for_auctions(
        market_id.index(),
        market_id.kind(),
        slot,
        oracle_prices[&market_id],
        perp_markets.get(&market_id),
        trigger_price,
        None,
    );
    
    process_crosses(market_id, crosses);
}

Testing Example

#[test]
fn test_taker_maker_matching() {
    let dlob = create_test_dlob();
    
    // Add maker orders
    dlob.insert_order(&maker1, slot, limit_ask_49_500);
    dlob.insert_order(&maker2, slot, limit_ask_50_000);
    
    // Create taker
    let taker = TakerOrder {
        price: 50_000,
        size: 2_000,
        direction: Direction::Long,
        market_index: 0,
        market_type: MarketType::Perp,
    };
    
    // Find crosses
    let crosses = dlob.find_crosses_for_taker_order(
        slot,
        oracle_price,
        taker,
        None,
        None,
    );
    
    assert_eq!(crosses.orders.len(), 2);
    assert!(!crosses.is_partial);
    assert_eq!(crosses.orders[0].0.price, 49_500);  // Best price first
}

See Also

  • DLOB Overview - Core DLOB concepts
  • DLOB Builder - Builder pattern for DLOB
  • L3Order - Order-level detail (in types.rs:672-760)
  • OrderKind - Order type classification (in types.rs:26-52)

Build docs developers (and LLMs) love