Skip to content
Documentation

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.

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.

ProviderResponsibilityReads from
marketProviderComposes on-chain engine output with indexed context into the normalized shapes the UI renders (listing, market detail, scan, tape, token search). Leaves unreadable values undefined — never zero.engine, all adapters
liquidityProviderPools, quote-side depth, the depth ladder, the round-trip spread proxy and single trade simulations, all from pool state with the venue’s own swap math.liquidity adapters via engine/market
priceProviderIndexed price context (24h / 1h change, FDV, market cap) and OHLCV history. Spot price itself comes from pool state on chain.index/dexscreener, index/geckoterminal
volumeProvider24h volume, 24h trade counts and recent trades. Always labelled as indexed; never the only input to a health rating.index/dexscreener, index/geckoterminal
rpcProviderThe only path to a JSON-RPC node: endpoint failover, chain-id assertion, chain head, status probe and the browser relay allow-list.blockchain/client
AdapterWhat it does
liquidity/uniswapV2Uniswap V2-compatible pairs. Price and depth from getReserves(); exact-input quotes by constant product with the pair fee; execution encoded for UniswapV2Router02.
liquidity/uniswapV3Uniswap V3-compatible pools. Price from slot0.sqrtPriceX96, depth from the pool’s real token balances, quotes by walking the tick bitmap (or a configured QuoterV2); execution encoded for SwapRouter02.
liquidity/ponsCurvePons V2 launchpad bonding curves. Discovery is token-driven (token.curve()), accepted only when curve.factory() is a registered launch factory and curve.token() points back at the asset. Math mirrors the verified contract; depth is realQuoteReserve().
contracts/sourcifySourcify v2 API (primary verification source): verified-source match, contract name and compiler for the contract and, for proxies, its implementation. A 404 is a definite “no verified source”; transport failures are retried, never cached.
contracts/explorerBlockscout v2 API (secondary): address facts, source verification, contract name and compiler, proxy implementations. Indexed data, labelled as such; failures surface as EXPLORER_UNAVAILABLE.
contracts/registry · catalog · token · inspectOfficial tokenized-stock registry (live, stale or shipped snapshot), the token catalogue built from it plus configured tokens, batched ERC-20 reads and the contract inspector (bytecode, ERC-20 surface, owner, EIP-1967 / EIP-1822 proxy slots).
index/dexscreenerDexScreener-compatible market index: 24h change, volume, txns, pair creation time, logo. Zod-validated; malformed fields are dropped, not coerced.
index/geckoterminalGeckoTerminal-compatible pool indexer: OHLCV and trades, throttled through one paced queue; backup index when the market index is silent.

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.

  1. 01
    Registry → 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.
  2. 02
    Contract 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.
  3. 03
    Factory 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.
  4. 04
    Block-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.
  5. 05
    SimulationsOn 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.
  6. 06
    Index 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.
Never fabricated
  • · 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

InputStrongModerateWeakNone
Liquidity (USD reference, all registered pools)≥ $250,000≥ $50,000≥ $5,000< $5,000 or 0
Depth (quote side of the deepest single pool)≥ $100,000≥ $25,000≥ $2,500< $2,500 or 0
Activity by 24h trade count (preferred)≥ 200≥ 30≥ 5< 5
Activity by 24h volume (when no trade count)≥ $100,000≥ $10,000≥ $500< $500
InputLow / TightModerateHigh / WideSevere
Price impact of a $1,000 buy (simulated)≤ 0.3%≤ 1%≤ 3%> 3%
Spread proxy — round-trip cost at a small size (simulated)≤ 30 bps≤ 100 bps> 100 bps (wide)

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

ComponentWeightPoints by grade
Liquidity30Strong 1.00 · Moderate 0.66 · Weak 0.33 · None 0 · Unknown 0
Depth20Strong 1.00 · Moderate 0.66 · Weak 0.33 · None 0 · Unknown 0
Activity20Strong 1.00 · Moderate 0.66 · Weak 0.33 · None 0 · Unknown 0
Price impact20Low 1.00 · Moderate 0.66 · High 0.33 · Severe 0 · Unknown 0
Spread10Tight 1.00 · Moderate 0.60 · Wide 0.20 · Unknown 0
Total100score = Σ weight × points, rounded

Score bands

StatusScore
THRIVING≥ 75
STABLE≥ 55
CAUTION≥ 35
THIN< 35
UNAVAILABLENo pool on any registered venue, no measurable input at all, or data older than the stale limit

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.

VenueSimulation
Uniswap V2Constant product with the pair fee: amountOut = reserveOut × amountIn × (1 − fee) ÷ (reserveIn + amountIn × (1 − fee)). Mid price from the reserve ratio; impact = execution vs mid.
Uniswap V3Tick walk: starting from slot0, the swap is stepped across initialized ticks read from the pool’s tick bitmap, applying per-tick liquidity and the fee tier, up to a bounded number of crossings. A configured QuoterV2 is used instead when one is verified on the chain.
Pons curveThe bonding curve’s own buy / sell formulas on getReserves() (including the phantom quote reserve), with the curve fee and creator tax; depth is the quote physically held (realQuoteReserve()). A graduated curve is reported but never verified.

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.

