Solvers
The solver APIs are off-chain REST endpoints configured per chain in createConfig. @symmio/trading-core exposes them as framework-agnostic actions plus TanStack Query option factories.
Use these APIs when you need solver metadata before building a trade flow: market lists, leverage limits, and locked percentages for a market/leverage pair.
Protocol model — intent-based, no orderbook
SYMMIO is intent-based. The current lowcap version has no orderbook. Positions are opened peer-to-peer between two roles:
- partyA — the user. Sends an intent to open a position.
- partyB — a solver. Watches partyA intents and takes the opposite side, becoming the direct counterparty.
Every lowcap position is one partyA ↔ one partyB agreement. There is no shared book, no matching engine, no other users on the other side. The solver is the counterparty.
Implications when building on the SDK:
- No bid/ask depth to render. Prices come from off-chain feeds (see Price Service), not an on-chain book.
- Trade lifecycle is intent → solver acceptance → on-chain settlement. Surface intent state, not order-fill progress.
partyA/partyBin ABIs, events, and notifications always mean user / solver respectively.
Markets
getMarkets
Fetch tradable markets from the chain solver’s /contract-symbols endpoint.
On Enigma, prefer getSymbols. It reads the same catalogue from the /symbols service but
adds server-side filtering (name, asset, token, validity, per-side state) and pagination (limit / offset),
so you fetch only the rows you need. Keep getMarkets when you want one call that also covers Rasa — /symbols
is Enigma-only.
import { getMarkets } from "@symmio/trading-core";
// Omit `solverId` → the `Market` union; narrow on `kind` for solver-specific fields.
const markets = await getMarkets(config, {});
const btc = markets.find((market) => market.name === "BTCUSDT");
// Pass a literal solver kind → the exact per-solver market type.
const rasa = await getMarkets(config, { solverId: "rasa" }); // RasaMarket[]Parameters
| Name | Type | Default | Notes |
|---|---|---|---|
chainId | number? | config default | Optional chain override. |
solverId | string? | default solver | A literal solver kind ("enigma" / "rasa") narrows the returned market type. |
Returns — Market[], the normalized SDK market shape. Market is a discriminated union EnigmaMarket | RasaMarket on kind; fields are camelCase and required (the SDK fills solver gaps), and maxLeverage is a number on every solver. Passing a literal solverId narrows the return to that kind (getMarkets(config, { solverId: "rasa" }) → RasaMarket[]); omitting it yields the union, which you narrow on kind to reach solver-specific fields.
Important market fields (all on Market):
| Field | Type | Notes |
|---|---|---|
kind | string | Discriminant: "enigma" or "rasa". |
symbolId | number | Contract symbol id. |
name | string | Solver market name, often the value to pass as symbol. |
symbol | string | Display symbol. |
maxLeverage | number | Maximum leverage accepted for this market. |
state (Enigma) | number | Enigma only — 0 disabled, 1 close only, 2 open only, 3 enabled. Narrow on kind === "enigma" to access. |
getMarketsQueryOptions
Build TanStack Query options for getMarkets.
import { getMarketsQueryOptions } from "@symmio/trading-core";
import { useQuery } from "@tanstack/react-query";
const query = useQuery(getMarketsQueryOptions(config, { query: { staleTime: 30_000 } }));Symbols (Enigma)
The /symbols service is the richer, preferred symbol catalogue on Enigma. Where getMarkets returns the whole /contract-symbols list in one shot, /symbols lets the solver do the work: filter by name, asset, token, validity, or per-side trading state, and page through the results — so a large catalogue costs one small request instead of a full download you filter yourself.
getSymbols
Fetch the tradable symbol catalogue from the Enigma solver’s /symbols endpoint. Enigma-only — calling it against a non-enigma solver throws UNSUPPORTED_BY_SOLVER.
import { getSymbols } from "@symmio/trading-core";
// First page, the solver's default of 100 rows.
const symbols = await getSymbols(config, {});
// Server-side filtering + pagination: enabled "BTC" symbols, 20 per page.
const page = await getSymbols(config, {
search: "BTC",
isValid: "true",
stateLong: "enabled",
limit: 20,
offset: 0,
});Parameters
| Name | Type | Default | Notes |
|---|---|---|---|
chainId | number? | config default | Optional chain override. |
solverId | "enigma"? | default solver | Must resolve to an enigma solver. |
limit | number? | 100 (max 500) | Page size. |
offset | number? | 0 | Page offset for pagination. |
symbolId | number? | — | Restrict to a single symbol id. |
search | string? | — | Case-insensitive substring match on the symbol name. |
asset | string? | — | Exact-match filter on the base asset. |
tokenAddress | string? | — | Exact-match filter on the collateral token address. |
isValid | "true" | "false" | "any"? | "true" | Validity filter; the solver defaults to valid-only. |
stateLong | "disabled" | "close_only" | "open_only" | "enabled"? | — | Long-side trading-state filter. |
stateShort | "disabled" | "close_only" | "open_only" | "enabled"? | — | Short-side trading-state filter. |
Returns — SolverSymbol[]. Same camelCase, gap-filled shape as an EnigmaMarket, except /symbols reports trading state per side (stateLong / stateShort) instead of the single state on getMarkets.
| Field | Type | Notes |
|---|---|---|
symbolId | number | Contract symbol id. |
name | string | Solver market name (e.g. "BTCUSDT"). |
symbol | string | Display ticker. |
maxLeverage | number | Maximum leverage (coerced to a number). |
stateLong | number | Long-side state: 0 disabled, 1 close only, 2 open only, 3 enabled. |
stateShort | number | Short-side state (same encoding). |
isValid | boolean | Whether the symbol is currently valid. |
getSymbolsQueryOptions
Build TanStack Query options for getSymbols.
import { getSymbolsQueryOptions } from "@symmio/trading-core";
import { useQuery } from "@tanstack/react-query";
const query = useQuery(getSymbolsQueryOptions(config, { search: "ETH", limit: 50 }));Trade volume & revenue (Enigma)
Three Enigma-only reads (each throws UNSUPPORTED_BY_SOLVER off Enigma, and each ships a matching …QueryOptions / …QueryKey factory).
getSolverRevenue
Revenue totals for a trailing window — protocol-wide by default, or one market’s share when symbolId is passed. Splits into a hedger-fee share and a funding share whose sum is totalRevenue, all as plain dollar numbers (no decimal scaling). Full reference: Revenue.
import { getSolverRevenue } from "@symmio/trading-core";
// Every market, since listing — the headline figure.
const lifetime = await getSolverRevenue(config);
// One market, trailing 24 hours. Always smaller than the protocol-wide total.
const day = await getSolverRevenue(config, { symbolId: 1, timeRange: "24h" });timeRange is a closed set — "1h" | "24h" | "7d" | "30d" | "lifetime" — and defaults to lifetime when omitted.
getTradeVolume
Daily trade-volume rows for one market from /trade-volume/{symbolId} — one { timestamp, volume } per day, ascending. timestamp is the solver’s ISO 8601 day bucket (e.g. "2026-07-09T00:00:00Z"); volume is a decimal string. symbolId is required. Full reference: Trade volume.
import { getTradeVolume } from "@symmio/trading-core";
const volume = await getTradeVolume(config, { symbolId: 1 });
const latest = volume.at(-1); // { timestamp: "2026-07-09T00:00:00Z", volume: "448.69…" }getRevenueRecords
Incremental revenue records from /revenue/records, cursor-paginated on the record id. Returns { records, count }.
import { getRevenueRecords } from "@symmio/trading-core";
const { records, count } = await getRevenueRecords(config, { limit: 100 });
const nextCursor = records.at(-1)?.id; // pass back as `id` for the next pageLocked Params
getLockedParams
Fetch locked params from the chain solver’s /get_locked_params/{symbol} endpoint.
The symbol parameter is the solver path value. In trade UIs, use the selected market’s name when available, with symbol as a fallback.
import { getLockedParams } from "@symmio/trading-core";
const locked = await getLockedParams(config, {
symbol: "BTCUSDT",
leverage: 5,
});
console.log(locked.cva, locked.lf, locked.partyAmm, locked.partyBmm);Parameters
| Name | Type | Default | Notes |
|---|---|---|---|
symbol | string | required | Solver market path value. |
leverage | number | required | Leverage used to calculate locked params. |
chainId | number? | config default | Optional chain override. |
Returns — SolverLockedParams.
interface SolverLockedParams {
cva?: string;
lf?: string;
partyAmm?: string;
partyBmm?: string;
leverage?: string;
}getLockedParamsQueryOptions
Build TanStack Query options for getLockedParams.
import { getLockedParamsQueryOptions } from "@symmio/trading-core";
import { useQuery } from "@tanstack/react-query";
const options = getLockedParamsQueryOptions(config, {
symbol: "BTCUSDT",
leverage: 5,
query: { staleTime: 10_000 },
});
const query = useQuery(options);GetLockedParamsOptions requires symbol and leverage. The query factory does not make required action inputs optional and does not validate missing inputs inside queryFn.
For form UIs where values may be incomplete, gate the query outside the factory:
const ready = Boolean(symbol) && leverage > 0;
const options = getLockedParamsQueryOptions(config, {
symbol: symbol ?? "",
leverage: leverage ?? 0,
query: { enabled: ready },
});getLockedParamsQueryKey
Build a cache key for a locked-params query.
import { getLockedParamsQueryKey } from "@symmio/trading-core";
const key = getLockedParamsQueryKey({
symbol: "BTCUSDT",
leverage: 5,
});symbol and leverage are part of the key, so each market/leverage pair caches independently.
Notional Cap (available liquidity)
Per-market caps on how much notional the solver still permits — the frontline “can I open this size?” gate.
getNotionalCapBySymbolId
Cap for one market.
import { getNotionalCapBySymbolId } from "@symmio/trading-core";
const cap = await getNotionalCapBySymbolId(config, { symbolId: 1n });
// { used: bigint, cap: bigint, availability: bigint }getNotionalCapAll
Batch — every market’s cap in one call. Cheaper than looping getNotionalCapBySymbolId.
import { getNotionalCapAll } from "@symmio/trading-core";
const caps = await getNotionalCapAll(config, {});getOpenInterestBySymbolId
Used vs capped notional per market.
import { getOpenInterestBySymbolId } from "@symmio/trading-core";
const oi = await getOpenInterestBySymbolId(config, { symbolId: 1n });Estimated price
getEstimatedPrice
Ask the solver what price an open or close would actually fill at — a read-only simulation of the trade (GET /estimated-price; nothing is submitted) → { estimatedPrice }. Pass the order quantity, the side, whether it’s an "open" or "close", and the slippage-adjusted request price the caller computed (calculateTradeParams().requestedOpenPrice for an open, calculateClosePrice() for a close) — not the raw mark. position_type is sent as "long" / "short".
import { getEstimatedPrice, PositionType } from "@symmio/trading-core";
const { estimatedPrice } = await getEstimatedPrice(config, {
symbolId: 1,
quantity: "1000",
positionType: PositionType.LONG,
entry: "open",
price: requestPrice,
});getEstimatedPriceQueryOptions / getEstimatedPriceQueryKey ship alongside for TanStack; the React layer wraps them as useEstimatedPrice.
calculatePriceImpact
Pure helper — signed price-impact percent of an estimated fill vs a reference price: (estimated − reference) / reference × 100. Returns 0 for a zero / non-finite reference. The UI colors it by side.
import { calculatePriceImpact } from "@symmio/trading-core";
const impact = calculatePriceImpact({ estimatedPrice, referencePrice: markPrice }); // e.g. 0.42 → +0.42%Rasa-only solver endpoints
The rasa solver (majors, on Base) exposes reads the enigma solver does not. Each is a normal getX action with a …QueryOptions / …QueryKey factory; calling one against a non-rasa solver throws UNSUPPORTED_BY_SOLVER. In React these are the Rasa Solver hooks. See Solvers & Chains for the kind model.
| Action | Purpose |
|---|---|
getSolverReadiness | Whether the solver is up and accepting trades. |
addSolverWhitelist | Add a partyA to the solver whitelist (a write, with addSolverWhitelistMutationOptions). |
getSolverBalanceInfo | Solver-side balance for an account. |
getPartyAUpnl | PartyA uPnL as computed by the solver. |
getSolverOpenInterest | Global open interest. |
getSolverPriceRange | Accepted price range for a symbol. |
Notification history search is not listed here: it is the unified searchNotifications, which dispatches to the rasa solver’s position-state endpoint (POST /position-state/{start}/{size}) for a rasa solver and returns the rasa result variant. See Notifications.
supportsEstimatedPrice
Pure predicate — whether the resolved solver kind supports the /estimated-price simulation. Gate the estimated-price read with it so a majors UI doesn’t call an endpoint its solver doesn’t serve.
import { supportsEstimatedPrice } from "@symmio/trading-core";
if (supportsEstimatedPrice(config, { solverId })) {
// safe to call getEstimatedPrice for this solver
}getErrorMessage
Resolve a solver error code to a human message (getErrorMessageQueryOptions / …QueryKey alongside). The React wrapper is useErrorMessage.
Instant Open / Instant Close
The lowcap trading flows live on their own pages:
- Instant Open —
instantOpen,instantOpenAuto,prepareInstantOpenParams,getInstantOpens,getInstantOpenQuoteId. - Instant Close —
instantClose,instantCloseAuto,instantCloseBulk,prepareInstantCloseParams,getInstantCloses.
Related
- SYMMIO Contract — on-chain reads.
getOnchainContractMarketsis the contract-side analog togetMarkets. - Unified Quotes —
PendingInstantOpen/PendingInstantClosefromgetInstantOpens/getInstantClosesfeedreconcileQuotes. - Errors — solver failures surface as
SymmApiError.