Current implementation status (
relayer/src/sources/card.ts): three sources.
pokemontcg is live and keyless: the TCGplayer market price for a RAW (ungraded)
card variant via pokemontcg.io, refreshed daily, with retry-and-backoff and a low/high
sanity band (set POKEMONTCG_API_KEY to raise rate limits). A PSA-graded market must
never use this source; raw and graded are different assets. manual is an
operator-attested USD price for markets whose real source is pending. gradedApi is
the shaped client for subscription graded-price APIs and refuses to run without
TCG_GRADED_API_KEY. All three convert through a live Pyth SOL/USD read. The
multi-source graded estimator described below is the production design those keys
unlock. Verified live 2026-07-24: swsh7-215 RAW ($2,137.76) pushed on chain within 2
bps of source. Graded cards are the flagship asset class; see
Asset Classes for why.The underlying unit
A card market’s underlying is one card and grade pair, for example “PSA 10 of card X”. Grade is part of the market identity: a PSA 10 market never blends PSA 9 or BGS 9.5 prices. Those are different assets with different markets.Sources
The estimator pools four independent source types:Estimator
Per market, per cycle:- Pool all sale prints of the card and grade within the lookback window (default 14 days; longer for slower cards).
- Reject outliers by median absolute deviation: prints more than about 3 MAD from the rolling median are dropped. This kills single shill sales.
- Compute a recency-weighted median of the survivors.
- Apply the sanity band: the estimate is floored by the best capitalized bid (the Collector Crypt buyback implied price, minus a tolerance) and capped by the lowest ask across venues (plus a tolerance). Asks are free to fake, so they only ever cap the estimate. They never set it.
Gates
Two gates can block a push entirely:- Cross-source disagreement: if the aggregator estimate, the eBay estimator, and the tokenized-venue estimator disagree by more than about 15 percent, the price feed (the relayer) holds the last value and alerts. A gap that large is either a real dislocation or someone gaming one venue; both deserve human eyes.
- Liquidity gate: if fewer than
min_salesprints exist in the window, no new value is pushed. A card that stops trading goes visibly stale instead of tracking noise.

