Files
lp-terminal_github/indexer/stats.ts
T
labrinyang 4e317eff6d LP TERMINAL — terminal-style LP frontend for Robinhood Chain
Static SPA over UP33 (ve(3,3)) + the official Uniswap v2/v3 deployments:
browse and add liquidity across all three, manage CL/v2 positions, one-token
ZAP adds, aggregator vs native swaps, and LP-based limit orders. Ships with a
zero-dependency pool indexer for uniswap discovery.

Browser-wallet signing only; no key material anywhere.
2026-07-17 19:10:30 +08:00

92 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// GeckoTerminal enrichment — volume/liquidity/txn stats + token USD price
// seeds for the pricing waterfall. GT fully covers this chain's Uniswap
// deployments (network `robinhood`, per-dex top lists) but each list is capped
// at 10 pages × 20 = top 200 — the long tail keeps chain-derived TVL only.
// Free tier is 30 calls/min: calls are paced ≥ TUNE.gtPaceMs apart and the
// whole cycle (≤30 calls) runs every TUNE.statsMs.
//
// NOTE: GT has no UP33 dex entry — UP33 pool stats stay on the frontend's
// existing dexscreener path; this indexer only serves the Uniswap catalog.
import { GT, TUNE, log, sleep } from './config'
import { poolRow, setTokenPrice, upsertStats } from './store'
const LISTS = [
{ path: '/networks/robinhood/pools', label: 'network' },
{ path: '/networks/robinhood/dexes/uniswap-v2-robinhood/pools', label: 'uni-v2' },
{ path: '/networks/robinhood/dexes/uniswap-v3-robinhood/pools', label: 'uni-v3' },
]
type GtPool = {
attributes?: {
address?: string
reserve_in_usd?: string
volume_usd?: { h24?: string }
transactions?: { h24?: { buys?: number; sells?: number } }
base_token_price_usd?: string
quote_token_price_usd?: string
}
relationships?: {
base_token?: { data?: { id?: string } }
quote_token?: { data?: { id?: string } }
}
}
let lastCall = 0
async function gtJson(url: string): Promise<{ data?: GtPool[] } | null> {
const wait = lastCall + TUNE.gtPaceMs - Date.now()
if (wait > 0) await sleep(wait)
lastCall = Date.now()
try {
const r = await fetch(url, { headers: { accept: 'application/json', 'user-agent': 'up33-lp-indexer/0.1' } })
if (!r.ok) return null
return (await r.json()) as { data?: GtPool[] }
} catch {
return null
}
}
const num = (x: unknown): number | null => {
const n = Number(x)
return Number.isFinite(n) ? n : null
}
const tokenOfId = (id?: string): string | null =>
id?.startsWith('robinhood_0x') ? id.slice('robinhood_'.length).toLowerCase() : null
function ingest(p: GtPool): boolean {
const a = p.attributes
const addr = a?.address?.toLowerCase()
if (!a || !addr || !poolRow(addr)) return false // catalog is the gate — unknown pools are ignored
const reserve = num(a.reserve_in_usd)
const h24 = a.transactions?.h24
const txns = h24 ? (h24.buys ?? 0) + (h24.sells ?? 0) : null
upsertStats(addr, num(a.volume_usd?.h24), txns, reserve, 'geckoterminal')
// token price seeds: ground truth while fresh; depth = half the pool's reserve
const depth = (reserve ?? 0) / 2
if (depth > 0) {
const base = tokenOfId(p.relationships?.base_token?.data?.id)
const quote = tokenOfId(p.relationships?.quote_token?.data?.id)
const bp = num(a.base_token_price_usd)
const qp = num(a.quote_token_price_usd)
if (base && bp && bp > 0) setTokenPrice(base, bp, depth, 'gt')
if (quote && qp && qp > 0) setTokenPrice(quote, qp, depth, 'gt')
}
return true
}
/** one enrichment cycle over the three GT top lists */
export async function gtCycle(): Promise<void> {
let matched = 0
let seen = 0
for (const list of LISTS) {
for (let page = 1; page <= 10; page++) {
const j = await gtJson(`${GT}${list.path}?page=${page}`)
const items = j?.data
if (!items?.length) break
seen += items.length
for (const it of items) if (ingest(it)) matched++
if (items.length < 20) break
}
}
log(`[stats] gt cycle: ${matched}/${seen} list entries matched catalog`)
}