@somnia-chain/markets-sdk


@somnia-chain/markets-sdk / index / SomniaMarkets

Class: SomniaMarkets

Defined in: packages/sdk/src/unified/exchange.ts:325

Constructors

Constructor

new SomniaMarkets(config): SomniaMarkets

Defined in: packages/sdk/src/unified/exchange.ts:430

Create one owner for Markets reads, watches, and optional signing. Reconciliation is off unless its positive block interval and bounded depth are supplied.

Parameters

config

SomniaMarketsConfig

Returns

SomniaMarkets

Throws

InvalidInputError If reconciliation bounds are invalid.

Throws

NotConfiguredError If the indexer endpoint is absent.

Properties

client

readonly client: SomniaMarketsClientWithObservations

Defined in: packages/sdk/src/unified/exchange.ts:327

The native engine — bigint-exact, address-keyed. The escape hatch.


markets

markets: Record<string, UnifiedMarket> = {}

Defined in: packages/sdk/src/unified/exchange.ts:329

Unified markets keyed by MARKET symbol (populated by loadMarkets).


symbols

symbols: string[] = []

Defined in: packages/sdk/src/unified/exchange.ts:331

All market symbols (populated by loadMarkets).


has

readonly has: object

Defined in: packages/sdk/src/unified/exchange.ts:338

Capability map — which unified verbs this venue supports (the ccxt exchange.has convention, for capability-probing bot code). Every listed verb is implemented here, so every flag is true.

fetchMarkets

readonly fetchMarkets: true = true

SomniaMarkets.fetchMarkets

fetchOrderBook

readonly fetchOrderBook: true = true

SomniaMarkets.fetchOrderBook

fetchTrades

readonly fetchTrades: true = true

SomniaMarkets.fetchTrades

fetchOHLCV

readonly fetchOHLCV: true = true

SomniaMarkets.fetchOHLCV

fetchBalance

readonly fetchBalance: true = true

SomniaMarkets.fetchBalance

fetchOpenOrders

readonly fetchOpenOrders: true = true

SomniaMarkets.fetchOpenOrders

getOrderHistoryPage

readonly getOrderHistoryPage: true = true

SomniaMarkets.getOrderHistoryPage

getOrdersPage

readonly getOrdersPage: true = true

SomniaMarkets.getOrdersPage

fetchMyTrades

readonly fetchMyTrades: true = true

SomniaMarkets.fetchMyTrades

fetchStatus

readonly fetchStatus: true = true

SomniaMarkets.fetchStatus

createOrder

readonly createOrder: true = true

SomniaMarkets.createOrder

cancelOrder

readonly cancelOrder: true = true

SomniaMarkets.cancelOrder

watchOrderBook

readonly watchOrderBook: true = true

SomniaMarkets.watchOrderBook

watchTrades

readonly watchTrades: true = true

SomniaMarkets.watchTrades

watchOrders

readonly watchOrders: true = true

SomniaMarkets.watchOrders

watchMyTrades

readonly watchMyTrades: true = true

SomniaMarkets.watchMyTrades

fetchPositions

readonly fetchPositions: true = true

SomniaMarkets.fetchPositions

fetchFundingRate

readonly fetchFundingRate: true = true

SomniaMarkets.fetchFundingRate

fetchFundingRateHistory

readonly fetchFundingRateHistory: true = true

SomniaMarkets.fetchFundingRateHistory — the key did not previously exist in this map, so it had to be ADDED rather than flipped.

watchPrice

readonly watchPrice: true = true

SomniaMarkets.watchPrice

fetchPrice

readonly fetchPrice: true = true

SomniaMarkets.fetchPrice

fetchPriceOHLCV

readonly fetchPriceOHLCV: true = true

SomniaMarkets.fetchPriceOHLCV

Accessors

trader

Get Signature

get trader(): Trader

Defined in: packages/sdk/src/unified/exchange.ts:448

The raw write tier bound to this exchange's signer — bigint-exact placeOrder/mintSet/faucet/… for anything the unified verbs don't cover.

Gotchas

Built lazily; throws if no signer was configured.

Returns

Trader


walletAddress

Get Signature

get walletAddress(): `0x${string}` | undefined

Defined in: packages/sdk/src/unified/exchange.ts:471

The authenticated wallet address, if a signer was configured.

Returns

`0x${string}` | undefined


perpDiscoveryError

Get Signature

get perpDiscoveryError(): SomniaMarketsError | null

Defined in: packages/sdk/src/unified/exchange.ts:535

Why chain-tier perp discovery did not run on the last loadMarkets, or null when it ran (or was never applicable).

loadMarkets() contains a discovery failure rather than throwing, because it is the implicit prerequisite of nearly every symbol-based verb and a chain failure must not take the SPOT and OUTCOME market lists down with it. This is where that contained failure is reported, with the underlying error preserved in cause.

When to use

