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/referencedepth-weights a token's price across every venue it can read (its bonding-curve/internal pool, its HartiiSwap WQUAI pair) into onemid/low/highband 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_fillsD1 table plus a FIFO lot-accounting function, ready to record real fills once there are any. See FIFO P&L. - The
/mmdashboard — 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, plusGET /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/pairsentry carriespreview: trueandmaker: 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_VAULTis optional, and unset today. This environment variable names the wallet whose balancesGET /api/mm/positionsreads. Unset (the default on hartiilabs.com right now) means every position reportsfunded: falsewith 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
statusstays"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:
| Field | Value | Meaning |
|---|---|---|
pair | QAXE/WQUAI | Display name. |
token | 0x0035187a7660f595d93cd53a4d16c635d6cffc8f | The QAXE token address. |
depthTargetQuai | 25000 | Target 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). |
targetRatio | 0.5 | Target QUAI share of inventory value — 50/50. |
band | 0.15 | Allowed drift around the target before inventory bands flags a rebalance — ± 15 percentage points. |
limits.minSpreadBps | 40 | Floor on the quote preview's full bid-to-ask spread. |
limits.maxSpreadBps | 300 | Ceiling — a computed spread above this sets reasons: ["spread-too-wide"] and ok: false. |
limits.maxConfidenceBps | 400 | Ceiling 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:
- Depth-weighted average.
midis the weighted mean of every venue with a known price, weighted bydepthQuaiWei. If every priced venue reports zero depth, it falls back to an equal weighting instead of dividing by zero. - 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 tolow/high—lowis the smallest lower bound across venues,highthe largest upper bound.confidenceBpsis the wider of(mid−low)/(high−mid)as a fraction ofmid. A venue with an explicitstalenessBpsuses that instead of computing one fromageBlocks. - Flags.
drift:<venue>when a venue's own price sits more than a configurable 100 bps frommid;thinwhen total depth across all priced venues is below a configurable 1,000 QUAI floor. Both are informational — the reference endpoint still returns amid, 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'sminSpreadBps. If the result exceedsmaxSpreadBps, or the reference'sconfidenceBpsexceedsmaxConfidenceBps, the preview adds a reason and setsok: 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
depthTargetQuaiper 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.makeridentifies 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.
Related
- API reference › Market-maker tools — exact request/response shapes, caching, and error cases for every route mentioned above.
- Pricing a token — the underlying curve/pool spot-price math the reference endpoint reads from.
- How the indexer works — the money-math rules (
TEXTwei columns,BigInt-only arithmetic) this feature follows. - Versions & changelog — the 1.4.0 entry this feature shipped under.