HartiiLabs docs

HartiiLabs

Market-maker tools (v0)

HartiiLabs is building toward a market maker that quotes two-sided prices on its own tokens. v0 — shipped 2026-10-04 — is the read-only foundation that gets built first: a price reference that aggregates every venue a token trades on, a deterministic quote-preview calculator, inventory-band math, and a FIFO P&L ledger. All of it reads; none of it writes to the chain.

What v0 is

  • A price reference — GET /api/token/:addr/reference depth-weights a token's price across every venue it can read (its bonding-curve/internal pool, its HartiiSwap WQUAI pair) into one mid/low/high band with a confidence score. See below.
  • A quote preview — given a reference and an inventory state, a pure function computes what a bid/ask would look like (spread, size, skew) without ever sending anything. See Quote preview.
  • Inventory bands — a pure function marks a QUAI/token inventory to the reference price and checks it against a 50/50 ± 15% target. See Inventory bands.
  • A FIFO P&L store — an append-only mm_fills D1 table plus a FIFO lot-accounting function, ready to record real fills once there are any. See FIFO P&L.
  • The /mm dashboard — a page with no navigation entry (owner-only, reached by typing the URL) that renders all of the above read-only: the pair registry, vault inventory, fill history, and P&L. Every number on it links back to the public API call that produced it.
  • The public API — GET /api/mm/pairs, GET /api/mm/positions, GET /api/mm/fills, GET /api/mm/pnl, plus GET /api/token/:addr/reference. Full request/response shapes, caching, and error cases: API reference › Market-maker tools.

What v0 is not

  • No maker is quoting. There is no process, bot, or wallet placing orders against these numbers. Every GET /api/mm/pairs entry carries preview: true and maker: null — constants in this release, not fields that happen to be empty right now.
  • Nothing sends a transaction. Every module under functions/_lib/mm/ is pure math: no RPC write call, no signer, no wallet. The only chain reads are the price-reference lookups and, if a vault is configured, its balance.
  • HARTII_LABS_MM_VAULT is optional, and unset today. This environment variable names the wallet whose balances GET /api/mm/positions reads. Unset (the default on hartiilabs.com right now) means every position reports funded: false with honestly zeroed balances — not an error, not a simulated position. Funding this vault would make positions real reads of a real wallet; it would still not make the maker quote.
  • A pair's status stays "planned" until an actual funded maker exists for it. The one registered pair today, QAXE/WQUAI, is "planned".

The pair registry

One pair is registered today, in src/data/mmPairs.json:

FieldValueMeaning
pairQAXE/WQUAIDisplay name.
token0x0035187a7660f595d93cd53a4d16c635d6cffc8fThe QAXE token address.
depthTargetQuai25000Target two-sided depth, in QUAI — a quote preview's size per side is half of this (less on the heavier side when inventory is skewed).
targetRatio0.5Target QUAI share of inventory value — 50/50.
band0.15Allowed drift around the target before inventory bands flags a rebalance — ± 15 percentage points.
limits.minSpreadBps40Floor on the quote preview's full bid-to-ask spread.
limits.maxSpreadBps300Ceiling — a computed spread above this sets reasons: ["spread-too-wide"] and ok: false.
limits.maxConfidenceBps400Ceiling on the reference's own confidenceBps — above this the preview also refuses itself ("confidence-too-wide").
status"planned"No maker funded for this pair.

GET /api/mm/pairs returns this exact row shape, enriched with a live reference, venues, and quote — see the API reference for the full response and a real worked example (including one where the preview correctly refuses to quote because confidence was too wide).

How the reference mid is computed

