Titandocs

Add a market

Four places to touch, in the order that avoids a market that routes but 400s.

Adding a perp touches the oracle, the engine, the BFF (implicitly), and the interface. Doing it in the wrong order produces a market users can navigate to but not trade.

Order

Oracle — get a price first

Add the asset with each venue's symbol for it. Venues disagree: BTCUSDT / BTC-USD / BTC-USDT.

cd server/oracle
npm run once

An asset printing "m":"na" means no venue delivered a usable frame. Stop here if so — without an index price the engine cannot compute mark, funding, or liquidation.

Engine — markets.json

{
  "AVAX-USDCX": {
    "maxLeverage": 20,
    "szDecimals": 2,
    "minNotional": "10",
    "maintMarginFraction": "0.05",
    "fundingIntervalSeconds": 3600
  }
}

szDecimals is the size grid exponent and it also bounds price precision (≤ 6 - szDecimals decimals). Pick it from the asset's realistic tick size, not arbitrarily.

Restart the engine.

Verify the BFF sees it

curl -s http://localhost:4000/v1/markets | jq '.[] | select(.id=="AVAX-USDCX")'

Nothing to change here — the BFF reads the engine's config.

Interface — registry, then seed

Token registry (src/lib/tokens/registry.ts) — add the key, name, logo, decimals, plus any alias:

CAVAX: { symbol: 'cAVAX', name: 'Canton AVAX', decimals: 8, logo: '/tokens/avax.svg' },
// alias layer: the engine calls it "AVAX"
TOKEN_ALIASES.AVAX = 'CAVAX';

Market seed (src/lib/tokens/markets.ts):

{ id: 'AVAX-USDCX', base: 'CAVAX', quote: 'USDCX', maxLeverage: 20, szDecimals: 2 }

Why the interface needs a seed at all

Route-loader slug validation is synchronous — it runs before any fetch can complete. So MARKETS must exist statically for /trade/avax-usdcx to validate.

GET /v1/markets then overwrites maxLeverage, szDecimals, minNotional, fees, maintMarginFraction and fundingIntervalSeconds, and records enabledMarkets.

A seeded market the engine does not report disappears from the pair selector. That is deliberate: better an absent market than one that routes cleanly and then 400s on every order.

The corollary — if you add the seed but forget the engine config, the market simply will not appear, and nothing will tell you why.

Checklist

  • Oracle prints a real price for the asset (npm run once).
  • markets.json has the market; engine restarted.
  • GET /v1/markets reports it.
  • Token registry has the key, the logo file exists, and the engine alias resolves.
  • Market seed added with matching szDecimals.
  • /trade/<slug> loads and the pair selector lists it.
  • A grid-valid order places; a deliberately off-grid one is rejected with invalid_size.

Removing a market

Remove it from markets.json and restart the engine. The interface drops it from the pair selector automatically via enabledMarkets — the seed entry can stay until the next cleanup without harming anything.

On this page