Skip to main content

Why traditional spot order books have failed on-chain

  1. You can’t update your full book atomically: on a CEX, you batch-cancel and batch-place orders. On Solana, transaction size limits cap how many orders you can place at once. Refreshing a book takes multiple transactions, and between them, your quotes are partially stale. Searchers often see this and pick you off.
  2. Block latency: from the moment you submit a cancel until it confirms, your resting order is live. On Solana it’s at least one block (~400ms), and block priority favors low-CU transactions, so a searcher’s single-instruction pickoff lands higher in the block than your multi-instruction cancel/replace.
On-chain order books have fundamentally been designed for takers. For example, a batch update on Manifest (a large Solana-native orderbook) costs ~50,000 CUs. Even if that txn lands in the same block as a price move, a searcher’s low-CU pick-off txn gets scheduled higher in the block, so your refresh arrives after.

Hadron

Unlike traditional on-chain order books, with Hadron you can efficiently place/cancel orders and segment flow, preventing pick-off. Instead of placing individual orders that each cost CUs to create, modify, and cancel, you define a continuous function that maps any incoming trade to a price.
Refreshing your entire book (curve) is a single mid-price update: ~32 CUs. That’s 1,500× cheaper than a Manifest batch update, 3,000× cheaper than adding/removing concentrated liquidity in an AMM, and you land near the top of the block for a structural maker advantage.

How does this work, I thought Hadron was a PropAM?

Every Hadron pool is defined by a single function:
output = f(input_size, mid_price, price_curve, inventory_curve)
where price_curve and inventory_curve are parameterized functions of “points” and “interpolations.” To quote as an Order Book, we use only the inventory curve, with step-interpolated points. When a swap arrives, Hadron walks through the inventory curve like an order book, applying different spreads to the mid-price at each level. Key advantage: because all prices are defined as spreads relative to mid-price, a single mid-price update (~32 CUs) instantly shifts every level in the book. That is equivalent to cancelling and re-placing your entire ladder of limit orders in one atomic operation. In the example below, change the mid-price to see the entire book shift, then increase “Trade size” to watch orders get filled:
import { HadronOrderbook } from "@hadron-fi/sdk";

const book = await HadronOrderbook.load({
  connection,
  pool: poolAddress,
});

// Bid orders — increasing size, widening spread from mid
book.placeOrder({ side: "bid", size: 500,   spreadBps: 15 });
book.placeOrder({ side: "bid", size: 1000,  spreadBps: 30 });
book.placeOrder({ side: "bid", size: 2000,  spreadBps: 50 });
book.placeOrder({ side: "bid", size: 3500,  spreadBps: 80 });
book.placeOrder({ side: "bid", size: 5000,  spreadBps: 120 });
book.placeOrder({ side: "bid", size: 7000,  spreadBps: 170 });
book.placeOrder({ side: "bid", size: 11000, spreadBps: 230 });

// Ask orders — mirror of bid side
book.placeOrder({ side: "ask", size: 500,   spreadBps: 15 });
book.placeOrder({ side: "ask", size: 1000,  spreadBps: 30 });
book.placeOrder({ side: "ask", size: 2000,  spreadBps: 50 });
book.placeOrder({ side: "ask", size: 3500,  spreadBps: 80 });
book.placeOrder({ side: "ask", size: 5000,  spreadBps: 120 });
book.placeOrder({ side: "ask", size: 7000,  spreadBps: 170 });
book.placeOrder({ side: "ask", size: 11000, spreadBps: 230 });

const ix = book.push();

Integrating Hadron as an order book

Hadron is reducible to a fully on-chain order book: we give you an interface that feels like an order book, with one key difference, Hadron has no matching engine. The matching engine is the aggregator (Jupiter, Titan, etc.).

SDK (TypeScript) example

1. Load your pool
import { HadronOrderbook } from "@hadron-fi/sdk";

const book = await HadronOrderbook.load({
  connection,
  pool: poolAddress,
});
2. Place orders Place limit orders just like on any exchange:
// Place bid orders (prices defined as spread below mid)
book.placeOrder({ side: "bid", size: 500,   spreadBps: 15 });
book.placeOrder({ side: "bid", size: 1000,  spreadBps: 30 });
book.placeOrder({ side: "bid", size: 2000,  spreadBps: 50 });
book.placeOrder({ side: "bid", size: 5000,  spreadBps: 80 });
book.placeOrder({ side: "bid", size: 10000, spreadBps: 120 });

// Place ask orders (spread above mid)
book.placeOrder({ side: "ask", size: 500,   spreadBps: 15 });
book.placeOrder({ side: "ask", size: 1000,  spreadBps: 30 });
book.placeOrder({ side: "ask", size: 2000,  spreadBps: 50 });
book.placeOrder({ side: "ask", size: 5000,  spreadBps: 80 });
book.placeOrder({ side: "ask", size: 10000, spreadBps: 120 });

const ix = book.push();
Orders are staged locally until you call .push(), which returns a single atomic instruction. 3. Update mid-price (move the entire book) One instruction, ~32 CUs. This atomically shifts every order — equivalent to cancelling and re-placing your entire ladder at new prices:
const ix = book.updateMidprice(121.05);
// Every bid and ask order is now centered around 121.05
4. Amend and cancel orders Stage any combination of new orders, amendments, and cancellations, then send them in one shot:
book.amendOrder({ side: "bid", level: 2, spreadBps: 70 });
book.amendOrder({ side: "ask", level: 1, size: 2000 });
book.cancelOrder({ side: "bid", level: 0 });
book.placeOrder({ side: "ask", size: 15000, spreadBps: 150 });

const ix = book.push();
Under the hood, .push() packs all staged operations into a single instruction (~50 CUs). Updates are applied atomically on the next swap. 5. Read book state
const state = await book.getBookState();
// { midprice, bids, asks, inventory }
6. Subscribe
book.onFill((fill) => { /* side, size, price, taker, slot, signature */ });
book.onState((state) => { /* inventory, midprice */ });
book.onHealth((h) => { /* oracle staleness, swap errors */ });