client.catalog is a snapshot of venue reference data (assets, pairs, scales, routes) with typed
readers on top. Every service consults it to turn symbols into ids and wire integers into exact
decimal strings. This page documents the reader surface; for the design and the decimal-string
contract, see Catalog & precision.
The four readers all hang off the catalog:
| Reader | Covers |
|---|---|
catalog.market | Trading assets and pairs: symbol and id lookups, listing status, price and quantity conversions. |
catalog.ledger | Ledger assets: id and symbol lookups, amount conversion and display formatting. |
catalog.orders | Per-pair order constraints (tick, step, minimums) and order input validation. |
catalog.zipper | Deposit and withdraw chains, unified assets, per-chain routes, and contracts. |
Across every reader, get* and lookup* methods return null on a miss, and require* methods
throw a CatalogLookupError from @polyester/sdk/catalogs.
Lifecycle
The catalog starts empty and fills itself from two public endpoints (spot config and zipper config) the first time something needs it. Service methods await readiness internally, so you only manage the lifecycle yourself before direct reader reads.
await client.catalog.ensureReady(); // fetch if empty, then resolve the snapshot| Method | Behavior |
|---|---|
ensureReady() | Resolve the current snapshot, fetching if the catalog is empty. |
ready() | Passive: resolve the current snapshot or an in-flight refresh, never start one. |
refresh() | Force a refetch (deduplicated while one is in flight). |
state() | { status } where status is empty, refreshing, fresh, or stale. |
snapshot() | The current CatalogSnapshot, or throw CatalogNotReadyError if empty. |
setSnapshot(s) | Replace the snapshot with one you already have. |
stale means a refresh failed and the previous snapshot is still being served rather than dropped.
ensureReady() yourself only before reading a reader directly, as in the examples
below.catalog.market
Assets and trading pairs, plus exact price and quantity conversions. Enriched pairs retain baseQuantityScale and quoteQuantityScale from the spot config. These fields can be absent in
older snapshots; freshly populated catalogs preserve them.
Assets also carry an optional marketDataVolumeScale, used to decode candle volume and
market-overview 24h base volume. Pairs carry an optional referencePriceScale, used to decode
composite reference candle prices. Snapshots without these fields fall back to the asset
quantity scale and the 9-decimal trading price scale.
await client.catalog.ensureReady();
const symbolId = client.catalog.market.requireSymbolIdByPairSymbol("BTC-USDT");
const pair = client.catalog.market.requirePairBySymbol("BTC-USDT");
const listed = client.catalog.market.listPairs({ listed: true });Lookups (each has a get* null-returning and a require* throwing form):
- Assets:
listAssets(),getAsset(key)/requireAsset(key),getAssetBySymbol(sym)/requireAssetBySymbol(sym),getAssetByLedgerId(id)/requireAssetByLedgerId(id). - Pairs:
listPairs(filter?),getPair(key)/requirePair(key),getPairBySymbol(sym)/requirePairBySymbol(sym),getPairBySymbolId(id)/requirePairBySymbolId(id). - Id and symbol crosswalk:
getSymbolIdByPairSymbol(sym)/requireSymbolIdByPairSymbol(sym),getPairSymbolBySymbolId(id)/requirePairSymbolBySymbolId(id).
Conversions and formatting (all exact, all keyed by a pair):
// Normalize UI input to the pair's precision by truncating excess fractional digits
client.catalog.market.normalizePriceInput("64250.512345", "BTC-USDT");
client.catalog.market.normalizeQuantityInput("0.2500", "BTC-USDT");
// Strict decimal string <-> scaled integer conversion
client.catalog.market.decimalPriceToTicks("64250.5", "BTC-USDT");
client.catalog.market.decimalQuantityToScaled("0.25", "BTC-USDT");
client.catalog.market.priceTicksToDecimalString(64250500000000n, "BTC-USDT"); // "64250.5"
// Display-rounded strings for UI
client.catalog.market.formatPrice("64250.5", "BTC-USDT");
client.catalog.market.formatQuantity("0.25", "BTC-USDT");Keys accept a pair symbol ("BTC-USDT"), a numeric symbolId, or { symbol } / { symbolId }.
normalizePriceInput and normalizeQuantityInput are for partial form input: they truncate to
catalog scale but do not round, snap to a tick grid, or check order constraints. The strict
converters reject excess precision with CatalogConversionError, exported from @polyester/sdk/catalogs.
catalog.ledger
Ledger assets, addressed by symbol or numeric ledger id, with amount conversion and display formatting.
const usdc = client.catalog.ledger.requireAssetBySymbol("USDC");
const display = client.catalog.ledger.formatAmount("1234.5", "USDC"); // display-roundedcatalog.orders
Per-pair order constraints and order-input validation. Use these to build order forms and to
pre-validate before calling client.orders.create.
const constraints = client.catalog.orders.getSpotOrderConstraints("BTC-USDT");
// tick size, step size, minimums, and protobuf wire ceilings
const result = client.catalog.orders.validateSpotOrderDecimalInput({
pair: "BTC-USDT",
price: "64250.5",
quantity: "0.25",
});
if (!result.valid) console.log(result.errors);getSpotOrderConstraints(pair)returns tick, step, and minimum values, plus the decimal-string wire ceilingsmaxPricefor price,maxQtyBasefor base quantity,maxNotionalQuotefor quote amount, andmaxQuoteSlippagefor quote-denominated slippage.validateSpotOrderDecimalInput(input)returns{ valid, errors }, a structured list you can render inline in a form.assertSpotOrderDecimalInput(input)throws aCatalogValidationFailedErroron the first violation instead of returning errors.
Use these validators when you need pre-submit feedback. orders.create and orders.modify still
perform strict shape and decimal-scale conversion, but do not automatically validate current tick,
step, or minimum rules. The validator checks parsing, tick, step, and minimum rules. It does not
enforce the four maxima. Those maxima are protobuf wire-format ceilings, not exchange limits.
Order and trigger methods throw CatalogConversionError before network I/O when a decimal exceeds
the corresponding ceiling. The backend is authoritative because catalog and balance state can
change.
catalog.zipper
Use environment.chain.id for the Polyester chain ID. Zipper configuration and catalog
snapshots do not expose polyesterChainId.
Deposit and withdraw chains, unified assets, and the per-chain routes that back deposits and withdrawals.
const chains = client.catalog.zipper.listChains();
const eth = client.catalog.zipper.requireAssetBySymbol("ETH");
const route = client.catalog.zipper.requireAssetChain("ETH", "ethereum-sepolia");Lookups: listChains(), requireChain(key), requireChainByCode(code), requireChainById(id), requireChainIdByCode(code), requireAssetBySymbol(sym), requireAssetChain(asset, chain), and requireAssetChainByZippedAssetId(id).
Unknown assets never crash
Events that reference an asset the catalog does not know resolve to a sentinel "unknown asset"
rather than throwing mid-stream. Detect them with the isUnknownLedgerAsset and isUnknownZipperAsset guards exported from @polyester/sdk/catalogs.
Owning the snapshot
By default each client fetches and holds its own snapshot. Three config options change that, all covered in Client configuration:
catalogSnapshotseeds a client with a snapshot you already have (typically from SSR: the server callscatalog.snapshot(), bakes it into the page, and the browser client starts warm).catalogCellbacks the snapshot with external, optionally reactive storage you control.cataloginjects a fully managedClientCataloginstance (mutually exclusive with the other two).
The @polyester/sdk/catalogs subpath exports the builders and reader types for these patterns: createPolyesterCatalog, buildCatalogSnapshot, createCatalogSnapshotReader, patchZipperCatalogSupply, the unknown-asset guards, and the reader and config types.
Related
- Catalog & precision for the design.
- Orders and the Trading guide for order validation in context.
- Errors for the catalog error classes.