Check it after loadMarkets() whenever a SHORT perp list would be worse than an error — a market page, an order router, anything that would otherwise present "this market does not exist". Then either tell the user the list is incomplete, or retry with loadMarkets(true): a bare loadMarkets() early-returns once any market is cached, so it never re-runs discovery and this value would stay stale.

Gotchas

  • Do NOT infer this from perpStatus being absent. That works only when the indexer already carries a perp row to inspect; with a configured factory and an indexer carrying none, a failed discovery yields an EMPTY perp list with no market to check. This accessor answers in both cases.
  • null does not mean the venue has perps. A chain with no perps plane deployed (local anvil) never attempts discovery and reports null too.

Example (Refusing to show a possibly-short perp list)

ts
await exchange.loadMarkets();
if (exchange.perpDiscoveryError) {
  // A bare loadMarkets() would early-return the cached registry and never retry.
  await exchange.loadMarkets(true);
}
if (exchange.perpDiscoveryError) {
  const partial = exchange.perpDiscoveryPartialFailure;
  throw new Error(
    partial
      ? `perp list incomplete: ${partial.failed} of ${partial.total} markets unread`
      : "perp discovery failed; the perp list may be short",
  );
}
Returns

SomniaMarketsError | null


perpDiscoveryPartialFailure

Get Signature

get perpDiscoveryPartialFailure(): { failed: number; total: number; } | null

Defined in: packages/sdk/src/unified/exchange.ts:548

How much of the chain-only perp set was lost when discovery PARTIALLY failed, or null when it did not.

Typed counts rather than prose in a message, so a consumer can decide on them — { failed: 1, total: 4 } reads as "three of four chain-only markets are listed". Always null when perpDiscoveryError is null, and also null when discovery failed outright rather than partly (nothing was read, so there is no ratio).

Returns

{ failed: number; total: number; } | null

Methods

setSigner()

setSigner(signer): void

Defined in: packages/sdk/src/unified/exchange.ts:461

Bind (or replace) the exchange's signer after construction. Browser apps construct the exchange at boot for public reads, then call this when the user's wallet connects — and again with {} on disconnect, which returns the exchange to unauthenticated reads. Replaces the trader every authenticated verb and walletAddress resolve against; live watches and market data are unaffected.

Parameters

signer

Pick<TraderConfig, "privateKey" | "account" | "walletClient">

Returns

void


loadMarkets()

loadMarkets(reload?): Promise<Record<string, UnifiedMarket>>

Defined in: packages/sdk/src/unified/exchange.ts:579

Load (or reload) the market registry: every market as a unified, symbol-keyed market object. Call once before anything symbol-based.

Perp markets come from two sources. The indexer's rows are unioned with the PerpPoolFactory's, because the indexer's perp set is a curated manifest and a pool deployed after it was written is live on chain and absent there. A market only the chain knows carries UnifiedMarket.indexed false; read that field's docs before touching anything history-derived on it.

A chain failure is contained, not thrown. This method is the implicit prerequisite of nearly every symbol-based verb, so letting a discovery failure out would take the SPOT and OUTCOME lists down with it — lists that need no chain read at all. The indexer read still throws: its failure means there is no registry to return. A contained discovery failure is reported by perpDiscoveryError, and that includes the PARTIAL case where some chain-only markets were read and others were not.

Gotchas

  • Early-returns the cached registry unless reload is true, so a retry after a discovery failure must pass true.
  • Concurrent cold reads share discovery. Each explicit reload waits for prior discovery, then re-reads independently.

Details

  • reload: Re-read everything, including the live tradeability gates and the binary pools' grids.

Parameters

reload?

boolean = false

Returns

Promise<Record<string, UnifiedMarket>>


market()

market(ref): Tradable

Defined in: packages/sdk/src/unified/exchange.ts:960

Resolve any handle (symbol, tradable symbol, pool/market address, market id) to its tradable. Requires loadMarkets().

Parameters

ref

string

Returns

Tradable


priceToPrecision()

priceToPrecision(ref, price): number

Defined in: packages/sdk/src/unified/exchange.ts:980

Snap a price to the market's tick grid (rounds down; binary prices are also clamped inside (0, 1)).

When to use

Use before createOrder with computed prices.

Spot/perp ticks come from the market row; binary ticks come from the pool, read once by loadMarkets — so a pool recycled mid-session keeps the grid captured at load time until loadMarkets(true) refreshes it.

Gotchas

  • Throws InvalidInputError if the market is binary and its pool's parameters could not be read — quantizing against a guessed grid is what produced off-tick rejections, so this fails loudly instead.

Parameters

ref

string

price

number

Returns

number


amountToPrecision()

amountToPrecision(ref, amount): number

Defined in: packages/sdk/src/unified/exchange.ts:1001

Snap an amount to the market's lot grid (rounds down).

Spot/perp lots come from the market row; binary lots come from the pool, read once by loadMarkets — so a pool recycled mid-session keeps the grid captured at load time until loadMarkets(true) refreshes it.

Gotchas

  • Throws InvalidInputError if the market is binary and its pool's parameters could not be read. Previously such a market fell back to a one-whole-token lot, silently flooring every sub-token amount to 0.

