CoreBook Developer Docs

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.

Markets open at launch

The CoreBook contract is final and fully tested (289/289 tests, six internal audit rounds, external audit contest scheduled), but no markets are listed yet — market listing is a Foundation decision. Everything on this page works against any deployed book address, and the SDK surface below is stable. When the first market opens, its address lands in AERE_COREBOOK.markets.

How the order book works

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.

One contract, one market

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.

Price-time priority

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.

Ticks and lots

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 → AereSink

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.

taker fill→ fee accrues in book→ flushFees() — permissionless→ AereSink: burn / buyback / staker yield

Order types and the crossing rule

The book deliberately separates resting from taking:

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 funds model

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.

Market immutables

ParameterMeaning
BASE / QUOTEThe token pair. BUY converts quote → base, SELL converts base → quote.
PRICE_TICKMinimum price increment. All prices must be exact multiples.
LOT_SIZEMinimum quantity increment, in base smallest units.
TAKER_FEE_BPSTaker fee in basis points of the quote leg (e.g. 5 = 0.05%). Maker fee is zero.
SINKThe AereSink address that receives flushed fees.
QUOTE_DECIMALS_FACTOR / BASE_DECIMALS_FACTOR10**decimals() of each token — used by the quote-amount math for cross-decimal pairs.

Prices and quantities

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"

SDK quickstart

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.

Install + connect
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 }
Top of book
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));
Place a resting GTC bid — approve, then place
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);
Take liquidity — IOC and slippage bounds
// 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
);
Cancel + claim
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);

Entrypoint reference

MethodUse 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.

Lens read patterns

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.

Aggregated depth — top N levels per side
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.

A maker's open orders — no enumeration view needed
// 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, … }
Other point reads
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);

Event streams

Four events describe everything that ever happens to a market. The SDK exports the topic hashes and a decoder — no ABI files needed.

EventEmitted when
OrderPlacedAn order rests on the book — orderId, maker, side, price, quantity.
FilledA taker matches a maker — both order ids, both parties, taker side, fill price, quantity, taker fee.
OrderCancelledA resting order is cancelled — includes the unfilled remainder.
ClaimedPull-based credits are withdrawn.
Decode your own transaction receipt
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}`);
}
Poll a market with eth_getLogs
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);
Live stream over WebSocket
// 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.

Source and verification

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.