// DexScreener trends: most-boosted tokens with live market data. Zero dependencies. import { pathToFileURL } from "node:url"; const API = "https://api.dexscreener.com"; async function getJson(url) { const res = await fetch(url, { headers: { accept: "application/json" } }); if (!res.ok) throw new Error(`DexScreener answered ${res.status} for ${new URL(url).pathname}`); return res.json(); } function num(v) { const n = Number(v); return Number.isFinite(n) ? n : null; } export async function trends({ chain = null, limit = 10 } = {}) { limit = Math.max(1, Math.min(30, Number(limit) || 10)); const boosts = await getJson(`${API}/token-boosts/top/v1`); if (!Array.isArray(boosts)) throw new Error("Unexpected boosts response"); const picked = []; const seen = new Set(); for (const b of boosts) { if (!b?.chainId || !b?.tokenAddress) continue; if (chain && b.chainId !== chain) continue; const key = b.chainId + ":" + b.tokenAddress; if (seen.has(key)) continue; seen.add(key); picked.push(b); if (picked.length >= limit) break; } // One request per chain for up to 30 addresses: the most liquid pair of each token. const best = new Map(); const byChain = new Map(); for (const b of picked) byChain.set(b.chainId, [...(byChain.get(b.chainId) ?? []), b.tokenAddress]); for (const [chainId, addrs] of byChain) { const pairs = await getJson(`${API}/tokens/v1/${chainId}/${addrs.join(",")}`); for (const p of Array.isArray(pairs) ? pairs : []) { const addr = p?.baseToken?.address; if (!addr) continue; const key = chainId + ":" + addr; const prev = best.get(key); if (!prev || (num(p.liquidity?.usd) ?? 0) > (num(prev.liquidity?.usd) ?? 0)) best.set(key, p); } } const tokens = picked.map((b, i) => { const p = best.get(b.chainId + ":" + b.tokenAddress); return { rank: i + 1, chainId: b.chainId, address: b.tokenAddress, symbol: p?.baseToken?.symbol ?? null, name: p?.baseToken?.name ?? null, boosts: num(b.totalAmount) ?? num(b.amount) ?? 0, priceUsd: num(p?.priceUsd), change24hPct: num(p?.priceChange?.h24), volume24hUsd: num(p?.volume?.h24), liquidityUsd: num(p?.liquidity?.usd), marketCapUsd: num(p?.marketCap), pairUrl: p?.url ?? b.url ?? null, }; }); return { source: "dexscreener", fetchedAt: new Date().toISOString(), chain: chain ?? "all", tokens }; } function parseArgs(argv) { const out = {}; for (let i = 0; i < argv.length; i++) { if (argv[i] === "--chain") out.chain = argv[++i]; else if (argv[i] === "--limit") out.limit = Number(argv[++i]); } return out; } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { trends(parseArgs(process.argv.slice(2))) .then((r) => console.log(JSON.stringify(r, null, 2))) .catch((e) => { console.log(JSON.stringify({ error: String(e?.message ?? e) })); process.exit(1); }); }