Parameters

ref

string

amount

number

Returns

number


fetchMarkets()

fetchMarkets(): Promise<UnifiedMarket[]>

Defined in: packages/sdk/src/unified/exchange.ts:1018

Every market as an array — loadMarkets (called if needed), minus the symbol keying.

When to use

Use as the ccxt-shaped sibling for list-style consumers.

Returns

Promise<UnifiedMarket[]>


fetchOrderBook()

fetchOrderBook(ref, options?): Promise<UnifiedOrderBook>

Defined in: packages/sdk/src/unified/exchange.ts:1158

One-shot book read from the contract (head-fresh; no watch needed).

When to use

Use when one book snapshot is enough. For a continuously-current zero-round-trip book, use watchOrderBook. Both sides use one selected head, or options.blockNumber. Empty books keep that pin. A numeric second argument remains the level limit.

Parameters

ref

string

options?

number | FetchOrderBookOptions

Returns

Promise<UnifiedOrderBook>

Throws

InvalidInputError If the symbol, depth, or pin is invalid.

Throws

NotConfiguredError If the owner has no chain endpoint.

Throws

RpcError If the head selection or either read fails.

Throws

ContractRevertError If the contract rejects a read.


fetchTrades()

fetchTrades(ref, since?, limit?): Promise<UnifiedTrade[]>

Defined in: packages/sdk/src/unified/exchange.ts:1184

Recent public trades with authoritative block and log positions (indexer, newest first).

Parameters

ref

string

since?

number

limit?

number = 50

Returns

Promise<UnifiedTrade[]>

Throws

InvalidInputError If the symbol or source position is invalid.

Throws

IndexerError If the indexer read fails.


fetchOHLCV()

fetchOHLCV(ref, timeframe?, since?, limit?): Promise<UnifiedOHLCV[]>

Defined in: packages/sdk/src/unified/exchange.ts:1230

OHLCV candles (indexer), oldest first as [ms,o,h,l,c,vol] rows. Timeframes: 1m 5m 15m 1h 4h 1d.

Example (Reading candles)

The last 24 hourly candles, destructured per row.

ts
const candles = await exchange.fetchOHLCV("SOMI/USDC", "1h", undefined, 24);
for (const [ts, open, high, low, close, volume] of candles) {
  console.log(new Date(ts).toISOString(), open, high, low, close, volume);
}

Parameters

ref

string

timeframe?

string = "5m"

since?

number

limit?

number = 500

Returns

Promise<UnifiedOHLCV[]>


fetchTicker()

fetchTicker(ref): Promise<UnifiedTicker>

Defined in: packages/sdk/src/unified/exchange.ts:1262

Rolling 24h ticker (indexer): OHLC + base/quote volume folded from the hourly candles, last from the freshest fill. NO-outcome tradables view prices through the 1−p lens like every other read.

Example (Reading a ticker)

Drive a price strip off one call.

ts
const tk = await exchange.fetchTicker("SOMI/USDC");
console.log(tk.last, tk.percentage, tk.baseVolume);

Parameters

ref

string

Returns

Promise<UnifiedTicker>


fetchBalance()

fetchBalance(): Promise<UnifiedBalances>

Defined in: packages/sdk/src/unified/exchange.ts:1369

Wallet balances for every currency the loaded markets use (+ native).

Gotchas

free === total: funds escrowed in resting orders live in the pools, not the wallet, so they simply don't appear here.

  • Throws SignerRequiredError - balances are per-account, so this needs a signer (or an account) even though it only reads.
  • Throws IndexerError - loadMarkets() or the outcome-holdings read needed the indexer and it was unreachable. Distinct from an empty result: no balances is {}, not a throw.
  • Throws RpcError - a chain balance read did not complete. A failed read is never reported as a zero balance or a missing key.
  • Throws ContractRevertError - a token's balanceOf reverted.

Example (Reading balances)

ERC-20s key by currency code; binary outcome holdings key by TRADABLE symbol.

ts
const bal = await exchange.fetchBalance();
console.log(bal.USDC?.total);                              // collateral in the wallet
console.log(bal["BTC-95000-31DEC26/USDC#YES"]?.total);     // YES shares held

Returns

Promise<UnifiedBalances>


fetchOpenOrders()

fetchOpenOrders(ref?, limit?): Promise<UnifiedOrder[]>

Defined in: packages/sdk/src/unified/exchange.ts:1433

Open orders (indexer view).

When to use

Use for an occasional snapshot; a trading loop should prefer watchOrders.

Details

limit is applied BY THE QUERY, per venue — not to the merged result. An unscoped call reads all three venues, so it can return up to 3 × limit rows; a ref-scoped call reads only that venue. The default is 200 per venue.

  • ref: Restrict to one tradable (symbol or address). Omit for all.
  • limit: Max orders PER VENUE the query returns (default 200).

Gotchas

The indexer view lags the chain slightly.

