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 theSpotMarketrow). - Two sides.
isBid: truebuys base (escrows quote);falsesells base (escrows base — or sends native SOMI asmsg.valuewhenbaseIsNative). - Book constraints. Orders must respect the pool's
tickSize,lotSize, andminQuantity(all on theSpotMarketrow, kept live by the watch). - Mark price. Pools publish a smoothed
markPrice(streamed live viaMarkPriceUpdated) — it's what stop orders trigger on, distinct fromlastPrice(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.