Titandocs

Markets & tokens

One dialect for market ids, one alias layer for asset names, and the placement grid that mirrors the engine's rejection rules.

Markets

src/lib/tokens/markets.ts seeds the tradable perps:

MarketMax leverageszDecimalsQuote
BTC-USDCX40×5USDCx
ETH-USDCX25×4USDCx
SOL-USDCX20×2USDCx

MARKETS is a seed, not the truth. Route-loader slug validation is synchronous, so the list must exist before any fetch can complete. GET /v1/markets (useMarketConfig) 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 — better than routing a user into a market that 400s.

One dialect

id"BTC-USDCX" — is the engine symbol, and it flows unchanged through:

  • /trade/:marketSlug (lowercased for the URL only)
  • ?market= on public reads
  • markets= on the WebSocket filter
  • every /v1/users/:party/* read
marketLabel(m);        // "BTC / USDCx"  — display only
slugOf(m);             // "btc-usdcx"    — URL only
findMarketBySlug(s);   // case-insensitive, used by the route loader
findMarket(id);        // by engine symbol
DEFAULT_MARKET;

Token registry & the alias layer

src/lib/tokens/registry.ts owns display metadata. Keys form a literal union:

type TokenKey = 'CC' | 'CBTC' | 'CETH' | 'CSOL' | 'USDCX';

The engine names its oracle assets BTC / ETH / SOL; the registry keys the Canton wrappers. The quote arrives as USDCX or USDC. One alias layer reconciles all of it:

resolveTokenKey('BTC');    // → 'CBTC'
resolveTokenKey('USDC');   // → 'USDCX'
assetLogo('ETH');          // → logo path, cBTC fallback so no broken <img>

Doing this in one place matters more than it looks. Ad-hoc symbol === 'BTC' ? … checks scattered across components is how half the app ends up showing the wrong icon for a market the other half renders correctly.

The placement grid

The engine rejects, never rounds — and rejections arrive as HTTP 200 with ok: false:

CodeMeaning
invalid_sizeSize is not an exact multiple of 10^-szDecimals
invalid_pricePrice violates the significant-figure / decimal bound
below_min_notional (0x0C)price × size under minNotional on an opening leg

So src/lib/market-grid.ts mirrors the rules client-side:

Size — exact multiple of 10^-szDecimals. Floor onto the grid before deriving any margin or fee figure, or the preview disagrees with the fill.

Price — integer, or ≤5 significant figures and6 - szDecimals decimals.

Notionalprice × size ≥ minNotional, on the opening leg only. A reduce is exempt.

Never gate a Close on minNotional. Reduce-only orders are exempt engine-side; enforcing it in the UI means a user with a small residual position cannot close it.

With szDecimals unknown there is no client-side grid at all. Guessing one number loosens the size bound while tightening the price bound — worse than not validating, because it rejects valid orders locally and lets invalid ones through.

UI helpers

<TokenIcon symbol="CBTC" />
<MarketIconPair market={market} />   // two overlapping icons, quote offset 0.62×

On this page