Skip to Content
Symmio Trading-SDK — the SDK surface for builders on Arbitrum

getSolverRevenue

Read what the solver has earned on one market — total, split into the hedger-fee share and the funding share — over a trailing window. symbolId names the market and is required; the window defaults to lifetime.

import { getSolverRevenue } from "@symmio/trading-core"; const revenue = await getSolverRevenue(config, { symbolId: 1 }); revenue.totalRevenue; // 15936.3 — dollars, market 1, since listing revenue.hedgerFeeRevenue; // the share earned from hedger fees revenue.fundingRevenue; // the share earned from funding payments revenue.recordCount; // how many underlying rows produced those totals

This is the solver’s own bookkeeping, not the inventory service’s and not the listing backend’s. A pools dashboard that shows revenue next to TVL next to a catalog row is reading three different vendors — see Pools for how they divide up.

The two halves of the signature are exported as GetSolverRevenueParameters (the parameters object) and GetSolverRevenueReturnType (an alias of SolverRevenue), so you can name them in your own wrappers:

import type { GetSolverRevenueParameters, GetSolverRevenueReturnType } from "@symmio/trading-core"; function lastDay(parameters: GetSolverRevenueParameters): Promise<GetSolverRevenueReturnType> { return getSolverRevenue(config, { ...parameters, timeRange: "24h" }); }

The read is per-market

There is no protocol-wide variant. Earlier solver generations served an aggregate /revenue endpoint and this action exposed it by omitting symbolId; the current generation removed that endpoint, so symbolId is now required and the read answers for exactly one market.

For a figure that spans several markets, call once per market and label the result with the markets it covers — do not sum a handful of markets and call it “protocol revenue”. A sum over markets [1, 2, 3] is the revenue of markets 1–3, nothing more, and every market you skip makes the label more wrong.

The underlying rows are still available: getRevenueRecords pages through every revenue-bearing row (optionally filtered by symbol), which is the honest source for anything cross-market.

Parameters

chainIdnumberoptional

Target chain id. Defaults to the config’s defaultChainId. Selects which chain’s solver is read, and is folded into the query key.

solverIdSolverIdoptional

Solver kind to target. Defaults to the chain’s default solver. Must resolve to an enigma solver — see Enigma-only.

symbolIdnumber

The market whose revenue to read. Required — the read hits /revenue/{symbolId} and there is no aggregate endpoint to fall back to.

timeRangeSolverRevenueTimeRangedefault "lifetime"optional

Trailing window: "1h", "24h", "7d", "30d" or "lifetime". Omitted entirely from the request when unset, so the solver applies its own default of lifetime.

timeRange is a closed set

type SolverRevenueTimeRange = "1h" | "24h" | "7d" | "30d" | "lifetime";

Those five literals are the whole vocabulary. The solver validates the value server-side and rejects anything else with TimeRange must be one of [1h 24h 7d 30d lifetime], so an arbitrary duration string, a number of seconds, or a date range is a failed request rather than a best-effort answer.

Windows are trailing from now, not calendar buckets: "24h" is the last twenty-four hours, not “today”. Each window is an independent request — there is no series endpoint here, so a sparkline means one call per point (or getRevenueRecords for the underlying rows).

Returns

Promise<SolverRevenue>
totalRevenuenumber

Total revenue over the window, in dollars15936.3 means $15,936.30. Equal to hedgerFeeRevenue + fundingRevenue.

hedgerFeeRevenuenumber

The share earned from hedger fees, in dollars.

fundingRevenuenumber

The share earned from funding payments, in dollars.

recordCountnumber

How many underlying revenue rows the totals were computed from. Surface it: it is what separates “this window genuinely earned nothing” (recordCount > 0, totals 0) from “there is no data for this window” (recordCount === 0).

No decimal scaling. Unlike the on-chain reads and the listing catalog’s 18-decimal bigints, these are plain JavaScript numbers already in dollar units. Do not run them through formatUnits; format them as currency directly.

Every field is optional in the solver’s own schema — it omits a dimension rather than sending a zero for it — so each one is defaulted to 0 on the way through. A response with nothing in it is { totalRevenue: 0, hedgerFeeRevenue: 0, fundingRevenue: 0, recordCount: 0 }, never undefined fields.

Because totalRevenue is the sum of the other two, the split is a share breakdown you can chart without a second call:

const { totalRevenue, hedgerFeeRevenue, fundingRevenue } = await getSolverRevenue(config, { symbolId: 1 }); const feeShare = totalRevenue === 0 ? 0 : (hedgerFeeRevenue / totalRevenue) * 100; const fundingShare = totalRevenue === 0 ? 0 : (fundingRevenue / totalRevenue) * 100;

Enigma-only

The /revenue/{symbolId} endpoint exists on the enigma solver only. getSolverRevenue resolves the target solver and checks its kind before the network hop, so a rasa target fails immediately with UNSUPPORTED_BY_SOLVER instead of surfacing a vendor 404 that reads like an outage.

import { getSolverRevenue } from "@symmio/trading-core"; import { base } from "viem/chains"; // Throws UNSUPPORTED_BY_SOLVER — Base's default solver is rasa-kind. No request is made. await getSolverRevenue(config, { chainId: base.id, symbolId: 1 });

Reaching that check means resolving a rasa solver, which is why the example switches chains rather than forcing solverId: "rasa". Asking for a solver kind a chain does not register — "rasa" on a chain that carries the enigma solver alone — fails one step earlier with UNKNOWN_SOLVER.

In a UI that can point at either solver, gate the panel on the resolved kind rather than catching the error.

Examples

One market, since listing

No timeRange — the solver’s own default window is lifetime:

const lifetime = await getSolverRevenue(config, { symbolId: 1 }); lifetime.totalRevenue; // 15936.3 lifetime.recordCount; // > 0, so those totals are real and not a data gap

The trailing 24 hours

const day = await getSolverRevenue(config, { symbolId: 1, timeRange: "24h" }); if (day.recordCount === 0) { // No rows in the window at all — render "no data", not "$0.00". }

Several markets, labeled honestly

One call per market, and the label names what the sum covers:

const symbolIds = [1, 2, 3]; const totals = await Promise.all(symbolIds.map((symbolId) => getSolverRevenue(config, { symbolId, timeRange: "30d" }))); const markets123 = totals.reduce((sum, t) => sum + t.totalRevenue, 0); // Render as "Revenue · markets 1–3", not "Protocol revenue".

Query options

import { getSolverRevenueQueryOptions } from "@symmio/trading-core"; import { useQuery } from "@tanstack/react-query"; useQuery(getSolverRevenueQueryOptions(config, { symbolId: 1, timeRange: "24h" }));

GetSolverRevenueOptions is the action’s parameters plus a query bag of TanStack overrides. The factory folds config.getChainConfigKey(chainId) into the key, so a runtime config override pointed at a different solver deployment refetches instead of serving the previous one’s cache. getSolverRevenueQueryKey builds the same key for cache matching and invalidation.

Both symbolId and timeRange are part of the key, so each market/window pair caches independently — one market’s 24-hour figure never gets overwritten by another market’s fetch.

The rest of the factory’s types are exported too: GetSolverRevenueData is what the query resolves to (the same SolverRevenue), GetSolverRevenueQueryKey is the key getSolverRevenueQueryKey returns, and GetSolverRevenueQueryOptions is the options bag the factory produces.

Throws

  • UNSUPPORTED_CHAIN — a SymmError (kind: "config") when the chainId is not one the config knows about at all.
  • UNKNOWN_SOLVER — a SymmError (kind: "config") when the resolved chain registers no solver of the requested kind. Raised before the kind check, so it precedes UNSUPPORTED_BY_SOLVER.
  • UNSUPPORTED_BY_SOLVER — a SymmError (kind: "config") when the resolved solver is not enigma-kind. Raised before any request.
  • FETCH_SOLVER_REVENUE_FAILED — the request itself failed. Any axios failure becomes a SymmApiError carrying status, statusText, responseData, url and method; a non-axios throw becomes a plain SymmError (kind: "api"), with the original error as its cause when it was an Error.

A malformed timeRange surfaces through the second code: the solver answers with its TimeRange must be one of [1h 24h 7d 30d lifetime] message, which arrives as the SymmApiError’s responseData.

  • Solvers — the slice overview, including getRevenueRecords for the individual rows behind these totals.
  • Solvers hooksuseSolverRevenue is the React wrapper over this action.
  • Solvers & Chains — the solver-kind model this read’s enigma-only constraint belongs to.
  • Errors — the SymmError / SymmApiError hierarchy these codes belong to.
Last updated on