Design Goals
- 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
- 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
- 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), notO(book size) - 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)
- 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
| Property | QuoteIntent | Decibel Bulk Orders |
|---|---|---|
| Shared state writes on update | 1 (execute-own-intent only) | N (one per order in batch) |
| Block-STM conflict surface | Minimal | Higher under many makers |
| Cancel-replace race condition | Impossible (atomic) | Protected by sequence numbers |
| Runtime | Aptos Move | Aptos Move |
| Gas cost per update | O(1) flat | O(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:
- 02CLRS specification: The matching engine algorithm (price-time priority, atomic sweep, cancel-all) is specified in CLRS pseudocode
- 04Coq/Lean translation: The specification is encoded as a Lean 4 theorem
- 06Move implementation: The actual Move module is written to implement the spec
- 08Proof obligations: Correctness properties are proven:
- ›No money created:
sum(inputs) = sum(outputs) + gasholds 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
- ›No money created:
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