{"version":3,"file":"price-service.mjs","names":[],"sources":["../../../src/services/price-service.ts"],"sourcesContent":["/**\n * Price Service — unified price feed shared across tools.\n *\n * Used by manage-orders (trigger checks), defi-swap (pre-swap display),\n * defi-balance (ETH valuation), and the workflow orchestrator.\n *\n * Sources: DexScreener (primary), CoinGecko (fallback).\n * Includes a TTL cache to avoid hammering APIs during multi-tool workflows.\n */\n\nimport { getTokenPriceUsd, getEthPriceUsd, searchToken, type DexPairData } from './dexscreener-service.js';\n\n// ─── Price Cache (30s TTL) ───────────────────────────────────────────────\n\ninterface CacheEntry {\n  priceUsd: number;\n  pair: DexPairData | null;\n  fetchedAt: number;\n}\n\nconst CACHE_TTL_MS = 30_000;\nconst MAX_CACHE_SIZE = 500;\nconst _cache = new Map<string, CacheEntry>();\n\nfunction cacheKey(token: string, chain: string): string {\n  return `${chain}:${token.toLowerCase()}`;\n}\n\nfunction getCached(token: string, chain: string): CacheEntry | null {\n  const entry = _cache.get(cacheKey(token, chain));\n  if (!entry) return null;\n  if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {\n    _cache.delete(cacheKey(token, chain));\n    return null;\n  }\n  return entry;\n}\n\nfunction setCache(token: string, chain: string, entry: Omit<CacheEntry, 'fetchedAt'>): void {\n  // Evict expired entries when cache exceeds size limit\n  if (_cache.size >= MAX_CACHE_SIZE) {\n    const now = Date.now();\n    for (const [key, val] of _cache) {\n      if (now - val.fetchedAt > CACHE_TTL_MS) _cache.delete(key);\n    }\n    // If still over, remove oldest entries\n    if (_cache.size >= MAX_CACHE_SIZE) {\n      const toDelete = _cache.size - MAX_CACHE_SIZE + 50;\n      let deleted = 0;\n      for (const key of _cache.keys()) {\n        if (deleted >= toDelete) break;\n        _cache.delete(key);\n        deleted++;\n      }\n    }\n  }\n  _cache.set(cacheKey(token, chain), { ...entry, fetchedAt: Date.now() });\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\nexport interface PriceResult {\n  priceUsd: number;\n  priceEth: number;\n  symbol: string;\n  name: string;\n  address?: string;\n  change24h?: number;\n  volume24h?: number;\n  liquidity?: number;\n  source: 'dexscreener' | 'coingecko' | 'cache';\n}\n\n/**\n * Get current price for any token. Cached for 30s.\n */\nexport async function getPrice(token: string, chain = 'base'): Promise<PriceResult> {\n  // Check cache first\n  const cached = getCached(token, chain);\n  if (cached) {\n    const ethPrice = await getEthPrice();\n    return {\n      priceUsd: cached.priceUsd,\n      priceEth: ethPrice > 0 ? cached.priceUsd / ethPrice : 0,\n      symbol: cached.pair?.baseToken?.symbol ?? token,\n      name: cached.pair?.baseToken?.name ?? '',\n      address: cached.pair?.baseToken?.address,\n      change24h: cached.pair?.priceChange?.h24,\n      volume24h: cached.pair?.volume?.h24,\n      liquidity: cached.pair?.liquidity?.usd,\n      source: 'cache',\n    };\n  }\n\n  // Fresh fetch\n  const { priceUsd, pair } = await getTokenPriceUsd(token, chain);\n  setCache(token, chain, { priceUsd, pair });\n\n  const ethPrice = await getEthPrice();\n\n  return {\n    priceUsd,\n    priceEth: ethPrice > 0 ? priceUsd / ethPrice : 0,\n    symbol: pair?.baseToken?.symbol ?? token,\n    name: pair?.baseToken?.name ?? '',\n    address: pair?.baseToken?.address,\n    change24h: pair?.priceChange?.h24,\n    volume24h: pair?.volume?.h24,\n    liquidity: pair?.liquidity?.usd,\n    source: 'dexscreener',\n  };\n}\n\n/**\n * Get ETH price in USD. Cached.\n */\nlet _ethPriceCache: { price: number; at: number } | null = null;\n\nexport async function getEthPrice(): Promise<number> {\n  if (_ethPriceCache && Date.now() - _ethPriceCache.at < CACHE_TTL_MS) {\n    return _ethPriceCache.price;\n  }\n  const price = await getEthPriceUsd();\n  _ethPriceCache = { price, at: Date.now() };\n  return price;\n}\n\n/**\n * Get prices for multiple tokens at once (batched, cached).\n */\nexport async function getPrices(\n  tokens: Array<{ token: string; chain?: string }>,\n): Promise<Map<string, PriceResult>> {\n  const results = new Map<string, PriceResult>();\n  await Promise.all(\n    tokens.map(async ({ token, chain }) => {\n      try {\n        const price = await getPrice(token, chain ?? 'base');\n        results.set(token, price);\n      } catch {\n        // Skip failures\n      }\n    }),\n  );\n  return results;\n}\n\n/**\n * Clear the price cache. Useful for testing.\n */\nexport function clearPriceCache(): void {\n  _cache.clear();\n  _ethPriceCache = null;\n}\n"],"mappings":";;;;;;;;;;;AAoBA,MAAM,eAAe;AACrB,MAAM,iBAAiB;AACvB,MAAM,yBAAS,IAAI,KAAyB;AAE5C,SAAS,SAAS,OAAe,OAAuB;AACtD,QAAO,GAAG,MAAM,GAAG,MAAM,aAAa;;AAGxC,SAAS,UAAU,OAAe,OAAkC;CAClE,MAAM,QAAQ,OAAO,IAAI,SAAS,OAAO,MAAM,CAAC;AAChD,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,KAAK,KAAK,GAAG,MAAM,YAAY,cAAc;AAC/C,SAAO,OAAO,SAAS,OAAO,MAAM,CAAC;AACrC,SAAO;;AAET,QAAO;;AAGT,SAAS,SAAS,OAAe,OAAe,OAA4C;AAE1F,KAAI,OAAO,QAAQ,gBAAgB;EACjC,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,MAAM,CAAC,KAAK,QAAQ,OACvB,KAAI,MAAM,IAAI,YAAY,aAAc,QAAO,OAAO,IAAI;AAG5D,MAAI,OAAO,QAAQ,gBAAgB;GACjC,MAAM,WAAW,OAAO,OAAO,iBAAiB;GAChD,IAAI,UAAU;AACd,QAAK,MAAM,OAAO,OAAO,MAAM,EAAE;AAC/B,QAAI,WAAW,SAAU;AACzB,WAAO,OAAO,IAAI;AAClB;;;;AAIN,QAAO,IAAI,SAAS,OAAO,MAAM,EAAE;EAAE,GAAG;EAAO,WAAW,KAAK,KAAK;EAAE,CAAC;;;;;AAoBzE,eAAsB,SAAS,OAAe,QAAQ,QAA8B;CAElF,MAAM,SAAS,UAAU,OAAO,MAAM;AACtC,KAAI,QAAQ;EACV,MAAM,WAAW,MAAM,aAAa;AACpC,SAAO;GACL,UAAU,OAAO;GACjB,UAAU,WAAW,IAAI,OAAO,WAAW,WAAW;GACtD,QAAQ,OAAO,MAAM,WAAW,UAAU;GAC1C,MAAM,OAAO,MAAM,WAAW,QAAQ;GACtC,SAAS,OAAO,MAAM,WAAW;GACjC,WAAW,OAAO,MAAM,aAAa;GACrC,WAAW,OAAO,MAAM,QAAQ;GAChC,WAAW,OAAO,MAAM,WAAW;GACnC,QAAQ;GACT;;CAIH,MAAM,EAAE,UAAU,SAAS,MAAM,iBAAiB,OAAO,MAAM;AAC/D,UAAS,OAAO,OAAO;EAAE;EAAU;EAAM,CAAC;CAE1C,MAAM,WAAW,MAAM,aAAa;AAEpC,QAAO;EACL;EACA,UAAU,WAAW,IAAI,WAAW,WAAW;EAC/C,QAAQ,MAAM,WAAW,UAAU;EACnC,MAAM,MAAM,WAAW,QAAQ;EAC/B,SAAS,MAAM,WAAW;EAC1B,WAAW,MAAM,aAAa;EAC9B,WAAW,MAAM,QAAQ;EACzB,WAAW,MAAM,WAAW;EAC5B,QAAQ;EACT;;;;;AAMH,IAAI,iBAAuD;AAE3D,eAAsB,cAA+B;AACnD,KAAI,kBAAkB,KAAK,KAAK,GAAG,eAAe,KAAK,aACrD,QAAO,eAAe;CAExB,MAAM,QAAQ,MAAM,gBAAgB;AACpC,kBAAiB;EAAE;EAAO,IAAI,KAAK,KAAK;EAAE;AAC1C,QAAO;;;;;AAMT,eAAsB,UACpB,QACmC;CACnC,MAAM,0BAAU,IAAI,KAA0B;AAC9C,OAAM,QAAQ,IACZ,OAAO,IAAI,OAAO,EAAE,OAAO,YAAY;AACrC,MAAI;GACF,MAAM,QAAQ,MAAM,SAAS,OAAO,SAAS,OAAO;AACpD,WAAQ,IAAI,OAAO,MAAM;UACnB;GAGR,CACH;AACD,QAAO;;;;;AAMT,SAAgB,kBAAwB;AACtC,QAAO,OAAO;AACd,kBAAiB"}