functions/_lib/mm/referenceMath.js's computeReference takes a list of venues — each with a priceWei (QUAI wei per whole token) and a depthQuaiWei — and produces one reference:

  1. Depth-weighted average. mid is the weighted mean of every venue with a known price, weighted by depthQuaiWei. If every priced venue reports zero depth, it falls back to an equal weighting instead of dividing by zero.
  2. Staleness widens the band, it doesn't move the mid. Each venue's price is allowed to drift ageBlocks × 5 bps (configurable, capped at 5,000 bps) before contributing to low/high — low is the smallest lower bound across venues, high the largest upper bound. confidenceBps is the wider of (mid−low)/(high−mid) as a fraction of mid. A venue with an explicit stalenessBps uses that instead of computing one from ageBlocks.
  3. Flags. drift:<venue> when a venue's own price sits more than a configurable 100 bps from mid; thin when total depth across all priced venues is below a configurable 1,000 QUAI floor. Both are informational — the reference endpoint still returns a mid, it just tells you why to trust it less.

Today there are two live venues per token (curve, hartiiswap) and two typed-but-always-null placeholders (external, conversion) reserved for a future off-platform venue and a future QUAI/Qi conversion leg — they carry zero weight until they have a real price to contribute.

Quote preview

functions/_lib/mm/quoter.js's quote() is a pure function: given a reference, an inventory state, a volatility estimate, a depth target, and the pair's spread/confidence limits, it computes a bid/ask preview — and nothing else. It never touches the network.

  • Spread is 2 × max(minSpreadBps, poolFeeBps + ½·sigmaBps + ½·confidenceBps + ½·|skewBps|) — the pool fee, half the volatility estimate, half the reference's own confidence, and half the inventory skew all widen the spread, floored at the pair's minSpreadBps. If the result exceeds maxSpreadBps, or the reference's confidenceBps exceeds maxConfidenceBps, the preview adds a reason and sets ok: false — it still returns the numbers (so you can see why it would refuse), it just tells you honestly that it would not actually quote this.
  • Skew comes from how far the inventory sits from its 50/50 ± band target (see next section), scaled to at most ± 100 bps, and shifts both sides of the quote in the same direction — a QUAI-heavy book quotes a higher bid and ask (encouraging buys of the token), a token-heavy book quotes lower (encouraging sells).
  • Size is half of depthTargetQuai per side by default, reduced on the heavier side as skew grows (down to zero reduction at skew 0, more reduction as |skewBps| approaches its cap).

Inventory bands

functions/_lib/mm/inventory.js's inventoryState() marks a { quai, tokenQty } position to a reference price and checks it against a target ratio and band — 50/50 ± 15% for the registered pair. Outside the band it returns a rebalance: { side, amountQuai } — side: "buy" when QUAI is over-weight, "sell" when the token is. This function is advisory only: nothing acts on a rebalance hint in v0 — there is no maker to act on it.

FIFO P&L

functions/_lib/mm/pnl.js's pnlFromFills() walks a list of fills in block order, opening a token lot at its QUAI cost on every buy and consuming the oldest open lots first on every sell (strict FIFO) to compute realisedQuai. Fees are tracked separately in feesQuai — netted out of inventory, but never folded into the realised/unrealised figures, so the gross trading result and the fee drag are always two distinct numbers. The backing store is mm_fills, an append-only D1 table (wei amounts stored as TEXT, matching every other money column in this app — see Indexer › money-math rules) — empty in v0, since nothing has filled yet.

Roadmap: market integrity

Before any maker is actually funded and allowed to quote, HartiiLabs intends to enforce (planned — not yet implemented; there is no live maker for these rules to apply to today):

  • No self-matching — a maker's own bid and ask must never cross against each other on the same venue.
  • No quoting against a pre-graduation curve — a maker only quotes a pair once its token has graduated to its internal pool, never against the bonding-curve formula price.
  • Every maker fill is tagged — mm_fills.maker identifies which maker wallet produced a fill, so a market-maker's activity is always distinguishable from organic trades in the data, not inferred after the fact.

These rules live in the engineering roadmap, not in shipped code — track AGENTS.md's "Market-maker tools" section and this app's CHANGELOG.md in the hartii-labs repo for when any of them land.

Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.