@somnia-chain/markets-sdk / index / SomniaMarkets
Class: SomniaMarkets
Defined in: packages/sdk/src/unified/exchange.ts:473
Constructors
Constructor
new SomniaMarkets(
config):SomniaMarkets
Defined in: packages/sdk/src/unified/exchange.ts:590
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
Returns
SomniaMarkets
Throws
InvalidInputError If reconciliation bounds are invalid.
Throws
NotConfiguredError If the indexer endpoint is absent.
Properties
client
readonlyclient:SomniaMarketsClientWithObservations
Defined in: packages/sdk/src/unified/exchange.ts:480
The PRIMARY native engine — the first configured chain's — bigint-exact, address-keyed. The escape hatch for a single-chain exchange; on a multi-chain exchange reach the other chains' engines with SomniaMarkets.engineFor.
markets
markets:
Record<string,UnifiedMarket> ={}
Defined in: packages/sdk/src/unified/exchange.ts:484
Unified markets keyed by MARKET symbol (populated by loadMarkets).
symbols
symbols:
string[] =[]
Defined in: packages/sdk/src/unified/exchange.ts:486
All market symbols (populated by loadMarkets).
has
readonlyhas:object
Defined in: packages/sdk/src/unified/exchange.ts:493
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
readonlyfetchMarkets:true=true
fetchOrderBook
readonlyfetchOrderBook:true=true
fetchTrades
readonlyfetchTrades:true=true
fetchOHLCV
readonlyfetchOHLCV:true=true
fetchBalance
readonlyfetchBalance:true=true
fetchOpenOrders
readonlyfetchOpenOrders:true=true
getOrderHistoryPage
readonlygetOrderHistoryPage:true=true
SomniaMarkets.getOrderHistoryPage
getOrdersPage
readonlygetOrdersPage:true=true
fetchMyTrades
readonlyfetchMyTrades:true=true
fetchStatus
readonlyfetchStatus:true=true
createOrder
readonlycreateOrder:true=true
cancelOrder
readonlycancelOrder:true=true
watchOrderBook
readonlywatchOrderBook:true=true
watchTrades
readonlywatchTrades:true=true
watchOrders
readonlywatchOrders:true=true
watchMyTrades
readonlywatchMyTrades:true=true
fetchPositions
readonlyfetchPositions:true=true
fetchFundingRate
readonlyfetchFundingRate:true=true
SomniaMarkets.fetchFundingRate
fetchFundingRateHistory
readonlyfetchFundingRateHistory:true=true
SomniaMarkets.fetchFundingRateHistory — the key did not previously exist in this map, so it had to be ADDED rather than flipped.
watchPrice
readonlywatchPrice:true=true
fetchPrice
readonlyfetchPrice:true=true
fetchPriceOHLCV
readonlyfetchPriceOHLCV:true=true
Accessors
chainIds
Get Signature
get chainIds():
number[]
Defined in: packages/sdk/src/unified/exchange.ts:651
Numeric chain ids this exchange spans, in binding order (the first is the primary).
Returns
number[]
trader
Get Signature
get trader():
Trader
Defined in: packages/sdk/src/unified/exchange.ts:726
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
walletAddress
Get Signature
get walletAddress():
`0x${string}`|undefined
Defined in: packages/sdk/src/unified/exchange.ts:749
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:814
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
perpStatusbeing 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. nulldoes not mean the venue has perps. A chain with no perps plane deployed (local anvil) never attempts discovery and reportsnulltoo.
Example (Refusing to show a possibly-short perp list)
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:827
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
registryReadError
Get Signature
get registryReadError():
SomniaMarketsError|null
Defined in: packages/sdk/src/unified/exchange.ts:872
Why the indexer's market registry could not be read on the last
loadMarkets, or null when the read COMPLETED.
loadMarkets() contains this failure rather than throwing, for the same reason it
contains a perp discovery failure: it is the implicit prerequisite of nearly every
symbol-based verb, and an indexer outage must not take down the perp markets the
chain can enumerate on its own. This is where that contained failure is reported,
with the underlying error preserved in cause. In practice it is an
IndexerError carrying operation and detail.
When to use
Check it after loadMarkets() whenever an EMPTY market list would be worse than an
error. null together with an empty symbols is the unambiguous "this venue
genuinely has no markets"; non-null with an empty list means the registry could not
be read and the list says nothing about what exists. Retry with
loadMarkets(true): a bare loadMarkets() early-returns once any market is
cached, so it never re-runs the sweep and this value would stay stale.
Gotchas
- This is all-or-nothing at this layer. A registry sweep that succeeded but dropped unparseable rows is a different condition, reported by
listRegistryMarketsCheckedthroughRegistrySweep.dropped. - Cleared on every
loadMarkets()that actually runs, so it always describes the LAST load, never an older one.
Example (Telling an outage apart from an empty venue)
await exchange.loadMarkets();
if (exchange.registryReadError) {
// A bare loadMarkets() would early-return the cached registry and never retry.
await exchange.loadMarkets(true);
}
if (exchange.registryReadError) {
throw new Error("the market registry could not be read; the market list may be short");
}
if (exchange.symbols.length === 0) {
console.log("this venue has no markets");
}
Returns
SomniaMarketsError | null
accounts
Get Signature
get accounts():
ExchangeSmartAccounts
Defined in: packages/sdk/src/unified/exchange.ts:2842
The smart-accounts surface across this exchange's chains: derive the one
address, scan every covered chain, fan one signed authorization out into
per-chain creation calls, and build the multi-chain account the exchange
trades through. See ExchangeSmartAccounts; the one-chain surface
is client.accounts.
Returns
collateralReadErrors
Get Signature
get collateralReadErrors():
Readonly<Record<number,CollateralReadError>>
Defined in: packages/sdk/src/unified/exchange.ts:2860
Failed reads from the last completed collateral balance sweep, keyed by chain.
Available after createOrder checks or ensureFunds, including successful
bridge plans. Empty before the first sweep and after a complete successful read.
Target-chain failures throw and leave this snapshot unchanged. Concurrent
sweeps replace it in completion order; a shortfall error retains its own snapshot.
Returns
Readonly<Record<number, CollateralReadError>>
bridge
Get Signature
get bridge():
ExchangeBridge
Defined in: packages/sdk/src/unified/exchange.ts:2869
The bridge between this exchange's chains, through the account's executors: plan a transfer, or execute one and wait for it to land. See ExchangeBridge.
Returns
Methods
engineFor()
engineFor(
chainId):SomniaMarketsClientWithObservations
Defined in: packages/sdk/src/unified/exchange.ts:663
The native engine bound to one configured chain — bigint-exact, address-keyed. The escape hatch for that chain.
Gotchas
Throws NotConfiguredError for a chain this exchange has no binding for.
Parameters
chainId
number
Returns
SomniaMarketsClientWithObservations
setSigner()
setSigner(
signer):void
Defined in: packages/sdk/src/unified/exchange.ts:738
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
Returns
void
loadMarkets()
loadMarkets(
reload?):Promise<Record<string,UnifiedMarket>>
Defined in: packages/sdk/src/unified/exchange.ts:917
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 read failure is contained, not thrown. This method is the implicit
prerequisite of nearly every symbol-based verb, so letting one source's failure out
would take every other source down with it. Both sources report instead of
throwing: the indexer registry read through registryReadError, and perp
discovery through perpDiscoveryError — which also covers the PARTIAL case
where some chain-only markets were read and others were not. So an indexer outage
still returns the perp markets the chain can enumerate, and a chain outage still
returns the indexer's spot and outcome lists. A caller that treated a throw as
"the registry is unreadable" must now check registryReadError instead.
Discovery coverage per kind.
- Perp — chain-complete through the PerpPoolFactory, so it survives an empty or unreachable indexer. One caveat: with NO indexed perp row the factory is found only from a configured
addresses.perpPoolFactory, because the bootstrap route otherwise reads themarginBankoff an indexed row. Configure it for a venue whose indexer carries no perp rows. - Binary — indexer-only here. Live discovery of NEW binary markets does not need this method:
watchMarkets({ discover: true })adds them from the chain'sMarketCreatedevents. - Spot — indexer-only.
SpotPoolRegistryanswers "is this pool registered", so it cannot be enumerated from the chain.
Gotchas
- Early-returns the cached registry unless
reloadis true, so a retry after a contained read failure must passtrue. - 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.- Throws NotConfiguredError - the load needed a chain read (ERC-20 metadata for an unlabeled token, or a binary pool's grid) and the owner has neither a
wsRpcUrlnor a chain whoserpcUrlscarry a WebSocket endpoint. Thrown before any request goes out, so it means misconfiguration rather than an outage — an outage reaches registryReadError or perpDiscoveryError instead. - Throws InvariantError - the SDK's own reasoning was wrong (a market the symbol registry did not name). Report it as a defect; there is nothing for a caller to do about it.
The complete union is SomniaMarketsLoadMarketsError. Neither an unreachable indexer nor an unreadable chain is in it: both are contained and reported through the accessors named above.
Parameters
reload?
boolean = false
Returns
Promise<Record<string, UnifiedMarket>>
market()
market(
ref):Tradable
Defined in: packages/sdk/src/unified/exchange.ts:1361
Resolve any handle (symbol, tradable symbol, pool/market address, market id) to its tradable. Requires loadMarkets().
Parameters
ref
Returns
priceToPrecision()
priceToPrecision(
ref,price):number
Defined in: packages/sdk/src/unified/exchange.ts:1381
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
price
number
Returns
number
amountToPrecision()
amountToPrecision(
ref,amount):number
Defined in: packages/sdk/src/unified/exchange.ts:1402
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
amount
number
Returns
number
fetchMarkets()
fetchMarkets():
Promise<UnifiedMarket[]>
Defined in: packages/sdk/src/unified/exchange.ts:1419
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:1559
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
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:1587
Recent public trades with authoritative block and log positions (indexer, newest first).
Parameters
ref
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:1634
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.
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
timeframe?
string = "5m"
since?
number
limit?
number = 500
Returns
Promise<UnifiedOHLCV[]>
fetchTicker()
fetchTicker(
ref):Promise<UnifiedTicker>
Defined in: packages/sdk/src/unified/exchange.ts:1666
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.
const tk = await exchange.fetchTicker("SOMI/USDC");
console.log(tk.last, tk.percentage, tk.baseVolume);
Parameters
ref
Returns
Promise<UnifiedTicker>
fetchBalance()
fetchBalance():
Promise<UnifiedBalances>
Defined in: packages/sdk/src/unified/exchange.ts:1797
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 - the outcome-holdings read needed the indexer and it was unreachable. Distinct from an empty result: no balances is
{}, not a throw. TheloadMarkets()this calls first does NOT throw on an unreachable indexer; it reports through registryReadError, so a balance set can be short rather than absent. - 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
balanceOfreverted.
Example (Reading balances)
ERC-20s key by currency code; binary outcome holdings key by TRADABLE symbol.
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:1873
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?
limit?
number
Returns
Promise<UnifiedOrder[]>
fetchOrders()
fetchOrders(
ref?,since?,limit?,params?):Promise<UnifiedOrder[]>
Defined in: packages/sdk/src/unified/exchange.ts:1915
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.
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?
since?
number
limit?
number = 100
params?
offset?
number
Returns
Promise<UnifiedOrder[]>
getOrderHistoryPage()
getOrderHistoryPage(
options?):Promise<UnifiedOrdersPage>
Defined in: packages/sdk/src/unified/exchange.ts:1982
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)
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:1996
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:2215
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)
const p = await exchange.fetchPortfolioAnalytics("7d");
console.log(p.pnl.totalUsd, p.mwrr.return, p.equity.length);
Parameters
timeframe
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:2363
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?
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:2483
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:2512
Observe independent indexer freshness and per-asset price health once. No polling starts.
The top-level verdict describes only the chain tails. indexer is the PRIMARY
chain's freshness; indexerByChain carries every configured chain's. 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:2623
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.
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
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:2664
Streaming public trades (the live tape), newest first.
Example (Watching trades)
Print each fill as it lands ([0] is always the latest).
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
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:2721
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).
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
limit?
number = 100
Returns
Promise<UnifiedOrder[]>
watchMyTrades()
watchMyTrades(
ref,limit?):Promise<UnifiedTrade[]>
Defined in: packages/sdk/src/unified/exchange.ts:2771
Streaming view of MY fills on this tradable (authenticated). Block provenance comes from the native owner. Background failures appear in tail status.
Parameters
ref
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.
watchUser()
watchUser():
Promise<WatchHandle>
Defined in: packages/sdk/src/unified/exchange.ts:2819
Hydrate and hold the signer's order/fill history on EVERY configured
chain, behind one handle. Each chain's engine opens its own ref-counted
user watch; stopping the handle releases all of them. Activity then
arrives on the per-market watch* verbs with each event's chainId.
Gotchas
- Throws SignerRequiredError - no signer on this exchange.
- Throws RpcError - a chain's socket could not open; handles already opened on other chains are released before the error propagates.
Returns
Promise<WatchHandle>
ensureFunds()
ensureFunds(
params,options?):Promise<ExchangeBridgeResult|null>
Defined in: packages/sdk/src/unified/exchange.ts:2900
Make sure the account holds amount of a currency on one chain, bridging
the shortfall from the richest other configured chain when it does not.
The opt-in helper behind createOrder(…, { funding: "auto" }).
Details
code: the currency's code — the key fetchBalance uses ("USDso","STT", …). Addresses differ per chain; the code names the asset.chainId: the chain the funds must end up on.amount: raw units of the currency on that chain.
Resolves with null when nothing had to move, else the bridge result.
Other-chain read failures remain available through collateralReadErrors.
A shortfall error also retains them in readErrors and its aggregate cause.
Gotchas
- Throws CollateralOnWrongChainError - no other configured chain holds enough, or the currency has no bridge route.
- Throws RpcError - the bridge leg landed but delivery was not observed within the timeout.
Parameters
params
code
string
chainId
number
amount
bigint
options?
ExchangeBridgeTransferOptions = {}
Returns
Promise<ExchangeBridgeResult | null>
watchPrice()
watchPrice(
asset):Promise<UnifiedPrice>
Defined in: packages/sdk/src/unified/exchange.ts:3148
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, INCLUDING its quote: an unpinned
feed rejects a live watch with a NotConfiguredError naming
config.priceFeed.quote, because the tick tape's cursor is unique only
within one quote. The one-shot price reads still work unpinned.
Parameters
asset
string
Returns
Promise<UnifiedPrice>
fetchPrice()
fetchPrice(
asset):Promise<UnifiedPrice|null>
Defined in: packages/sdk/src/unified/exchange.ts:3170
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:3186
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:3252
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
errorNamefor the protocol's own reason (e.g.InsufficientBalance,ExpiredOrderMustBeCancelled). - Throws RpcError - the send never got an answer from the node.
Example (Placing binary orders)
Rest a bid at 62% on YES, then take the NO book at market.
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
type
"market" | "limit"
side
"buy" | "sell"
amount
number
price?
number
params?
CreateOrderParams = {}
Returns
Promise<UnifiedOrder>
cancelOrder()
cancelOrder(
id,ref?):Promise<{id:string;symbol:string;chainId:number;status:"canceled";info:unknown; }>
Defined in: packages/sdk/src/unified/exchange.ts:3448
Cancel a resting order by id (from createOrder / watchOrders).
Gotchas
- Throws SignerRequiredError - no signer on this exchange.
- Throws InvalidInputError - unknown symbol.
- Throws ContractRevertError - the cancel did not land;
errorNamesays why (an already-filled or already-canceled order reverts). - Throws RpcError - the send never got an answer from the node.
Example (Cancelling an open order)
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 | ChainOrderRef
ref?
Returns
Promise<{ id: string; symbol: string; chainId: number; status: "canceled"; info: unknown; }>
createStopOrder()
createStopOrder(
ref,type,side,amount,triggerPrice,price?,params?):Promise<UnifiedStopOrder>
Defined in: packages/sdk/src/unified/exchange.ts:3538
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 (passparams.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.triggerDirectionset 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.
const stop = await exchange.createStopOrder("SOMI/USDC", "market", "sell", 5, 1.10);
// …later: await exchange.cancelStopOrder(stop.id, "SOMI/USDC");
Parameters
ref
type
"market" | "limit"
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:3656
The wallet's pending (armed, untriggered) stop orders, newest first.
Scope to one tradable with ref.
Parameters
ref?
Returns
Promise<UnifiedStopOrder[]>
cancelStopOrder()
cancelStopOrder(
id,ref):Promise<{id:string;symbol:string;chainId:number;status:"canceled";info:unknown; }>
Defined in: packages/sdk/src/unified/exchange.ts:3699
Cancel a pending stop order on its registry (refunds the keeper
payment). id comes from fetchOpenStopOrders.
Parameters
id
string
ref
Returns
Promise<{ id: string; symbol: string; chainId: number; status: "canceled"; info: unknown; }>
fetchFundingRate()
fetchFundingRate(
ref):Promise<UnifiedFundingRate>
Defined in: packages/sdk/src/unified/exchange.ts:3722
Live funding-rate + mark/index snapshot for a perp market (chain read).
Parameters
ref
Returns
Promise<UnifiedFundingRate>
fetchFundingRateHistory()
fetchFundingRateHistory(
ref,since?,limit?):Promise<UnifiedFundingRate[]>
Defined in: packages/sdk/src/unified/exchange.ts:3793
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 addresssince: unix MILLISECONDS (ccxt convention), inclusive; acts as a forward cursorlimit: max rows (default 100)
Example (Paging through funding history)
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
since?
number
limit?
number
Returns
Promise<UnifiedFundingRate[]>
fetchPositions()
fetchPositions(
refs?):Promise<UnifiedPosition[]>
Defined in: packages/sdk/src/unified/exchange.ts:3825
Open perp positions (authenticated; on-chain MarginBank reads). Pass symbols to scope; defaults to every loaded perp market.
Parameters
refs?
Returns
Promise<UnifiedPosition[]>
depositMargin()
depositMargin(
ref,amount):Promise<{hash:string;info:unknown; }>
Defined in: packages/sdk/src/unified/exchange.ts:3892
Deposit collateral into the perp MarginBank (human quote units, e.g. USDso). One cross-margin balance covers every perp market.
Parameters
ref
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:3911
Withdraw free collateral from the perp MarginBank (human quote units).
Parameters
ref
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:3951
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.
await exchange.mintSet("BTC-95000-31DEC26/USDC", 100);
await exchange.createOrder("BTC-95000-31DEC26/USDC#NO", "limit", "sell", 100, 0.38);
Parameters
ref
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:3966
Burn complete sets back to collateral.
Parameters
ref
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:4022
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.
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
Market symbol or id.
amount
number
Outcome-token amount to burn, in display units.
options?
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:4073
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>