Subgraph hooks
React hooks over the two per-chain GraphQL subgraphs (analytics + events). See Core Subgraph for the full data model, endpoint layout, and how to add missing queries.
Read only. Subgraphs are historical indexes — writing happens on-chain, then indexed here.
Import
import {
useSubgraphQuery,
useBalanceHistory,
useTransferHistory,
useQuoteHistory,
useQuoteEventsByType,
useQuoteFunding,
useQuotesFunding,
useQuoteGroupFunding,
useQuoteGroupFundingHistory,
useDepositHistory,
useWithdrawHistory,
} from "@symmio/trading-react";Escape hatch — useSubgraphQuery
Send any raw GraphQL query against either subgraph. Use when the SDK doesn’t ship a pre-baked hook for what you need.
const history = useSubgraphQuery({
subgraph: "analytics",
query: gql`
query MyCustomQuery($account: String!) {
myEntities(where: { account: $account }) {
id
value
}
}
`,
variables: { account: subAccount },
});Returns UseQueryResult<T, SymmioRequestError> — same shape as every SDK hook. The response is parsed as-is; consumers own the type.
If you find yourself writing the same raw query across two components, that’s the signal to open a PR adding a typed hook — see Core Subgraph.
Pre-baked history reads
useBalanceHistory
Deposit / withdraw / allocate / deallocate events over time.
const history = useBalanceHistory({ account, first: 50, skip: 0 });useTransferHistory
Every collateral in / out for an account.
const transfers = useTransferHistory({ account, first: 50, skip: 0 });useDepositHistory / useWithdrawHistory
Filtered projections of the balance history for the two most common UI needs.
Per-quote history
useQuoteHistory
Terminal (close / liquidation) events for a set of accounts, newest first. Keyed on subAccounts — not on a quote id — because the subgraph filters history by the quote’s owning account. Pass the parent SubAccount and, when the positions are isolated, its Virtual Accounts.
const { data } = useQuoteHistory({ subAccounts: [subAccount], first: 100 });
const rows = data?.rows ?? [];useQuoteEventsByType
Non-terminal events for one quote, filtered to the QuoteEventTypes you ask for → { rows, hasMore }. The indexed types are SettleUpnl (open-price recompute), ChargeFundingRate, and ChargeAccumulatedFundingFee; two presets ship with the SDK — PRICE_HISTORY_EVENT_TYPES (all three, the open-price timeline) and FUNDING_HISTORY_EVENT_TYPES (the two funding charges only).
import { FUNDING_HISTORY_EVENT_TYPES } from "@symmio/trading-core";
const { data } = useQuoteEventsByType({
quoteId: 42n,
types: FUNDING_HISTORY_EVENT_TYPES,
first: 20,
});useQuoteFunding
Funding totals — paid, received, netReceived — for one quote across its lifetime. Disabled while quoteId is undefined (an off-chain row).
const { data, isLoading } = useQuoteFunding({ quoteId: 42n });
/** `data` is `QuoteFundingData | null`: { quoteId, paid, received, netReceived }, all wei. */
const netReceived = data?.netReceived ?? 0n;useQuotesFunding
Batch variant — funding for many quotes in one request, plus the running sums. Feeds “sum of funding across open positions” UI.
It takes quote-shaped rows, not bare ids: anything with an optional quoteId (a UnifiedQuote works as-is). Off-chain rows (quoteId === undefined) are skipped silently and duplicate ids are fetched — and summed — exactly once.
const { rows, netReceived, missingQuoteIds, isLoading } = useQuotesFunding({ quotes: group.quotes });rows is aligned 1:1 with the input quotes (null for an off-chain or not-yet-indexed row). missingQuoteIds is every requested id while the query is in flight or failed, and the not-yet-indexed subset once it settles.
Group funding
Both hooks take a QuoteGroup and derive the child quote ids themselves: optimistic children (no quoteId) are dropped and duplicates collapsed, so one grouped position costs one subgraph round-trip.
Sign convention — shared with useQuoteFunding and useQuotesFunding above: netReceived = received − paid, the P&L perspective, so a positive value means the position earned funding. This matches QuoteFundingData.netReceived and the uPnL folds, and is the inverse of the cost-positive on-chain int256. A UI that colors “money in” green renders it as-is — no negation anywhere.
Settled to date only. These are funding charges the protocol has already applied and the analytics subgraph has indexed. Funding accrued since a quote’s last charge is not indexed anywhere and is therefore not included.
useQuoteGroupFunding
Aggregated settled funding for a whole group — the group-level counterpart of useQuoteFunding. Reads every child in one round-trip via useQuotesFunding, then folds with core’s pure aggregateGroupFunding. The result is memoized, so it is referentially stable while the group and its rows are unchanged.
function GroupedPositionFunding({ group }: { group: QuoteGroup }) {
const { funding, isLoading } = useQuoteGroupFunding({ group });
/** `funding.netReceived` is a LOWER BOUND until `isComplete` is true — never render it as final before checking. */
if (isLoading || !funding.isComplete) return <FundingSkeleton />;
/** Already income-positive: `netReceived > 0n` means the group EARNED funding. */
return <Money amount={funding.netReceived} label="Funding" />;
}funding is a QuoteGroupFunding: paid / received / netReceived (wei), resolvedCount, expectedCount, missingQuoteIds, and isComplete. Treat isComplete: false as “funding unknown”, not “no funding” — an all-optimistic or empty group reports false with netReceived: 0n. rows is the per-child QuoteFundingData | null, aligned 1:1 with group.quotes.
useQuoteGroupFundingHistory
The per-tick timeline behind that total: every child’s funding events as one merged, time-sorted stream. types is locked to FUNDING_HISTORY_EVENT_TYPES, so the hook takes only the group plus paging / sort.
const { data, isLoading } = useQuoteGroupFundingHistory({ group, first: 100 });
for (const row of data?.rows ?? []) {
/** Rows arrive interleaved across quotes — `row.quoteId` says which leg this tick belongs to. */
const netReceived = (row.fundingReceived ?? 0n) - (row.fundingPaid ?? 0n);
/** netReceived > 0n → the position earned funding on this tick. `row.rate` is the signed rate applied (wei). */
}Returns a TanStack UseQueryResult<{ rows: QuoteEventRow[]; hasMore: boolean }, SymmioRequestError>. Paging is over the merged stream, not per quote id: pass first / skip and keep going while data.hasMore is true. Omitting first asks for the 1000-row ceiling the subgraph enforces, so an un-paged call loads the whole timeline unless the group has more ticks than one request can return. Pass orderDirection: "asc" for an oldest-first timeline. The query stays disabled while the group has no on-chain children.
On current deployments effectively every row is CHARGE_FUNDING_RATE; CHARGE_ACCUMULATED_FUNDING_FEE is part of the preset for completeness but is not yet emitted, so do not rely on seeing it.
Related
- Core Subgraph — the two subgraphs, contract sync, adding queries.
getQuotesEventsByType— the batched core action behinduseQuoteGroupFundingHistory.aggregateGroupFunding— the pure fold behinduseQuoteGroupFunding.- Unified Quotes —
useQuoteHistorydecorates rendered rows. - Errors — subgraph failures surface as
SymmioRequestErrorkind: "api".