ChainIdExplorerQuote tokensVenuesIndex / indexer
Robinhood Chain (default)4663Blockscout · explorer.mainnet.chain.robinhood.comUSDG (USD), WETHV2 ×1 · V3 ×1 (0.01%/0.05%/0.3%/1%) · Pons ×1DexScreener · GeckoTerminal

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.

VariableScopeMeaning
NEXT_PUBLIC_CHAIN_IDbrowserDefault chain the UI opens on. Must exist in src/config/chains.ts.
NEXT_PUBLIC_BLOCK_EXPLORER_URLbrowserPublic block explorer used for links. Unset = chain default.
NEXT_PUBLIC_SITE_URLbrowserCanonical site URL (og:url). Falls back to VERCEL_PROJECT_PRODUCTION_URL, then localhost.
NEXT_PUBLIC_WALLETCONNECT_PROJECT_IDbrowserWalletConnect Cloud project id. Empty = WalletConnect connector disabled; injected wallets still work.
NEXT_PUBLIC_USE_MOCK_DATAbrowserDeveloper fixtures. Must stay false in production; when true every value is stamped MOCK and a banner is printed on every page.
RPC_URLserverPrimary JSON-RPC endpoint for the default chain. Unset = the chain’s public endpoint.
RPC_FALLBACK_URLserverTried when the primary endpoint fails with a transport error (timeout / 5xx / 429).
RPC_URL_<chainId>serverPer-chain primary override; takes precedence over RPC_URL when the chain id matches (e.g. RPC_URL_4663).
RPC_FALLBACK_URL_<chainId>serverPer-chain fallback override.
RPC_RESOLVE_OVERRIDEserverLocal development only: host=ip,host=ip pins hostnames while keeping TLS SNI intact. Leave empty in production.
EXPLORER_API_URLserverBlockscout-compatible API base. Unset = chain default. Explicitly empty = explorer disabled.
EXPLORER_API_KEYserverOptional explorer API key.
SNAPE_TOKEN_ADDRESSserver$SNAPE contract address. Unset or SOON = the site prints “CA: SOON” and reads nothing; once set, the token panel is a live market read. Publish with npm run token:ca -- 0x… --vercel.
SOURCIFY_API_URLserverSourcify API base — the primary contract-verification source. Unset = https://sourcify.dev/server.
MARKET_API_URLserverDexScreener-compatible market index for 24h change / volume / trades. Unset = default. Empty = disabled.
MARKET_API_KEYserverOptional market index key (sent as x-api-key).
INDEXER_API_URLserverGeckoTerminal-compatible pool indexer for OHLCV history and recent trades. Unset = chain default. Empty = disabled.
INDEXER_API_KEYserverOptional pool indexer key.
SNAPE_V2_FACTORIESserverComma-separated lowercase Uniswap V2-compatible factory addresses for the default chain. Unset = chain defaults.
SNAPE_V2_ROUTERserverUniswapV2Router02-compatible router used for execution.
SNAPE_V3_FACTORIESserverUniswap V3-compatible factories.
SNAPE_V3_ROUTER02serverSwapRouter02-compatible router.
SNAPE_V3_QUOTERserverQuoterV2 address if a verified one exists. Unset = local tick-walk simulation.
SNAPE_PONS_FACTORIESserverPons V2 launchpad factories whose bonding curves are read.
SNAPE_STOCK_REGISTRY_URLserverOfficial tokenized-stock registry JSON. Unset = chain default; explicitly empty = shipped snapshot only.
SNAPE_QUOTE_TOKENSserverExtra quote tokens: SYMBOL:0xaddress:decimals[:usd].
SNAPE_LISTED_TOKENSserverExtra listed markets: SYMBOL:0xaddress:decimals.
StatePrinted asMeaning
LoadingLeaf loader / skeletonA request is in flight. The scanner shows the server pipeline stages while it waits.
No marketNo market detectedThe address is a valid ERC-20 but no pool exists on any registered venue.
No sourceNo liquidity source foundDiscovery ran but no factory answered with a pool.
Missing valueUnavailable / —The specific value could not be read or the index did not carry it. Never zero.
Provider downDATA SOURCE UNAVAILABLEThe RPC, explorer or index request failed; the message names the failure without a stack trace.
Not configuredLIVE DATA PROVIDER NOT CONFIGUREDNo RPC endpoint is configured for the chain.
Rate limitedToo many requestsThe per-IP token bucket on the API routes is exhausted; retry shortly.
FixturesMOCK bannerNEXT_PUBLIC_USE_MOCK_DATA=true — nothing on the page is market data.

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 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.