On SPOT the same limit also bounds the PENDING STOP ORDERS the underlying portfolio read returns — one query variable caps both sets. That list is not part of this verb's result, so the coupling is invisible here, but a caller reading client.getSpotPortfolio directly with a small ordersLimit will see a correspondingly short pendingStopOrders.

Parameters

ref?

string

limit?

number

Returns

Promise<UnifiedOrder[]>


fetchOrders()

fetchOrders(ref?, since?, limit?, params?): Promise<UnifiedOrder[]>

Defined in: packages/sdk/src/unified/exchange.ts:1470

The wallet's orders across every lifecycle status (indexer), newest first — the history counterpart to fetchOpenOrders. Scope to one tradable with ref; page with limit/params.offset, both forwarded to the query as a true offset window over one ordered set. (Its siblings page differently: fetchMyTrades pages a fill tape to satisfy limit, and fetchOpenOrders applies its limit per venue.)

Example (Reading order history)

The last 50 orders on one book, whatever became of them.

ts
const orders = await exchange.fetchOrders("SOMI/USDC", undefined, 50);
for (const o of orders) console.log(o.status, o.side, o.amount, o.txHash);

Parameters

ref?

string

since?

number

limit?

number = 100

params?
offset?

number

Returns

Promise<UnifiedOrder[]>


getOrderHistoryPage()

getOrderHistoryPage(options?): Promise<UnifiedOrdersPage>

Defined in: packages/sdk/src/unified/exchange.ts:1528

Read one raw-row page of the authenticated owner's indexed order history.

Use this method to traverse indexed order records with immutable order identities. The records and offset window are mutable. They are not an immutable lifecycle log or a current Open Orders snapshot. The default limit is 100 and the default offset is 0. Continuation depends on raw rows, so a full page can have no display orders and still continue. The result is partial because changes can make pages skip or repeat rows. Exhaustion does not prove complete account state.

The status option is an indexed history filter. In particular, status: "Open" cannot establish completeness, removals, continuity, or action freshness. Use a separately supported current-state flow when the caller needs the SDK's current open-order view.

Example (Following order-history page offsets)

ts
let offset = 0;
while (true) {
  const page = await exchange.getOrderHistoryPage({ limit: 100, offset });
  for (const order of page.orders) console.log(order.identity, order.status);
  if (page.nextOffset === null) break;
  offset = page.nextOffset;
}

Parameters

options?

SomniaMarketsGetOrdersPageOptions = {}

Returns

Promise<UnifiedOrdersPage>

Throws

InvalidInputError If the ref, status, limit, or offset is invalid.

Throws

SignerRequiredError If no account is configured.

Throws

IndexerError If the indexer read or producer-row conversion fails.


getOrdersPage()

getOrdersPage(options?): Promise<UnifiedOrdersPage>

Defined in: packages/sdk/src/unified/exchange.ts:1542

Read one raw-row page of the authenticated owner's indexed order history.

This compatibility name has the same result, pagination, and errors as SomniaMarkets.getOrderHistoryPage.

Parameters

options?

SomniaMarketsGetOrdersPageOptions = {}

Returns

Promise<UnifiedOrdersPage>

Throws

InvalidInputError If the ref, status, limit, or offset is invalid.

Throws

SignerRequiredError If no account is configured.

Throws

IndexerError If the indexer read or producer-row conversion fails.


fetchPortfolioAnalytics()

fetchPortfolioAnalytics(timeframe, params?): Promise<PortfolioAnalytics>

Defined in: packages/sdk/src/unified/exchange.ts:1744

The wallet's portfolio metrics plane over a timeframe: a holdings curve, an equity curve, per-bucket PnL, money-weighted return, volume, and fees saved versus a comparison taker rate. Computed client-side from the wallet's indexed fills (avg-cost basis) marked to candle closes — no server aggregate involved.

The two curves measure different things. holdings is what the traded book is worth at each sample, so it is a level. equity is the window's cumulative realized and unrealized PnL, so it is a change. Neither counts a token that arrived without a fill: read balances from the chain for what the wallet itself holds. Read HoldingsPoint before presenting the level — it states where its sign and its completeness end.

SPOT-scoped today: binary outcomes settle rather than mark, and the perp account plane (funding, margin) joins the fold as new event kinds when perp analytics land. Fills are paged to exhaustion — truncating would drop the OLDEST fills and silently corrupt the carried-in cost basis, not just undercount volume. Fills whose taker direction the indexer has not resolved (takerIsBid null), or where the wallet's role (maker vs taker) is unknowable, are skipped rather than guessed.

The money-weighted return needs to know what capital the wallet put in. Fills alone cannot say — capital that never passed through a trade is invisible to them — so without funding the capital base is a trades-only proxy that overstates the return for a wallet trading a small part of its balance. Pass funding to measure against real external capital, and read mwrr.capitalBasis to see which definition applied.

Example (Measuring portfolio performance)

ts
const p = await exchange.fetchPortfolioAnalytics("7d");
console.log(p.pnl.totalUsd, p.mwrr.return, p.equity.length);

