@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
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: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
readonlyhas: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
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
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
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
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: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
reloadis true, so a retry after a discovery 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.
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
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.
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.
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
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: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.
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)
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)
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: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.
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).
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).
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
errorNamefor 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.
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
- 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
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 (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
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 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
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.
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.
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?
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>