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. |
hedgerFeeCloseEarlyRate (Enigma) | string | Enigma only — early (peak) close-fee rate; see Solver close fee. Paired with hedgerFeeCloseEarlyThreshold / hedgerFeeCloseStandardThreshold. |
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. |
hedgerFeeClose | string | Standard (floor) close-fee rate. |
hedgerFeeCloseEarlyRate | string | Early (peak) close-fee rate — see Solver close fee. |
hedgerFeeCloseEarlyThreshold | number | Seconds the early rate applies flat. |
hedgerFeeCloseStandardThreshold | number | Seconds after which the standard rate applies (linear decay between the two). |
Solver close fee
The solver charges more to close a freshly opened position than an aged one. The close-fee rate starts at hedgerFeeCloseEarlyRate, holds flat until hedgerFeeCloseEarlyThreshold seconds, then decays linearly to the standard hedgerFeeClose by hedgerFeeCloseStandardThreshold. These are flat fields on both EnigmaMarket (from getMarkets) and SolverSymbol (from getSymbols), so either satisfies SolverCloseFeeRates — pass a market or a symbol straight in:
import { calculateSolverCloseFee, getSolverCloseFeeRate } from "@symmio/trading-core";
const market = markets.find((m) => m.kind === "enigma" && m.symbolId === id);
getSolverCloseFeeRate(market, 30); // "0.0024" — still in the early window
getSolverCloseFeeRate(market, 105); // "0.0015" — halfway through the decay
getSolverCloseFeeRate(market, 180); // "0.0006" — standard rate
// Price a real close: holding time = now − the position's createTimestamp (seconds).
const holdingSeconds = Number(nowSeconds - createTimestamp);
const fee = calculateSolverCloseFee(market, { notional: "1000", holdingSeconds });
// Pass holdingSeconds: 0 for the worst case (a just-opened position) — the peak price for closing now.A market with no decay — a non-Enigma solver, or one that omits the early fields — has hedgerFeeCloseEarlyRate === hedgerFeeClose and both thresholds 0, so getSolverCloseFeeRate returns the standard rate for every holding time.
These are the low-level pieces; for a full close-fee preview (this leg plus the platform and static legs, with the market and rates resolved for you) use getInstantCloseFees.
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
One market’s revenue totals for a trailing window — symbolId is required; the current solver generation serves no protocol-wide aggregate. 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";
// One market, since listing.
const lifetime = await getSolverRevenue(config, { symbolId: 1 });
// The same market, trailing 24 hours.
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 pageSolver info (Enigma)
getSolverInfo
The solver’s general information from the Enigma-only /info endpoint — today its static fee configuration: the configured USD amount charged per instant open and per instant close (including each partial close), before signed fee-cap adjustments. Returns an EnigmaSolverInfo — a flat camelCase mirror of the wire shape, amounts as USD decimal strings exactly as the solver reports them, undefined where the solver omits a field.
import { getSolverInfo } from "@symmio/trading-core";
const info = await getSolverInfo(config);
info.staticSolverFeeOpen; // "0.5" — USD per instant open
info.staticSolverFeeClose; // "0.25" — USD per instant close (incl. partial closes)A rasa-kind solver has no /info endpoint, so the read fails before the wire with UNSUPPORTED_BY_SOLVER rather than a vendor 404. A request that goes out and fails throws SymmApiError with code FETCH_SOLVER_INFO_FAILED. Ships the matching getSolverInfoQueryOptions / getSolverInfoQueryKey factories; the React binding is useSolverInfo.
The instant fee previews consume these legs: getInstantOpenFees reports the open leg as staticSolverFeeOpen, and prepareInstantOpenParams funds it through the addMargin amount (full-balance sizing carves it off the balance before the linear solve). The static close leg is not funded at open — the contract charges it at close from the position, and getInstantCloseFees reports it as staticSolverFeeClose (see Fees at close). Pass a pre-fetched result as solverInfo to skip the fetch in either preview.
Locked 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. |
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.