Parameters

timeframe

PortfolioTimeframe

params?
sessionSince?

number

cexRateBps?

number

funding?

readonly PortfolioFundingEvent[]

External capital movements (deposits/withdrawals) for the wallet, USD valued at event time. When any of them bears on the window they define the MWRR capital base (mwrr.capitalBasis: "funding") in place of the trades-only proxy. They never affect PnL or volume. Venue fills are not funding — see PortfolioFundingEvent for the sourcing rules.

Returns

Promise<PortfolioAnalytics>


fetchMyTrades()

fetchMyTrades(ref?, since?, limit?): Promise<UnifiedTrade[]>

Defined in: packages/sdk/src/unified/exchange.ts:1883

My historical trades, newest-first across every venue.

Details

Reads the unified fill tape (getUserFills), so the scope, the window and the limit are applied by the INDEXER rather than to an already-truncated page. This is what makes a narrow question answerable: a ref-scoped call returns that market's fills however old they are, where a per-venue read would have capped at its newest 50 across all markets first and left nothing to filter.

limit counts rows YOU receive. Fills whose pool is not in the loaded registry are unresolvable and are skipped, so the read pages until it has limit resolvable rows or the tape runs out — asking the query for exactly limit would under-deliver by however many it then dropped.

  • ref: Restrict to one tradable (symbol or address). Omit for all.
  • since: Lower time bound, milliseconds — same clock as UnifiedTrade.timestamp, so a value read off a previous row can be passed straight back. Converted to the indexer's unix seconds internally.
  • limit: Max rows to return (default 50).

Parameters

ref?

string

since?

number

limit?

number = 50

Returns

Promise<UnifiedTrade[]>

Throws

InvalidInputError If the symbol or source position is invalid.

Throws

IndexerError If the indexer read fails.

Throws

SignerRequiredError If no account is configured.


fetchStatus()

fetchStatus(): Promise<ExchangeStatus>

Defined in: packages/sdk/src/unified/exchange.ts:1981

Read the local chain-tail status without network requests. "ok" means no watches are held or the tail is connected. "connecting" means a watch is awaiting its first head. "error" means a previously live socket was lost or the tail retains an operational failure.

Returns

Promise<ExchangeStatus>


fetchDataStatus()

fetchDataStatus(): Promise<ExchangeDataStatus>

Defined in: packages/sdk/src/unified/exchange.ts:2001

Observe independent indexer freshness and per-asset price health once. No polling starts. The top-level verdict describes only the local chain tail. Use fetchStatus for a local-only snapshot.

Returns

Promise<ExchangeDataStatus>

Throws

Configured chain or metadata arguments are invalid.

Throws

Indexer metadata could not be read.

Throws

The owner has no chain transport.

Throws

Independent chain measurement failed.


watchOrderBook()

watchOrderBook(ref, limit?): Promise<UnifiedOrderBook>

Defined in: packages/sdk/src/unified/exchange.ts:2097

Streaming book off the local store: zero round-trips, current to the last block; each await resolves on the next book change.

Example (Watching the order book)

A quoting loop: wake on every book change, read the touch.

ts
while (true) {
  const book = await exchange.watchOrderBook("SOMI/USDC", 5);
  const [bestBid] = book.bids[0] ?? [];
  const [bestAsk] = book.asks[0] ?? [];
  console.log(`bid ${bestBid} / ask ${bestAsk}`);
}

Block provenance comes from the native owner. Background failures appear in tail status.

Parameters

ref

string

limit?

number = 10

Returns

Promise<UnifiedOrderBook>

Throws

InvalidInputError If the symbol or source position is invalid.

Throws

NotConfiguredError If the owner has no chain endpoint.

Throws

IndexerError If snapshot initialization fails.

Throws

RpcError If chain catch-up fails.

Throws

ContractRevertError If a required contract read reverts.


watchTrades()

watchTrades(ref, limit?): Promise<UnifiedTrade[]>

Defined in: packages/sdk/src/unified/exchange.ts:2136

Streaming public trades (the live tape), newest first.

Example (Watching trades)

Print each fill as it lands ([0] is always the latest).

ts
while (true) {
  const [latest] = await exchange.watchTrades("SOMI/USDC", 1);
  if (latest) console.log(`${latest.side ?? "?"} ${latest.amount} @ ${latest.price}`);
}

Block provenance comes from the native owner. Background failures appear in tail status.

Parameters

ref

string

limit?

number = 50

Returns

Promise<UnifiedTrade[]>

Throws

InvalidInputError If the symbol or source position is invalid.

Throws

NotConfiguredError If the owner has no chain endpoint.

Throws

IndexerError If snapshot initialization fails.

Throws

RpcError If chain catch-up fails.

Throws

ContractRevertError If a required contract read reverts.


watchOrders()

watchOrders(ref, limit?): Promise<UnifiedOrder[]>

Defined in: packages/sdk/src/unified/exchange.ts:2191

Streaming view of MY orders on this tradable (authenticated).

When to use

