CLOB Design Spec

Design Goals

  1. 02Crankless: every match, book update, and asset settlement happens in a single atomic Move transaction, no separate crank, no async queue, no off-chain matching
  2. 04Privacy-layered: resting orders are public entries on BigOrderedMap (price + time priority, fully visible onchain). Pre-execution privacy comes from the encrypted mempool (hiding orders before they hit the book). Post-settlement privacy uses CA to hide transfer amounts and UTT to hide wallet identity on transfers
  3. 06HFT-viable: market makers can update their full order ladder in one transaction using QuoteIntent, and per-user seat tracking makes cancel-all O(user orders), not O(book size)
  4. 08Formally verifiable: the matching engine logic is designed to be proven correct using CLRS-spec-to-Lean verification pipeline (Alex's existing methodology from Dropset)
  5. 10MEV-resistant: encrypted mempool (pending governance) prevents front-running via mempool observation, and crankless design eliminates the sandwich attack vector present in Decibel perps

Data Structures

BigOrderedMap (AIP-120)

The price-level index for the orderbook. A B+ tree replacing Econia's original AVL tree.

Why the change matters:

  • AVL rebalance: O(log n) with unpredictable gas (depends on current tree shape)
  • B+ tree leaf split: O(1) flat gas regardless of tree state
  • 57% bytecode reduction over the Econia implementation

The flat gas property is the prerequisite for HFT market-making viability. A market maker submitting thousands of quotes per day cannot operate on a system where each quote has variable gas cost. The O(1) B+ tree makes per-quote gas cost predictable and budgetable.

Order Representation

Each resting order is a public entry in the BigOrderedMap B+ tree, keyed by price level and time priority:

OrderEntry {
    price: u64,          // visible. The limit price
    quantity: u64,       // visible. The order size
    maker_address: address, // visible. The maker's onchain address
    timestamp: u64,      // visible. Insertion time for priority
}

The orderbook is fully transparent. Any observer can see all resting orders, their sizes, and their price levels. This is the standard CLOB design. Privacy protections operate at other layers: the encrypted mempool hides orders before they reach the book (preventing front-running), and settlement proceeds can use CA (hidden amounts) or UTT (hidden identity and amounts) for post-execution transfer privacy.

Seat Structure

Each market participant has a seat. A per-user data structure that tracks their active orders without reference to the shared book state.

struct Seat has key {
    // User's active order identifiers
    // Keyed by price level + time priority
    active_orders: Table<OrderKey, OrderRef>,
    order_count: u64,
}

Why the seat matters: Standard cancel-all on most CLOBs requires iterating the entire book to find a user's orders. O(book size). With the seat, cancel-all is O(n) where n = user's orders. For a market maker with 20 active quotes, cancel-all is 20 operations, not 10,000.

QuoteIntent: the Market-maker Primitive

The key design choice that separates the Whop spot CLOB from Decibel's current bulk orders implementation.

The Problem QuoteIntent Solves

When a market maker updates their order ladder, they write to the shared BigOrderedMap. Under Block-STM optimistic execution, multiple makers writing to the same market in the same block will have conflicts resolved by re-execution, but the re-execution frequency is proportional to how often writes contend. Decibel's bulk orders write directly to the shared B+tree on every update, maximizing write contention.

QuoteIntent separates the quote update into two steps that reduce shared-state contention:

Step 1: Submit-intent

public entry fun submit_intent(
    maker: &signer,
    // The maker's desired new order state
    // Written ONLY to the maker's own address
    // No BigOrderedMap writes
    new_quotes: vector<QuoteSpec>,
    nonce: u64,
) {
    let seat = borrow_global_mut<Seat>(signer::address_of(maker));
    seat.pending_intent = PendingIntent { new_quotes, nonce };
    // No book state modified. Only seat state.
}

What this does: Writes the maker's desired new ladder to their own address only. Zero writes to the shared BigOrderedMap. Zero Block-STM conflicts with other makers.

Step 2: Execute-own-intent

public entry fun execute_own_intent(
    maker: &signer,
) {
    let seat = borrow_global_mut<Seat>(signer::address_of(maker));
    let intent = seat.pending_intent;
    
    // 1. Cancel all existing quotes atomically
    // Removes existing orders from BigOrderedMap, returns collateral
    for each existing_order in seat.active_orders:
        cancel_order_atomic(existing_order);
    
    // 2. Place new quotes
    // Inserts new orders into BigOrderedMap at specified price levels
    for each new_quote in intent.new_quotes:
        place_order_atomic(new_quote);
    
    // 3. Update seat
    seat.active_orders = new_orders;
    seat.pending_intent = None;
}

What this does: The single point of shared state contention. But it's atomic. One operation that cancels the old ladder and places the new one. Competitors cannot front-run between cancel and replace (no race condition). The BigOrderedMap write is a single B+ tree operation: O(1) flat gas.

QuoteIntent vs Decibel Bulk Orders

PropertyQuoteIntentDecibel Bulk Orders
Shared state writes on update1 (execute-own-intent only)N (one per order in batch)
Block-STM conflict surfaceMinimalHigher under many makers
Cancel-replace race conditionImpossible (atomic)Protected by sequence numbers
RuntimeAptos MoveAptos Move
Gas cost per updateO(1) flatO(n) orders in batch

Crankless Atomic Taker Sweep

When a taker order crosses the spread and matches resting maker orders, the entire settlement, including coin transfer, book update, and asset routing, happens in one Move transaction.

public entry fun sweep(
    taker: &signer,
    // The taker's collateral (pulled from wallet)
    taker_quantity: u64,
    // Maximum price willing to pay (taker limit) 
    // or zero for market order
    price_limit: u64,
    // Gas paid publicly
    gas_value: u64,
) {
    // 1. Pull taker's collateral from wallet
    let taker_coins = coin::withdraw(taker, taker_quantity);
    
    // 2. Sweep BigOrderedMap from best price inward
    let matched_orders = [];
    let remaining_quantity = taker_quantity;
    
    loop {
        let best = BigOrderedMap::get_best_price(market);
        if best crosses price_limit: break;
        let maker_order = BigOrderedMap::pop_best(market);
        matched_orders.push(maker_order);
        remaining_quantity -= maker_order.quantity;
        if remaining_quantity <= 0: break;
    }
    
    // 3. Settle all matches atomically
    // Route assets to all matched parties:
    //   - Each maker receives their proceeds (base or quote)
    //   - Taker receives their proceeds
    // Settlement can optionally use CA (hidden amounts)
    // or UTT (hidden identity + amounts) for transfer privacy
    for each match in matched_orders:
        settle_match(match.maker_address, match.value);
    settle_match(taker_address, remaining_proceeds);
    
    // 4. Chart replay trigger emitted
    // Shelby service listens for this event
    event::emit(TradeExecutedEvent { ... });
    
    // 5. ContentRewards attribution
    // Indicator/signal attribution recorded
    contentrewards::record_trade_attribution(...);
}

Atomic crankless. The match, book update, and asset routing are one atomic operation. No async queue, no separate settlement step. Econia's original design promise, now on Block-STM v2 with optional CA/UTT settlement privacy.

The Permissionless Crank Problem (why Decibel Perps Can't Do This)

Decibel perps require a crank because perpetuals have ongoing state that must be continuously maintained:

  • Funding rate computation (every block)
  • Mark price updates (continuous)
  • Liquidation checks (continuous)
  • Auto-deleverage processing (event-driven)

None of these fit in a single atomic taker transaction. They require a background process. The crank. That runs continuously. Because the crank is permissionless on Decibel (anyone can trigger it), whoever controls the crank controls matching timing. This is a known sandwich attack vector.

Spot trading has none of these requirements. A spot order either fills at the limit price or doesn't fill. There's no ongoing position maintenance, no funding rate, no liquidation. This is why spot can be crankless and perps currently cannot.

The Whop spot CLOB is crankless by design and by necessity. It is not a feature that was added, it is the fundamental architectural property of atomic spot settlement.

Block-STM Intra-market Parallelism

The deepest technical advantage of Aptos over Solana for this use case.

Solana Sealevel: Transactions declare all accounts upfront. Two transactions touching the same market account serialize. Multiple market makers → sequential execution → throughput bottleneck.

Aptos Block-STM: Optimistic concurrent execution. All transactions run speculatively in parallel. Conflicts detected in validation pass, conflicting transactions re-executed. Multiple makers in the same block execute concurrently. Conflicts resolved after the fact, not pre-prevented.

For the Whop spot CLOB under high maker activity:

  • 50 market makers simultaneously updating their ladders in the same block
  • Block-STM runs all 50 speculatively
  • Conflict detection identifies which ones touched the same price level
  • Only those re-execute sequentially. The rest commit in parallel

This is true intra-market parallelism. The QuoteIntent design reduces the shared-state write surface further (step 1 writes nothing to shared state, and step 2 is the only conflict point), making the re-execution rate minimal even under heavy market-maker activity.

Per-user Order Tracking and Cancel-all

A market maker's most critical operation is canceling their entire ladder instantly in response to a market move (news event, large trade, connectivity issue).

Standard implementation: Iterate the BigOrderedMap to find all orders with maker_address == my_address. O(book size). Expensive and slow under a large orderbook.

Seat-based implementation: Each user's active orders are indexed in their seat. Cancel-all reads from the seat, not from the shared book.

public entry fun cancel_all(
    maker: &signer,
) {
    let seat = borrow_global_mut<Seat>(signer::address_of(maker));
    // O(n) where n = seat.order_count, NOT book size
    for each order_ref in seat.active_orders:
        BigOrderedMap::remove(market, order_ref.key);
    // Return locked collateral to maker
    coin::deposit(signer::address_of(maker), released_collateral);
    seat.active_orders = Table::empty();
    seat.order_count = 0;
}

For a market maker with 20 active quotes: 20 operations. For the same maker on a book with 10,000 resting orders: still 20 operations.

Privacy Integration Details

The Orderbook is Public. Privacy Operates at Other Layers

Resting orders on the BigOrderedMap are fully visible. When Alice places a bid for 10 BTC at $82,000, every observer can see the order, its size, and its price level. This is standard CLOB design and is necessary for price discovery.

Pre-execution Privacy: Encrypted Mempool

The encrypted mempool (pending governance approval) hides transaction contents before block inclusion. No observer can see Alice's incoming order before it lands on the book. This prevents front-running (seeing the order and racing ahead of it) and sandwich attacks (wrapping the order with adversarial trades). Once the order is on the book, it is visible, but by then it is already resting at its stated price, immune to MEV extraction.

Post-settlement Privacy: CA and UTT for Transfers

When Alice's order fills:

  • The match settles atomically. Assets routed to both parties in one transaction
  • Settlement proceeds can optionally use CA (AIP-143) to hide transfer amounts while keeping wallet addresses visible
  • For full privacy, settlement can use UTT to hide both wallet identity and transfer amounts, breaking the link between the fill and the recipient's wallet

Compliance Checkpoint (for UTT Settlements)

When UTT is used for settlement transfers, the ZK circuit enforces budget_new = budget_old − v ≥ 0. A whale settling $50M in BTC fills through UTT would exhaust their monthly anonymity budget, and at that scale, settlements revert to Zone 0 (publicly visible transfers). This is the regulatory behavior required: everyday settlement is private, while systematic accumulation at institutional scale triggers transparency.

Formal Verification Plan

Alex's CLRS-spec-to-Lean pipeline from Dropset ports to this codebase:

  1. 02CLRS specification: The matching engine algorithm (price-time priority, atomic sweep, cancel-all) is specified in CLRS pseudocode
  2. 04Coq/Lean translation: The specification is encoded as a Lean 4 theorem
  3. 06Move implementation: The actual Move module is written to implement the spec
  4. 08Proof obligations: Correctness properties are proven:
    • No money created: sum(inputs) = sum(outputs) + gas holds for all valid transactions
    • Price-time priority preserved: the book always fills the best available price first
    • No partial fills (unless explicitly requested): an order either fills at the limit or remains resting
    • Double-spend impossibility: a nullifier can only appear once in the spent set
    • Cancel idempotency: canceling an already-canceled order is a no-op, not an error

The result: the first formally verified onchain CLOB on Aptos.

Gas Economics

Target: sub-1000 gas units per order operation under normal conditions.

Key contributions to AIP-120's 57% bytecode reduction:

  • BigOrderedMap replaces AVL tree (smaller, flatter structure)
  • Inline functions eliminate function call overhead
  • Zero-copy frames reduce allocation on hot paths
  • Compact vector encoding for bulk operations

For a market maker running 1,000 quote updates per day at 26 gas per Bulk Order (Decibel's measured figure). That's 26,000 gas units/day. With QuoteIntent's O(1) execute-own-intent, the target is ≤30 gas per full ladder replace. HFT market-making becomes economically viable at standard Aptos gas prices.

Implementation Checklist

  • Deploy BigOrderedMap (already available via AIP-120)
  • Implement Seat struct with Table-based order indexing
  • Implement QuoteIntent: submit-intent (private write) + execute-own-intent (atomic B+tree update)
  • Implement crankless atomic taker sweep
  • Integrate CA for settlement amount privacy (optional per-trade)
  • Integrate UTT for settlement identity + amount privacy (optional per-trade)
  • Integrate budget coin compliance checkpoints for UTT settlements
  • Connect chart replay trigger (emit TradeExecutedEvent)
  • Connect Content Rewards attribution
  • Connect PnL card minting on position close
  • Add credits to Alex Kahn from Econia in all code comments
  • CLRS formal specification
  • Lean 4 proof obligations