AereCoreBookV0 is the native on-chain order book of AERE Network — one contract per market, deterministic price-time priority, and a taker fee that feeds the burn engine on every fill. This guide covers the mechanics and the @aere/sdk client.
A central limit order book that lives entirely on chain 2800. No off-chain matcher, no operator, no admin pause — the book is the protocol.
Each (base, quote) pair gets its own AereCoreBookV0 deployment. Market parameters — price tick, lot size, taker fee — are immutable from deploy. Nobody can change the rules of a live market, including the Foundation.
Best bid is the highest resting BUY, best ask is the lowest resting SELL. Within a price level, orders fill strictly first-in-first-out. Matching is deterministic — the same book state always produces the same fills.
Prices must be a multiple of PRICE_TICK, quantities a multiple of LOT_SIZE. Off-grid orders revert. The SDK ships snapToTick / snapToLot helpers so you never send an illegal order.
TAKER_FEE_BPS of the quote leg is charged on every fill (makers pay nothing). Fees accrue in the book and anyone can call flushFees() to forward them to AereSink — the immutable burn router. Every trade feeds the flywheel.
The book deliberately separates resting from taking:
place() puts an order on the book and refuses to cross — if your price would match immediately it reverts with CrossingRequiresSlippage. This makes the naive call safe: you can never get a worse fill than you asked for by accident.iocFill() sweeps the opposite side up to your worst acceptable price. Whatever doesn't fill is discarded, never rested.placeWithSlippage() is the recommended entrypoint for crossing orders — you set a minimum fill, a hard cap on quote spent, and an average-price band, and the transaction reverts if any bound is violated.Resting orders carry a short cancel delay (2 blocks by default, configurable from 0 up to 1200 per order) — an anti-spoofing measure that makes place-and-instantly-cancel games expensive while staying invisible to normal traders.
The book holds funds only while orders rest. A SELL locks the base quantity. A BUY locks the quote principal plus the worst-case taker fee plus a 256-wei rounding buffer — compute it exactly with computeMaxBuyLock(). Anything unspent is refunded in the same transaction. Standard ERC-20 approve to the book address is required before placing.
Proceeds from fills are pushed to your wallet directly. If a token transfer to you fails for any reason, the amount is credited to a pull-based balance instead — check claimableOf() and withdraw with claimBase() / claimQuote(). Funds can never be stranded.
| Parameter | Meaning |
|---|---|
BASE / QUOTE | The token pair. BUY converts quote → base, SELL converts base → quote. |
PRICE_TICK | Minimum price increment. All prices must be exact multiples. |
LOT_SIZE | Minimum quantity increment, in base smallest units. |
TAKER_FEE_BPS | Taker fee in basis points of the quote leg (e.g. 5 = 0.05%). Maker fee is zero. |
SINK | The AereSink address that receives flushed fees. |
QUOTE_DECIMALS_FACTOR / BASE_DECIMALS_FACTOR | 10**decimals() of each token — used by the quote-amount math for cross-decimal pairs. |
One convention to remember: price is quote-per-base scaled by 1e18, independent of token decimals.
For a human price P (quote units per one whole base unit), the on-chain price is always P × 1e18 — the decimals factors cancel out in the fill math. Quantity is a uint128 in base smallest units, snapped to the lot grid. The SDK does all of this for you:
import { humanPriceToWei, weiPriceToHuman, snapToTick, snapToLot, computeQuote } from '@aere/sdk'; // "0.85 quote per base" → uint128 book price (P × 1e18, regardless of decimals) const raw = humanPriceToWei('0.85', 6 /* quote decimals */, 18 /* base decimals */); const price = snapToTick(raw, m.priceTick); // snap to the market's grid // 1,000 whole base units, snapped to the lot grid const qty = snapToLot(1000n * m.baseDecimalsFactor, m.lotSize); // exact mirror of the contract's fill math (floor division) const quoteOut = computeQuote(price, qty, m.quoteDecimalsFactor, m.baseDecimalsFactor); weiPriceToHuman(price); // → "0.85"
CoreBookClient is dependency-free — it wraps any EIP-1193 provider (window.ethereum, viem, ethers) and builds calldata with precomputed selectors. You sign; it never touches your keys.
npm i @aere/sdk import { CoreBookClient, AERE_COREBOOK } from '@aere/sdk'; const book = new CoreBookClient({ address: AERE_COREBOOK.markets['WAERE/USDC.e'], // empty until launch — any book address works provider: window.ethereum, // any EIP-1193 provider on chain 2800 }); const m = await book.marketInfo(); // { base, quote, sink, priceTick, lotSize, takerFeeBps, // quoteDecimalsFactor, baseDecimalsFactor }
import { COREBOOK_BID_EMPTY, COREBOOK_ASK_EMPTY, weiPriceToHuman } from '@aere/sdk'; const [bid, ask] = await Promise.all([book.bestBid(), book.bestAsk()]); // sentinels: bid side empty → 0n; ask side empty → 2**128 - 1 if (bid !== COREBOOK_BID_EMPTY) console.log('best bid', weiPriceToHuman(bid)); if (ask !== COREBOOK_ASK_EMPTY) console.log('best ask', weiPriceToHuman(ask));
import { humanPriceToWei, snapToTick, snapToLot, computeMaxBuyLock } from '@aere/sdk'; const [from] = await window.ethereum.request({ method: 'eth_requestAccounts', params: [] }); // 1 — build a tick/lot-legal order const price = snapToTick(humanPriceToWei('0.85', 6, 18), m.priceTick); const qty = snapToLot(1000n * m.baseDecimalsFactor, m.lotSize); // 2 — approve the exact BUY lock: principal + fee ceiling + 256-wei buffer. // (For SELL orders, approve the base quantity instead.) const lock = computeMaxBuyLock(price, qty, m.takerFeeBps, m.quoteDecimalsFactor, m.baseDecimalsFactor); await book.ensureApprovalForBook(m.quote, from, lock); // approves only if needed // 3 — rest on the book. Reverts with CrossingRequiresSlippage if it would // match immediately — crossing flow belongs to placeIOC / placeWithSlippage. const txHash = await book.placeGTC(from, 0 /* BUY */, price, qty); // optional: custom anti-spoof cancel delay (0–1200 blocks; default is 2) await book.placeGTC(from, 0, price, qty, 10);
// Sweep the ask side up to a worst price. Unfilled remainder is discarded. await book.placeIOC(from, 0 /* BUY */, worstPrice, qty); // Recommended for anything size-sensitive: explicit bounds, atomic revert. await book.placeWithSlippage( from, 0 /* BUY */, price, qty, 1 /* IOC */, minBaseFilled, // revert if fewer base units fill maxQuoteSpent, // hard cap on quote out — (1n << 256n) - 1n = uncapped maxAvgPriceWei, // average-price band — 0n disables );
await book.cancel(from, orderId); // honors the order's cancel delay // pull-based credits exist only if a direct payout transfer ever failed const { base, quote } = await book.claimableOf(from); if (base > 0n) await book.claimBase(from); if (quote > 0n) await book.claimQuote(from); // anyone may forward accrued taker fees to AereSink await book.flushFees(from);
| Method | Use it for |
|---|---|
placeGTC(from, side, price, qty, delay?) | Resting limit order. Refuses to cross. Optional per-order cancel delay (0–1200 blocks). |
placeIOC(from, side, worstPrice, qty) | Immediate fill up to a worst price; remainder discarded. |
placeWithSlippage(…) | Crossing order with min-fill, quote-spend cap, and average-price band. GTC or IOC. |
placeWithSlippageProtected(…) | All slippage bounds plus a custom cancel delay on the resting remainder. |
cancel(from, orderId) | Remove a resting order; locked funds return immediately. |
claimBase(from) / claimQuote(from) | Withdraw pull-based credits from failed payout transfers. |
flushFees(from) | Permissionless — forwards pendingSinkFee() to AereSink. |
Side is 0 = BUY, 1 = SELL. TimeInForce is 0 = GTC, 1 = IOC.
The contract stores the book as linked lists; the SDK lens composes plain eth_call reads into the views a UI actually needs. No indexer required.
const { bids, asks } = await book.depth(15); // bids: best (highest) first · asks: best (lowest) first // each level: { price, quantity, orderCount, truncated }
The lens walks the populated-level lists pointer by pointer — one RPC read per hop and per resting order — so keep levels UI-sized (10–25) and let your indexer handle full-book analytics.
// orderId = uint128(keccak256(abi.encode(maker, nonce))) — derived client-side. // openOrdersOf probes recent nonces until openOrderCount orders are found. const open = await book.openOrdersOf(from); // [{ orderId, maker, side, price, quantity, placedAt, cancelDelayBlocks, … }] // or derive ids yourself (a maker's first order uses nonce 1): import { deriveOrderId } from '@aere/sdk'; const nonce = await book.makerNonce(from); // latest used nonce const lastId = deriveOrderId(from, nonce); const order = await book.getOrder(lastId); // { exists, price, quantity, … }
await book.pendingSinkFee(); // taker fees waiting for the next flush await book.openOrderCount(maker); // resting orders (max 256 per maker) await book.balanceOf(m.quote, from);// ERC-20 funding check before placing await book.allowanceForBook(m.quote, from);
Four events describe everything that ever happens to a market. The SDK exports the topic hashes and a decoder — no ABI files needed.
| Event | Emitted when |
|---|---|
OrderPlaced | An order rests on the book — orderId, maker, side, price, quantity. |
Filled | A taker matches a maker — both order ids, both parties, taker side, fill price, quantity, taker fee. |
OrderCancelled | A resting order is cancelled — includes the unfilled remainder. |
Claimed | Pull-based credits are withdrawn. |
import { decodeCoreBookLogs } from '@aere/sdk'; const receipt = await window.ethereum.request({ method: 'eth_getTransactionReceipt', params: [txHash], }); const events = decodeCoreBookLogs(receipt.logs, book.address); for (const ev of events) { if (ev.type === 'Filled') console.log(`filled ${ev.quantity} @ ${ev.price}`); }
import { TOPIC_ORDER_PLACED, TOPIC_FILLED, TOPIC_ORDER_CANCELLED, decodeCoreBookLogs } from '@aere/sdk'; const logs = await provider.request({ method: 'eth_getLogs', params: [{ address: book.address, topics: [[TOPIC_ORDER_PLACED, TOPIC_FILLED, TOPIC_ORDER_CANCELLED]], // OR-match fromBlock: lastSeen, toBlock: 'latest', }], }); const events = decodeCoreBookLogs(logs);
// wss://wss.aere.network — sub-second blocks make polling fine too, // but eth_subscribe gives you fills the moment they land. ws.send(JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_subscribe', params: ['logs', { address: book.address, topics: [[TOPIC_FILLED]] }], })); // decode incoming params.result with decodeCoreBookLogs([log])
Topic constants are keccak hashes of the exact event signatures, precomputed in the SDK — pin your indexer to them rather than recomputing from ABI strings.
Contract: contracts/corebook/AereCoreBookV0.sol · SDK: @aere/sdk → src/corebook/CoreBookClient.ts. Every helper in the SDK mirrors the contract math exactly and is verified against the contract test suite. Chain ID 2800 · RPC https://rpc.aere.network · WS wss://wss.aere.network.
Questions or integration review before launch: contact the team.