How the roots are read.
SnapeFi is a read-only market intelligence layer. This page documents what it reads, from where, and exactly how a Market Health rating is produced. The thresholds below are imported from the same module the engine uses.
Overview
Every market has roots. SnapeFi follows them: for each token it discovers every pool on the chain's registered venues, reads pool state at one block, simulates real trades against that state and grades the result with a published, deterministic method. Indexed sources are consulted only for what a single block cannot answer — how the price moved and how much traded in the last 24 hours — and every such value is labelled with its source.
The product rule is simple: no fabricated metrics. A value the chain or an index cannot supply is printed as Unavailable, —, No market detected or Data source unavailable, and a rating built on incomplete inputs is capped and says why.
- /marketsListing of every catalogued market with price, liquidity, depth and a preliminary health rating.
- /market/[address]Full inspection: pools, depth ladder, spread, chart, trades, contract facts, full health rating. Accepts a ticker or an address.
- /scannerPaste any contract: bytecode, ERC-20 surface, identity, pools, metrics, health, activity, contract facts.
- /compareTwo to four markets side by side across the health engine’s inputs.
- /watchlistSaved markets, joined with live readings; browser- or wallet-scoped.
- /explorerThe data-source map: chain probe, adapters, providers, registry and universe counts.
Requests flow from the App Router API routes into five providers (src/lib/providers), which compose the engine (src/lib/engine) and the adapters (src/lib/adapters). Adapters are the only modules that speak to an external system; each one is replaceable behind a small interface.
The adapter registry (src/lib/adapters/liquidity/index.ts) lists [uniswapV3, uniswapV2, ponsCurve]. A chain enables an adapter simply by listing factories for it in src/config/chains.ts; an adapter with no factories is inert on that chain.
- 01Registry → catalogueThe tokenized-stock registry (live, or the dated snapshot when it does not answer) plus configured quote, listed and ecosystem tokens form the catalogue. Every entry is re-verified on chain: decimals, symbol and name are read from the contract, and unreadable contracts are dropped.
- 02Contract readsFor a scan or a market page the address is checked for bytecode and an ERC-20 surface first. Identity (registry stock, launchpad token, uncatalogued ERC-20) is established before any price is asked for.
- 03Factory discoveryEvery registered factory of every adapter is asked for the pool of (asset, quote) for each quote token — all in one multicall. A zero answer is cached briefly; found pools are re-read on every inspection.
- 04Block-stamped multicallPool state (reserves, slot0, liquidity, curve reserves) is read through Multicall3 in one batch, so every value in a response carries the same block number and timestamp. The USD reference for a non-USD quote (WETH) comes from the deepest WETH/USD pool of the same read.
- 05SimulationsOn the deepest verified pool the depth ladder is simulated with the venue’s own math at the reference sizes, and a round trip at a small size gives the spread proxy. The health engine’s reference size is always part of the ladder.
- 06Index for 24h contextThe market index supplies 24h change, 24h volume and 24h trade counts; the pool indexer supplies candles and the trades tape. Both are cached, both may be stale, and both are stamped with the time they were stored.
- · Spot price, liquidity, depth, price impact and spread are never taken from an index; they come from pool state or are absent.
- · 24h change, 24h volume and trade counts are never derived from a single block; they come from an index or are absent.
- · Source verification, contract name and compiler come from Sourcify or the explorer; holder counts are never guessed and print Unavailable.
- · Fixture data exists only behind
NEXT_PUBLIC_USE_MOCK_DATA=true, is stamped MOCK on every response and prints a banner on every page.
A rating is produced by src/lib/health/calculateMarketHealth.ts from the thresholds in src/config/health.ts. Each input is graded, the grades are weighted into a 0–100 score, the score is banded into a status, and hard floors and caps are applied. Every step is returned as text so “Why this rating?” can print exactly what fired. The numbers below are read from that config at build time.
Inputs and grades
An input that cannot be read is graded UNKNOWN, contributes zero points, and is listed by name in the rating's missing inputs.
Points and weights
Score bands
Floors, caps and freshness
- Hard floor: liquidity graded NONE or depth graded NONE forces THIN regardless of activity. Price impact graded SEVERE also forces THIN.
- Missing inputs cap at CAUTION: if any input other than data age is missing, a THRIVING or STABLE result is lowered to CAUTION and the reason names the missing inputs. The engine never claims certainty on partial data.
- Preliminary (listing mode): a listing pass reads every pool but cannot simulate every market at once. Price impact and round-trip spread are simulated in the background in batches of 40 after each listing request and merged in on the next read; until a market’s batch has run, the weight of the two simulated inputs is redistributed over liquidity, depth and activity (score = weighted points ÷ 70 × 100), only the non-simulated inputs count toward the cap, and the rating is flagged
preliminary. Simulations are cached for ten minutes per pool; the market page and the scanner always simulate on demand. - Freshness: data older than 5 minutes lowers THRIVING / STABLE to CAUTION; data older than 30 minutes makes the rating UNAVAILABLE. The UI's freshness dot reads fresh ≤ 60 s, delayed ≤ 5 min, stale beyond.
“Analytical simulation from pool state” means: the pool's state is read at one block, and the venue's own swap function is evaluated locally for the requested size. It is not a firm quote from a router, and it is never interpolated — a size the pool cannot fill is reported as PARTIAL or INSUFFICIENT_LIQUIDITY.
The market page depth ladder, the scanner's price impact input and the compare page's spread and impact rows are all produced by this same code path.
A chain only supports a venue when its contract addresses are listed and the adapter re-verifies them at runtime (bytecode present, factory() bindings answer). Adding a chain is one entry in src/config/chains.ts: id, RPC endpoints, explorer, indexer and market-index hosts, Multicall3, quote tokens, listed tokens, route intermediates and the venue factories. Optional env overrides (RPC_URL_<id>, SNAPE_*) apply without a code change.
Mirror of .env.example. Anything prefixed NEXT_PUBLIC_ is inlined into the browser bundle; everything else stays on the server. Copy the file to .env.local; never commit real secrets.
Every live component carries a freshness marker: UPDATED n AGO · BLOCK #n with a green dot (fresh, ≤ 60 s), amber (delayed, ≤ 5 min) or red (stale). Hover the marker to see the sources that contributed. Indexed values carry the time they were stored by the index, not the time they were displayed.
Wallet
Wallet connection is optional and read-only in this product: it scopes the watchlist to an address. Injected wallets are discovered with EIP-6963 (multi-provider discovery through wagmi's injected connector); WalletConnect v2 is enabled only when NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID is set. Browser-side reads for every chain go through the same-origin relay /api/rpc/<chainId>, which only forwards an allow-list of read methods (eth_call, eth_getCode, eth_getLogs, balances, blocks, gas queries), so the RPC host and any key stay on the server. A wallet broadcasts through its own provider; the site never asks for a signature.
- No order book: the “spread” is a simulated round-trip cost on the deepest pool, not a bid/ask spread.
- 24h values depend on the market index. When it is silent, 24h change, volume and trade counts are unavailable and the health rating is capped at CAUTION.
- Single chain by default. The registry supports many chains; the shipped configuration lists one.
- Right after a cold start, listing ratings are preliminary until the background simulation batches (40 markets per listing request) have covered every market.
- Holder counts are unavailable — no key-free source publishes them for this chain, so the field prints Unavailable rather than a guess.
- V3 tick walks are bounded (a fixed maximum number of tick crossings); very large sizes on very sparse pools report PARTIAL.
- USD references for non-USD quotes come from the deepest WETH/USD pool of the same read; if none exists, depth is shown in quote units and USD values are unavailable.
- The tokenized-stock registry can be served from a dated snapshot when the live endpoint does not answer; the status is shown on /explorer.