Skip to main content
Documentation for the serverless functions exposed by the app. Useful for integrations, frontend, and AI tools working with this codebase. Base URL: https://app.seer.pm Functions are available at: https://app.seer.pm/.netlify/functions/<function-name>

get-market

Fetches a single market by ID or URL/slug. Merges real-time subgraph data with cached database data (including verification status) for maximum accuracy.

Body (JSON)

Response 200

Serialized market object: all bigint fields are returned as string for JSON compatibility.

Errors

  • 400 – Missing request body or Missing required parameters: chainId and (id or url).
  • 404 – Market not found.
  • 500 – Internal server error.

Search and list markets with filters, pagination, and sorting. Queries one or multiple chains; when VITE_TESTNET_WEBSITE=1 only Sepolia is used.

Body (JSON) – Search parameters

All optional. Defaults in the handler: limit=1000, page=1, chains = all supported (or Sepolia only on testnet).

Response 200

Errors

  • 400 – Missing request body.
  • 500 – Internal server error.

get-token-transactions

Returns top holders and recent transactions for one or more tokens (per chain).

Query params

Response 200

recentTransactions items use the following type (value is serialized as string in JSON):
Header: Cache-Control: public, max-age=300 (5 minutes).

Errors

  • 400 – Missing tokenIds or chainId, invalid chainId, or unsupported chain.
  • 500 – Internal server error.

get-transactions

Transaction history for an account: swaps, liquidity (LP add/remove), and CTF events (split/merge/redeem). Always computed across all production chains and cached; optional filters apply in memory on the cached payload. Covers the account and the TradeExecutor contracts it owns. Someone trading through an executor holds the outcome tokens inside that contract and the DEX indexes the swap with the executor as recipient, so querying the address alone returned only what the EOA did directly. Rows carry sourceWallet, which differs from the requested account when an executor produced them, and are deduped across the wallets by eventId. Executor discovery is best-effort: if it fails the response is the account’s own rows, as before.

Query params

Response 200

Array of TransactionData, sorted by timestamp descending (then blockNumber). Each row includes chainId.

Errors

  • 400 – Missing account, invalid chainId/eventType, or non-numeric startTime/endTime.
  • 500 – Internal server error.

get-portfolio

Positions for an account: outcome token balances per market, with market data, outcome, collateral, and redeemed prices. Always computed across all production chains and cached; optional chainId filters the cached payload.

Query params

Response 200

Array of PortfolioPosition:
Only positions with balance > 0 are included; for closed markets, only those that qualify as winning per payout.

Errors

  • 400 – Missing account, or invalid chainId.
  • 500 – Internal server error.

get-portfolio-value

Current portfolio value and ~24h mark-to-market change (outcome positions only; same collateral filter as get-portfolio). Totals are USD (primary collateral × spot USD via DexScreener / on-chain sDAI·sUSDS, same source as the PnL leaderboard).

Query params

Response 200

currentPortfolioValue, historyPortfolioValue, historyTimestamp, delta, deltaPercent, unit: "USD".

get-portfolio-pl

Period P/L for an account (outcome MTM, including what the account holds in LP positions, + router collateral legs − swap/LP cashflow). Adding or removing liquidity on its own does not move P/L; price moves and fees compounded into the position do. Fees accrued but not collected are not counted. Generic markets only — Futarchy is excluded so non-primary notional (PNK/GNO, …) is not mixed into the profile’s primary units. get-portfolio still lists Futarchy positions.
  • Global (no marketId / marketIds): reads materialized rows from Supabase pnl_leaderboard (app_id = all). No live compute; miss → zeros. Values are USD (pnl = pnl_usd; other fields converted with collateral_price_usd). chainId=all sums USD across chains. Response includes unit: "USD".
  • Market-scoped (marketId or marketIds): computes live via computePortfolioPlAllPeriods in native collateral units. Futarchy ids → 404. chainId=all is not allowed.

Query params

@seer-pm/sdk fetchPortfolioPnL / fetchPortfolioValue send collateralProfile from getActiveCollateralProfileName() after configureCollateral — see Collateral profiles. fetchPortfolioPnL / fetchPortfolioValue accept chainId: number | "all".

get-pnl-leaderboard

Ranked wallet P/L for markets configured under Seer apps (web/src/lib/apps.ts). Data is materialized by scheduled-refresh-pnl-leaderboard / refresh-pnl-leaderboard-background into Supabase pnl_leaderboard (apply web/supabase/sql/pnl_leaderboard.sql first). The refresh considers every wallet with analytics activity (not a top-N by tx count) and rotates a stale/missing batch each run.

Query params

All rankings use USD (pnl_usd from materialization: native collateral PnL × spot collateral USD). Per-chain filters still return USD so ranks stay comparable; chainId=all sums pnl_usd across chains. App filter all is independent of SEER_APPS market lists. Each row includes an optional username when the wallet has chosen one. Usernames are opt-in, so its absence means only that the wallet never set one.

