{"version":3,"file":"dexscreener-service.mjs","names":[],"sources":["../../../src/services/dexscreener-service.ts"],"sourcesContent":["/**\n * DexScreener Service — shared API client for token data.\n *\n * Centralizes all DexScreener calls. Previously duplicated across\n * defi-price.ts, defi-balance.ts, and market-intel.ts.\n */\n\nimport { guardedFetch } from './endpoint-allowlist.js';\nimport { getMarketCache, type CacheCategory } from './market-cache.js';\n\nconst BASE_URL = 'https://api.dexscreener.com';\n\nconst CHAIN_ALIASES: Record<string, string> = {\n  base: 'base',\n  ethereum: 'ethereum',\n  eth: 'ethereum',\n  arbitrum: 'arbitrum',\n  arb: 'arbitrum',\n  optimism: 'optimism',\n  op: 'optimism',\n  polygon: 'polygon',\n  matic: 'polygon',\n};\n\nexport function resolveChain(input: string): string {\n  return CHAIN_ALIASES[input.toLowerCase()] || input;\n}\n\n/**\n * Infer the cache category from the DexScreener API path.\n */\nfunction inferCacheCategory(path: string): CacheCategory {\n  if (path.includes('/token-boosts/') || path.includes('/trending')) return 'trending';\n  if (path.includes('/latest/dex/pairs/')) return 'new_pairs';\n  if (path.includes('/tokens/') || path.includes('/simple/price')) return 'token_price';\n  if (path.includes('/search')) return 'token_search';\n  if (path.includes('/profiles')) return 'token_profile';\n  return 'token_price'; // default\n}\n\nexport async function fetchDexScreener(path: string): Promise<any> {\n  const cache = getMarketCache();\n  const category = inferCacheCategory(path);\n\n  return cache.getOrFetch(category, `dexscreener:${path}`, async () => {\n    // H10: Add request timeout to prevent hanging\n    const response = await guardedFetch(`${BASE_URL}${path}`, {\n      headers: { Accept: 'application/json' },\n      signal: AbortSignal.timeout(15_000),\n    });\n    if (!response.ok) {\n      throw new Error(`DexScreener API error: ${response.status} ${response.statusText}`);\n    }\n    return response.json();\n  });\n}\n\n// ─── Common Queries ──────────────────────────────────────────────────────\n\nexport interface DexPairData {\n  pairAddress?: string;\n  chainId?: string;\n  dexId?: string;\n  url?: string;\n  baseToken?: { address?: string; symbol?: string; name?: string };\n  quoteToken?: { symbol?: string };\n  priceUsd?: string;\n  priceNative?: string;\n  priceChange?: { m5?: number; h1?: number; h6?: number; h24?: number };\n  volume?: { h1?: number; h6?: number; h24?: number };\n  txns?: { h1?: any; h6?: any; h24?: any };\n  liquidity?: { usd?: number };\n  marketCap?: number;\n  fdv?: number;\n  pairCreatedAt?: number;\n}\n\n/**\n * Search for a token by symbol, name, or address.\n * Returns pairs sorted by liquidity (highest first).\n */\nexport async function searchToken(\n  query: string,\n  chain?: string,\n): Promise<DexPairData[]> {\n  let data: any;\n\n  if (query.startsWith('0x') && query.length === 42 && chain) {\n    data = await fetchDexScreener(`/tokens/v1/${resolveChain(chain)}/${query}`);\n  } else {\n    data = await fetchDexScreener(`/latest/dex/search?q=${encodeURIComponent(query)}`);\n  }\n\n  const pairs: DexPairData[] = data?.pairs ?? (Array.isArray(data) ? data : []);\n\n  return pairs\n    .filter((p) => !chain || p.chainId === resolveChain(chain))\n    .sort((a, b) => (b.liquidity?.usd ?? 0) - (a.liquidity?.usd ?? 0));\n}\n\n/**\n * Get the USD price for a token. Returns 0 if not found.\n */\nexport async function getTokenPriceUsd(\n  query: string,\n  chain = 'base',\n): Promise<{ priceUsd: number; pair: DexPairData | null }> {\n  try {\n    const pairs = await searchToken(query, chain);\n    const top = pairs[0];\n    if (!top) return { priceUsd: 0, pair: null };\n    return { priceUsd: parseFloat(top.priceUsd ?? '0'), pair: top };\n  } catch {\n    return { priceUsd: 0, pair: null };\n  }\n}\n\n/**\n * Get ETH price in USD (via WETH/USDC pair on Base).\n */\nexport async function getEthPriceUsd(): Promise<number> {\n  try {\n    const data = await fetchDexScreener('/latest/dex/search?q=WETH%20USDC');\n    const basePair = data.pairs?.find(\n      (p: any) => p.chainId === 'base' && p.baseToken?.symbol === 'WETH',\n    );\n    return basePair ? parseFloat(basePair.priceUsd ?? '0') : 0;\n  } catch {\n    return 0;\n  }\n}\n\n/**\n * Get top boosted (trending) tokens.\n */\nexport async function getTrending(chain?: string, limit = 20): Promise<any[]> {\n  const data = await fetchDexScreener('/token-boosts/top/v1');\n  return (data ?? [])\n    .filter((t: any) => !chain || t.chainId === resolveChain(chain))\n    .slice(0, limit);\n}\n\n/**\n * Get latest pairs on a chain.\n */\nexport async function getNewPairs(chain = 'base', limit = 10): Promise<DexPairData[]> {\n  const data = await fetchDexScreener(`/latest/dex/pairs/${resolveChain(chain)}`);\n  return (data?.pairs ?? []).slice(0, limit);\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,WAAW;AAEjB,MAAM,gBAAwC;CAC5C,MAAM;CACN,UAAU;CACV,KAAK;CACL,UAAU;CACV,KAAK;CACL,UAAU;CACV,IAAI;CACJ,SAAS;CACT,OAAO;CACR;AAED,SAAgB,aAAa,OAAuB;AAClD,QAAO,cAAc,MAAM,aAAa,KAAK;;;;;AAM/C,SAAS,mBAAmB,MAA6B;AACvD,KAAI,KAAK,SAAS,iBAAiB,IAAI,KAAK,SAAS,YAAY,CAAE,QAAO;AAC1E,KAAI,KAAK,SAAS,qBAAqB,CAAE,QAAO;AAChD,KAAI,KAAK,SAAS,WAAW,IAAI,KAAK,SAAS,gBAAgB,CAAE,QAAO;AACxE,KAAI,KAAK,SAAS,UAAU,CAAE,QAAO;AACrC,KAAI,KAAK,SAAS,YAAY,CAAE,QAAO;AACvC,QAAO;;AAGT,eAAsB,iBAAiB,MAA4B;CACjE,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,WAAW,mBAAmB,KAAK;AAEzC,QAAO,MAAM,WAAW,UAAU,eAAe,QAAQ,YAAY;EAEnE,MAAM,WAAW,MAAM,aAAa,GAAG,WAAW,QAAQ;GACxD,SAAS,EAAE,QAAQ,oBAAoB;GACvC,QAAQ,YAAY,QAAQ,KAAO;GACpC,CAAC;AACF,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,0BAA0B,SAAS,OAAO,GAAG,SAAS,aAAa;AAErF,SAAO,SAAS,MAAM;GACtB;;;;;;AA2BJ,eAAsB,YACpB,OACA,OACwB;CACxB,IAAI;AAEJ,KAAI,MAAM,WAAW,KAAK,IAAI,MAAM,WAAW,MAAM,MACnD,QAAO,MAAM,iBAAiB,cAAc,aAAa,MAAM,CAAC,GAAG,QAAQ;KAE3E,QAAO,MAAM,iBAAiB,wBAAwB,mBAAmB,MAAM,GAAG;AAKpF,SAF6B,MAAM,UAAU,MAAM,QAAQ,KAAK,GAAG,OAAO,EAAE,GAGzE,QAAQ,MAAM,CAAC,SAAS,EAAE,YAAY,aAAa,MAAM,CAAC,CAC1D,MAAM,GAAG,OAAO,EAAE,WAAW,OAAO,MAAM,EAAE,WAAW,OAAO,GAAG;;;;;AAMtE,eAAsB,iBACpB,OACA,QAAQ,QACiD;AACzD,KAAI;EAEF,MAAM,OADQ,MAAM,YAAY,OAAO,MAAM,EAC3B;AAClB,MAAI,CAAC,IAAK,QAAO;GAAE,UAAU;GAAG,MAAM;GAAM;AAC5C,SAAO;GAAE,UAAU,WAAW,IAAI,YAAY,IAAI;GAAE,MAAM;GAAK;SACzD;AACN,SAAO;GAAE,UAAU;GAAG,MAAM;GAAM;;;;;;AAOtC,eAAsB,iBAAkC;AACtD,KAAI;EAEF,MAAM,YADO,MAAM,iBAAiB,mCAAmC,EACjD,OAAO,MAC1B,MAAW,EAAE,YAAY,UAAU,EAAE,WAAW,WAAW,OAC7D;AACD,SAAO,WAAW,WAAW,SAAS,YAAY,IAAI,GAAG;SACnD;AACN,SAAO;;;;;;AAOX,eAAsB,YAAY,OAAgB,QAAQ,IAAoB;AAE5E,SADa,MAAM,iBAAiB,uBAAuB,IAC3C,EAAE,EACf,QAAQ,MAAW,CAAC,SAAS,EAAE,YAAY,aAAa,MAAM,CAAC,CAC/D,MAAM,GAAG,MAAM;;;;;AAMpB,eAAsB,YAAY,QAAQ,QAAQ,QAAQ,IAA4B;AAEpF,UADa,MAAM,iBAAiB,qBAAqB,aAAa,MAAM,GAAG,GACjE,SAAS,EAAE,EAAE,MAAM,GAAG,MAAM"}