mirror of
https://github.com/labrinyang/lp-terminal.git
synced 2026-07-27 21:27:43 +00:00
4e317eff6d
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.
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
// 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`)
|
||
}
|