Use to learn that a resting order filled: its status flips to "closed".

Example (Waiting for an order)

Place a limit order, then block until it fully fills (or dies).

ts
const placed = await exchange.createOrder(symbol, "limit", "buy", 10, 0.62);
while (placed.status === "open") {
  const orders = await exchange.watchOrders(symbol); // resolves on the next change
  const mine = orders.find((o) => o.id === placed.id);
  if (!mine || mine.status !== "open") break; // filled, canceled, or expired
}

Parameters

ref

string

limit?

number = 100

Returns

Promise<UnifiedOrder[]>


watchMyTrades()

watchMyTrades(ref, limit?): Promise<UnifiedTrade[]>

Defined in: packages/sdk/src/unified/exchange.ts:2239

Streaming view of MY fills on this tradable (authenticated). Block provenance comes from the native owner. Background failures appear in tail status.

Parameters

ref

string

limit?

number = 50

Returns

Promise<UnifiedTrade[]>

Throws

InvalidInputError If the symbol or source position is invalid.

Throws

NotConfiguredError If the owner has no chain endpoint.

Throws

IndexerError If snapshot initialization fails.

Throws

RpcError If chain catch-up fails.

Throws

ContractRevertError If a required contract read reverts.

Throws

SignerRequiredError If no account is configured.


watchPrice()

watchPrice(asset): Promise<UnifiedPrice>

Defined in: packages/sdk/src/unified/exchange.ts:2306

Streaming price off the local price store: zero round-trips, current to the last pushed tick; each await resolves on the next price change.

Details

First call hydrates the ref-counted feed watch.

Gotchas

Requires config.priceFeed to be set.

Parameters

asset

string

Returns

Promise<UnifiedPrice>


fetchPrice()

fetchPrice(asset): Promise<UnifiedPrice | null>

Defined in: packages/sdk/src/unified/exchange.ts:2328

One-shot current price (indexer HTTP read; no watch needed), or null if the feed has no observations yet.

Parameters

asset

string

Returns

Promise<UnifiedPrice | null>


fetchPriceOHLCV()

fetchPriceOHLCV(asset, timeframe?, since?, limit?): Promise<UnifiedOHLCV[]>

Defined in: packages/sdk/src/unified/exchange.ts:2344

OHLC price candles (EMA oracle), oldest first as [ms,o,h,l,c,vol] rows.

Details

