{s}omniamarkets

Spot markets

A spot market is a plain base/quote order book on a SpotPool (e.g. SOMI/USDC) — same OrderBook core as the binary pools, so the live machinery is identical; only the semantics differ. This guide covers reading and trading spot; the shared client mechanics (watches, read tiers, signers) are in the engine guide.

The mental model

  • Prices are quote-per-base. Raw quote units per whole base token, scaled by the market's own quoteDecimals/baseDecimals (spot markets are NOT assumed 6dp — read the decimals off the SpotMarket row).
  • Two sides. isBid: true buys base (escrows quote); false sells base (escrows base — or sends native SOMI as msg.value when baseIsNative).
  • Book constraints. Orders must respect the pool's tickSize, lotSize, and minQuantity (all on the SpotMarket row, kept live by the watch).
  • Mark price. Pools publish a smoothed markPrice (streamed live via MarkPriceUpdated) — it's what stop orders trigger on, distinct from lastPrice (last fill).

Reading

Discover markets first (indexer tier) — listSpotMarkets returns the board and getSpotMarket resolves one by id; both yield the SpotMarket rows the live reads key off:

const markets = await client.listSpotMarkets({ limit: 50 }); // SpotMarket[]; filterable (base/quote/…)
const spot    = await client.getSpotMarket(id);              // SpotMarket | null
const watch = await client.watchMarket(spot.poolAddress);

const book = client.getLiveSpotOrderBook(spot.poolAddress, { depth: 11 }); // { bids, asks }, best first
const tape = client.getLiveFills(spot.poolAddress, { limit: 40 });
const live = client.getLiveMarketByPool(spot.poolAddress);      // markPrice, tick/lot, stats

React: useLiveSpotOrderBook(pool), useLiveFills(pool), useLiveMarketByPool(pool) — all auto-watch while mounted. History and wallet views come from the indexer tier: getCandles(pool, interval), getSpotPortfolio(account) (open orders + pending stops + trades), and getSpotStopOrders(account, { pool }). Holdings are plain balances — read them on-chain with getErc20Balance / getNativeBalance, not from the indexer.

Trading

const trader = client.createTrader({ privateKey });

// Rest a limit bid: buy 5 base at 1.25 quote.
const { orderId, fills } = await trader.placeSpotOrder({
  pool: spot.poolAddress,
  isBid: true,
  price: parseUnits("1.25", spot.quoteDecimals),
  quantity: parseUnits("5", spot.baseDecimals),
  baseDecimals: spot.baseDecimals,
  quoteToken: spot.quoteToken,
  baseToken: spot.baseToken,
  baseIsNative: spot.baseIsNative,
});

await trader.cancelOrder({ pool: spot.poolAddress, orderId }); // same core as binary

Escrow is approved automatically (quote on buys, base on non-native sells; native-base sells pay via msg.value instead). A market order is orderType: ORDER_TYPE.MARKET with a crossing price — take the live book's best opposite level ± slippage, tick-aligned, so it sweeps and the remainder cancels:

const best = client.getLiveSpotOrderBook(pool, { depth: 1 });   // zero RTT, last-block fresh
const crossing = (best.asks[0].price * 10100n) / 10000n; // +1% slippage bound

Stop orders

Spot pools with a stopRegistry support stop-loss / take-profit orders that rest OFF the book and fire when the mark price crosses the trigger:

await trader.placeSpotStopOrder({
  registry: spot.stopRegistry,
  pool: spot.poolAddress,
  isBid: false,                       // sell when the market drops…
  quantity: parseUnits("5", spot.baseDecimals),
  triggerPrice: parseUnits("1.10", spot.quoteDecimals),
  triggerOperator: 1,                 // 1 = LTE (mark ≤ trigger), 0 = GTE
  stopOrderType: 1,                   // 1 = MARKET at trigger, 0 = LIMIT (needs limitPrice)
  quoteToken: spot.quoteToken,
  baseToken: spot.baseToken,
  baseIsNative: spot.baseIsNative,
});

await trader.cancelStopOrder({ registry: spot.stopRegistry, orderId });

Under the hood the first stop order per account performs a one-time operator approval (so the registry may place the triggered order for you), funds the trigger gas with a small SOMI payment (msg.value, refunded on cancel), and ensures the pool can pull the escrow at trigger time — including pre-loading the pool vault for native-base sells. The SDK handles all of it; list pending stops with getSpotStopOrders(account, { pool }) and stream their market context via the watch.