Trader score

Each row carries score (0-100, one decimal) and tier (Elite ≥85, Great ≥70, Good ≥55, Average ≥40, Weak below). It is a weighted mean of five clamped-linear components, shrunk toward 50 afterwards: Every ratio is taken over the same market set: those carrying more than $1 of capital at risk. scoredCapitalUsd is the capital in exactly that set, which is why returns can differ slightly from the row’s roi (whole-wallet). hitRate scores the wallet against its own break-even rate — avgLoss / (avgWin + avgLoss), from the realised per-market payoffs — rather than a fixed band, because a prediction market pays out inversely to the price paid and a fixed band marks down every long-shot strategy regardless of profitability. scoreBreakdown.inputs carries hitRate, breakEvenHitRate and their difference hitEdge; the component scores hitEdge. scoreBreakdown.sampleShrink is { scoredMarketCount, factor, rawScore }, where factor is n / (n + 10) and score = 50 + (rawScore − 50) × factor. The weighted component points sum to rawScore, not to score — a client rendering the breakdown has to show the shrink or it will look like an arithmetic error. score is null, never 0, when the wallet has fewer than 3 markets above 1ofcapitalatrisk,lessthan1 of capital at risk, less than 100 of capital across those markets, or more than 25% of its pnl_usd outside them. When it is null the row also carries scoreUnavailable — { reason: "markets" | "capital" | "coverage", scoredMarketCount, minScoredMarkets, minCapitalUsd, dustUsd, unscoredPnlFraction, maxUnscoredPnlFraction } — so a client can say which gate was missed. Note that marketCount counts every traded market while the gate counts only those above $1 of capital at risk, so a wallet can show a large marketCount and still be unscored, or (for a pure LP wallet, which never swaps) a smaller one. Two priors keep small books honest on top of the shrink: the hit rate carries 5 pseudo-markets at 50%, and the profit factor carries symmetric prior mass of 2% of the book’s own gross flow on both sides, so a book with no losing market reads a finite ratio rather than infinity. reason: "coverage" means the scored markets do not account for the wallet’s P/L. Positions that move value without putting primary collateral at risk — conditional markets, whose pool trades one outcome token against another, and outcome tokens received by transfer — carry P/L into pnl_usd against no measured capital, so they never enter the scored set. pnl and roi on the row still include them. Read score as a summary of the wallet’s cross-market distribution. It is not a time-series metric: lossBurn is a terminal-state loss measure — negative terminal market P/L over capital — and says nothing about the path taken to get there (it cannot see a position that was deep underwater mid-window and recovered), breadth measures concentration across markets rather than the smoothness of an equity curve, and returns has a capital denominator but no risk denominator. scoreBreakdown.method is "markets" and identifies which implementation produced the number. The score is computed at read time from additive statistics stored on pnl_leaderboard, so it is correct after the executor→owner, app-scope and cross-chain merges. It is not stored per row, and it is not meaningful to average two scores. Those statistics describe an owner’s book, not an address’s. A wallet that trades through a TradeExecutor contract holds its capital on one address and, after the contract sweeps the outcome tokens out, its value on another; each market would fall below the $1 gate on both. So the per-market books of an owner and its executors are combined at materialization, before the gate, and the resulting statistics are stored on the owner’s row while the executors’ rows carry zeros. market_count follows them: it counts distinct markets, so summing each member’s own count double-counted every market an owner and its executor both traded. The additive totals — pnl_usd, volume_usd, capital_deployed — do stay per address and still sum across the merge.

get-market-pnl-leaderboard

Ranked wallet P/L within one market, including its conditional children. Reads pnl_market_leaderboard (apply web/supabase/sql/pnl_market_leaderboard.sql first) — same materialization pass as the global board, so freshness is identical.

Query params

Response 200

{ marketId, marketIds, chainId, period, total, rows }, where each row is { rank, address, pnl, volume, roi, unit: "USD", capitalUsd, score, tier, updatedAt, mergedWallets? }. score and tier are the trader’s protocol-wide score (the app_id = 'all' board for the same chain and period), not a score for this market: a single market can never clear the score’s 3-market eligibility gate, so a market-scoped score would always be null. It is looked up for the current page only, after pagination — which is why this endpoint cannot rank by it. A failed lookup yields score: null rather than failing the board.

markets-charts

Returns chart data (hourly price/volume) for markets. If IDs are provided, only those markets; if omitted, all cached chart data.

Query params

Response 200

Object keyed by marketId with each market’s chart data (array of series):

Errors

  • 400 – ids was provided but no valid address: No valid market IDs provided.
  • 500 – Supabase or internal error.

Quick reference for AIs

All responses are JSON. Errors return { "error": "message" } with the appropriate HTTP status code.