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 and six internal review rounds; no external audit yet), 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
// @aere/sdk is not on npm yet: build it from git.aere.network/aere-network/sdk-js (npm run build)

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 to 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 to 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 to 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, so no ABI files are 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, with 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.