Timeframes: 1m 1h 1d (aliases for the feed's M1/H1/D1).

Gotchas

vol is the oracle update count for the bucket (NOT trade volume).

Parameters

asset

string

timeframe?

string = "1m"

since?

number

limit?

number = 500

Returns

Promise<UnifiedOHLCV[]>


createOrder()

createOrder(ref, type, side, amount, price?, params?): Promise<UnifiedOrder>

Defined in: packages/sdk/src/unified/exchange.ts:2411

Place an order.

Details

Works identically for every market kind: the tradable symbol carries the outcome, side is plain buy/sell, prices and amounts are human units in the tradable's own terms. type: "market" computes a crossing limit from the best opposite level ± params.slippage (default 1%) and sends it IOC. Resolves once mined, with fills decoded from the same round-trip.

Gotchas

A NO price is the NO probability — the YES-terms complement is handled internally.

The price and quantity are ALIGNED to the market's tick and lot grids before they are sent, because the pool rejects an off-grid value outright. Alignment never moves a value against you: a buy price rounds down, a sell price rounds up, and a quantity always rounds down, so the order is never larger or worse priced than you asked for. The returned UnifiedOrder carries what was actually placed, which may differ from the arguments by up to one tick or lot — read price and amount back from it rather than assuming your inputs. Pre-aligning with priceToPrecision / amountToPrecision makes this a no-op, since aligning an aligned value changes nothing.

Note priceToPrecision always rounds DOWN, for either side; this path is side-aware instead, so for a sell the two can differ by one tick.

A quantity below one whole lot throws InvalidInputError rather than silently placing a zero-quantity order.

  • Throws SignerRequiredError - the exchange was built without a privateKey / account / walletClient.
  • Throws InvalidInputError - unknown symbol (call loadMarkets() first), a "limit" order with no price, or a "market" order whose opposite book side is empty so no crossing price exists.
  • Throws ContractRevertError - the chain rejected the order. Branch on errorName for the protocol's own reason (e.g. InsufficientBalance, ExpiredOrderMustBeCancelled).
  • Throws RpcError - the send never got an answer from the node.
  • Throws IndexerError - a symbol lookup needed the indexer and it was unreachable.

Example (Placing binary orders)

Rest a bid at 62% on YES, then take the NO book at market.

ts
const rested = await exchange.createOrder("BTC-95000-31DEC26/USDC#YES", "limit", "buy", 25, 0.62);
console.log(rested.status, rested.filled); // "open" 0 — or "closed" if it crossed

const taken = await exchange.createOrder("BTC-95000-31DEC26/USDC#NO", "market", "sell", 10, undefined, {
  slippage: 0.02, // accept up to 2% past the best bid
});

Parameters

ref

string

type

"limit" | "market"

side

"buy" | "sell"

amount

number

price?

number

params?

CreateOrderParams = {}

Returns

Promise<UnifiedOrder>


cancelOrder()

cancelOrder(id, ref): Promise<{ id: string; symbol: string; status: "canceled"; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:2600

Cancel a resting order by id (from createOrder / watchOrders).

Gotchas

Example (Cancelling an open order)

ts
const placed = await exchange.createOrder("SOMI/USDC", "limit", "buy", 10, 0.55);
if (placed.status === "open") await exchange.cancelOrder(placed.id, "SOMI/USDC");

Parameters

id

string

ref

string

Returns

Promise<{ id: string; symbol: string; status: "canceled"; info: unknown; }>


createStopOrder()

createStopOrder(ref, type, side, amount, triggerPrice, price?, params?): Promise<UnifiedStopOrder>

Defined in: packages/sdk/src/unified/exchange.ts:2671

Place a stop / take-profit order: rests OFF the book on the market's stop registry and fires as a market or limit order when the pool's mark price crosses triggerPrice. The trigger direction is inferred from which side of the current mark the trigger sits on; pass params.triggerDirection to pin it explicitly.

Gotchas

The trigger, limit price and quantity are aligned to the market's grids, and the trigger aligns AWAY from the mark so it cannot land on it (a trigger equal to the mark fires the instant it is armed). The limit price aligns like any order price — a buy down, a sell up — so it never becomes worse than stated.

Those two rules are independent, so a limit set exactly EQUAL to the trigger can end up one tick inside it: a buy stop at trigger 0.5004, limit 0.5004 on a 0.001 grid arms at 0.501 and rests a 0.500 bid, which may not fill. That is deliberate — pulling the limit up to meet the trigger would make you pay more than you asked. Set the limit a tick or two past the trigger when you want the triggered order to cross.

  • Throws SignerRequiredError - the exchange was built without a privateKey / account / walletClient.
  • Throws InvalidInputError - unknown symbol, a non-spot market, a market with no stop registry, a "limit" stop with no price, a sub-lot quantity, or no mark yet to infer the trigger direction from (pass params.triggerDirection).
  • Throws IndexerError - the pool's mark was needed (to infer the trigger direction, or to price a market stop's protective limit) and the indexer read did not complete. A limit stop with params.triggerDirection set needs no mark.
  • Throws ContractRevertError - the registry or the pool rejected the placement (or the operator grant / escrow approval that precedes it). Branch on errorName.
  • Throws RpcError - a send or a pre-placement chain read did not complete.

Example (Placing a stop order)

A stop-loss: sell 5 if the mark drops to 1.10.

ts
const stop = await exchange.createStopOrder("SOMI/USDC", "market", "sell", 5, 1.10);
// …later: await exchange.cancelStopOrder(stop.id, "SOMI/USDC");

Parameters

ref

string

type

"limit" | "market"

side

"buy" | "sell"

amount

number

triggerPrice

number

price?

number

params?
triggerDirection?

"above" | "below"

Returns

Promise<UnifiedStopOrder>


fetchOpenStopOrders()

fetchOpenStopOrders(ref?): Promise<UnifiedStopOrder[]>

Defined in: packages/sdk/src/unified/exchange.ts:2788

The wallet's pending (armed, untriggered) stop orders, newest first. Scope to one tradable with ref.

Parameters

ref?

string

Returns

Promise<UnifiedStopOrder[]>


cancelStopOrder()

cancelStopOrder(id, ref): Promise<{ id: string; symbol: string; status: "canceled"; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:2824

Cancel a pending stop order on its registry (refunds the keeper payment). id comes from fetchOpenStopOrders.

Parameters

id

string

ref

string

Returns

Promise<{ id: string; symbol: string; status: "canceled"; info: unknown; }>


fetchFundingRate()

fetchFundingRate(ref): Promise<UnifiedFundingRate>

Defined in: packages/sdk/src/unified/exchange.ts:2845

Live funding-rate + mark/index snapshot for a perp market (chain read).

Parameters

ref

string

Returns

Promise<UnifiedFundingRate>


fetchFundingRateHistory()

fetchFundingRateHistory(ref, since?, limit?): Promise<UnifiedFundingRate[]>

Defined in: packages/sdk/src/unified/exchange.ts:2915

Historical funding rates for a perp market, oldest first (ccxt-standard shape).

Reads the INDEXED series rather than the chain: only one funding value is readable on chain at a time. Positional (symbol, since, limit) follows the ccxt convention set by fetchOHLCV, unlike the object-options readers on the client.

fundingRate is normalized to a per-8h fraction using each row's own fundingWindowSec, so the series stays consistent across a parameter change. The raw indexed row is on info for anything more specific — including spanStart / spanEnd, which matter because a row's accrual reaches BACKWARDS from its timestamp and a lazily-settled one can cover hours.

since is a CURSOR, not just a window bound: passing it walks FORWARD from that point, so the ccxt pagination idiom terminates.

Details

  • ref: market symbol or pool address
  • since: unix MILLISECONDS (ccxt convention), inclusive; acts as a forward cursor
  • limit: max rows (default 100)

Example (Paging through funding history)

ts
let since = startOfHistory;
for (;;) {
  const page = await exchange.fetchFundingRateHistory("BTC/USDSO:USDSO", since, 100);
  if (page.length === 0) break;
  consume(page);
  since = page[page.length - 1].timestamp + 1;   // advances
}

Without the forward ordering this loop spins: the underlying read pages newest-first, so narrowing the window from below still returns the newest N and since never gets past the tail. Omitting since keeps the newest-first behaviour, which is what a "latest funding" read wants — fetchOHLCV has the same split.

Parameters

ref

string

since?

number

limit?

number

Returns

Promise<UnifiedFundingRate[]>


fetchPositions()

fetchPositions(refs?): Promise<UnifiedPosition[]>

Defined in: packages/sdk/src/unified/exchange.ts:2946

Open perp positions (authenticated; on-chain MarginBank reads). Pass symbols to scope; defaults to every loaded perp market.

Parameters

refs?

string[]

Returns

Promise<UnifiedPosition[]>


depositMargin()

depositMargin(ref, amount): Promise<{ hash: string; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:3011

Deposit collateral into the perp MarginBank (human quote units, e.g. USDso). One cross-margin balance covers every perp market.

Parameters

ref

string

amount

number

Returns

Promise<{ hash: string; info: unknown; }>


withdrawMargin()

withdrawMargin(ref, amount): Promise<{ hash: string; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:3030

Withdraw free collateral from the perp MarginBank (human quote units).

Parameters

ref

string

amount

number

Returns

Promise<{ hash: string; info: unknown; }>


mintSet()

mintSet(ref, amount): Promise<{ hash: string; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:3070

Mint complete sets: amount collateral → amount of EVERY outcome.

Example (Minting complete sets)

Mint 100 sets (100 USDC → 100 YES + 100 NO), then sell the side you don't want.

ts
await exchange.mintSet("BTC-95000-31DEC26/USDC", 100);
await exchange.createOrder("BTC-95000-31DEC26/USDC#NO", "limit", "sell", 100, 0.38);

Parameters

ref

string

amount

number

Returns

Promise<{ hash: string; info: unknown; }>


burnSet()

burnSet(ref, amount): Promise<{ hash: string; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:3085

Burn complete sets back to collateral.

Parameters

ref

string

amount

number

Returns

Promise<{ hash: string; info: unknown; }>


redeem()

redeem(ref, amount, options?): Promise<{ hash: string; info: unknown; }>

Defined in: packages/sdk/src/unified/exchange.ts:3141

Redeem outcome tokens for collateral after settlement. Settlement- extraction v2 routes by marketId. When the caller omits the leg, the SDK verifies the BinaryMarket state and reads its winner on-chain.

Example (Redeeming a winning position)

After resolution, redeem the winning side found in the balance map.

ts
const bal = await exchange.fetchBalance();
const winning = bal["BTC-95000-31DEC26/USDC#YES"]?.total ?? 0;
if (winning > 0) await exchange.redeem("BTC-95000-31DEC26/USDC", winning);

Gotchas

A VOIDED market has no winning outcome — both legs pay — so the auto-lookup cannot pick one and redeem throws. Pass { outcomeIdx } for the leg you hold, and call once per leg to claim both. An unresolved market also throws instead of guessing from an unfinalized payout vector.

Errors

  • Throws InvalidInputError for an unknown or non-binary market, an invalid amount, or an omitted leg when the on-chain market is unresolved, voided, or does not store an exact one-hot resolved payout vector.
  • Throws NotConfiguredError when the binary module address is not configured.
  • Throws SignerRequiredError when the exchange has no usable signer.
  • Throws RpcError when a market, approval, submission, or receipt request does not complete.
  • Throws ContractRevertError when a market read, operator approval, or redemption transaction is rejected by a contract.

Parameters

ref

string

Market symbol or id.

amount

number

Outcome-token amount to burn, in display units.

options?

RedeemOptions

Optional leg selection. Omit it on a resolved market to verify the terminal state and read the winner on-chain. A voided market requires options.outcomeIdx because both legs are redeemable and only the caller knows which one they hold.

Returns

Promise<{ hash: string; info: unknown; }>


close()

close(): Promise<void>

Defined in: packages/sdk/src/unified/exchange.ts:3192

Release every watch, channel, socket, and timer this exchange holds and stop the client's live machinery.

Details

This includes the chain WebSocket, so a Node process that has touched the chain exits on its own once this resolves. The instance stays usable for one-shot fetch calls: a later read reopens a connection, and a later close() releases that one too.

Gotchas

Exchanges configured with the same wsRpcUrl share one chain socket — the underlying viem transport caches sockets per URL and offers no way to opt out — so it closes only when the last of them closes.

Returns

Promise<void>