{"version":3,"file":"dex-aggregator.mjs","names":[],"sources":["../../../src/services/dex-aggregator.ts"],"sourcesContent":["/**\n * DEX Aggregator — multi-source swap quoting with best-price selection.\n *\n * Queries multiple DEX aggregators in parallel and returns the best quote\n * (factoring in output amount and gas cost). Falls back gracefully if any\n * aggregator is unavailable.\n *\n * Supported aggregators:\n * - 0x (current default)\n * - 1inch Fusion\n * - ParaSwap\n * - CowSwap (MEV-protected)\n * - Odos (multi-hop optimization)\n * - KyberSwap\n * - OpenOcean\n */\n\nimport { guardedFetch } from './endpoint-allowlist.js';\nimport { getCredentialVault } from './credential-vault.js';\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface SwapQuote {\n  aggregator: string;\n  sellToken: string;\n  buyToken: string;\n  sellAmount: string;\n  buyAmount: string;\n  price: number;        // buyAmount / sellAmount in human-readable units\n  gasEstimate: string;\n  gasPrice?: string;\n  gasCostUsd?: number;\n  netOutputUsd?: number; // buyAmount value - gas cost\n  route?: string;        // human-readable route description\n  data?: unknown;        // raw aggregator response for execution\n  error?: string;        // if this aggregator failed\n}\n\nexport interface AggregatorConfig {\n  enabled?: boolean;\n  apiKeyEnv?: string;\n  baseUrl: string;\n}\n\nexport interface DexAggregatorConfig {\n  /** Which aggregators to use. Default: all available. */\n  aggregators?: Partial<Record<string, AggregatorConfig>>;\n  /** Chain ID. Default: 8453 (Base). */\n  chainId?: number;\n  /** Slippage in basis points. Default: 50 (0.5%). */\n  slippageBps?: number;\n  /** Quote timeout per aggregator in ms. Default: 5000. */\n  timeoutMs?: number;\n}\n\n// ── Default Aggregator Configs ──────────────────────────────────────────────\n\nconst DEFAULT_AGGREGATORS: Record<string, AggregatorConfig> = {\n  '0x': {\n    enabled: true,\n    apiKeyEnv: 'ZEROX_API_KEY',\n    baseUrl: 'https://api.0x.org',\n  },\n  '1inch': {\n    enabled: true,\n    apiKeyEnv: 'ONEINCH_API_KEY',\n    baseUrl: 'https://api.1inch.dev',\n  },\n  paraswap: {\n    enabled: true,\n    baseUrl: 'https://apiv5.paraswap.io',\n  },\n  odos: {\n    enabled: true,\n    baseUrl: 'https://api.odos.xyz',\n  },\n  kyberswap: {\n    enabled: true,\n    baseUrl: 'https://aggregator-api.kyberswap.com',\n  },\n  openocean: {\n    enabled: true,\n    baseUrl: 'https://open-api.openocean.finance',\n  },\n};\n\nconst CHAIN_SLUG: Record<number, Record<string, string>> = {\n  8453: { '0x': 'base', '1inch': '8453', paraswap: '8453', odos: '8453', kyberswap: 'base', openocean: 'base' },\n  1: { '0x': 'ethereum', '1inch': '1', paraswap: '1', odos: '1', kyberswap: 'ethereum', openocean: 'eth' },\n  42161: { '0x': 'arbitrum', '1inch': '42161', paraswap: '42161', odos: '42161', kyberswap: 'arbitrum', openocean: 'arbitrum' },\n  10: { '0x': 'optimism', '1inch': '10', paraswap: '10', odos: '10', kyberswap: 'optimism', openocean: 'optimism' },\n  137: { '0x': 'polygon', '1inch': '137', paraswap: '137', odos: '137', kyberswap: 'polygon', openocean: 'polygon' },\n};\n\n// ── Individual Aggregator Fetchers ──────────────────────────────────────────\n\nasync function fetchQuote0x(\n  sellToken: string,\n  buyToken: string,\n  sellAmount: string,\n  chainId: number,\n  slippageBps: number,\n  timeoutMs: number,\n): Promise<SwapQuote> {\n  const apiKey = getCredentialVault().getSecret('dex.0x.apiKey', 'dex-aggregator');\n  const chain = CHAIN_SLUG[chainId]?.['0x'] ?? 'base';\n\n  const params = new URLSearchParams({\n    sellToken,\n    buyToken,\n    sellAmount,\n    slippagePercentage: String(slippageBps / 10000),\n  });\n\n  const headers: Record<string, string> = { Accept: 'application/json' };\n  if (apiKey) headers['0x-api-key'] = apiKey;\n\n  const resp = await guardedFetch(\n    `https://api.0x.org/swap/v1/quote?${params}`,\n    { headers, signal: AbortSignal.timeout(timeoutMs) },\n  );\n\n  if (!resp.ok) throw new Error(`0x: ${resp.status} ${await resp.text()}`);\n  const data = (await resp.json()) as any;\n\n  return {\n    aggregator: '0x',\n    sellToken,\n    buyToken,\n    sellAmount,\n    buyAmount: data.buyAmount ?? '0',\n    price: parseFloat(data.price ?? '0'),\n    gasEstimate: data.estimatedGas ?? '0',\n    gasPrice: data.gasPrice,\n    route: data.sources?.filter((s: any) => parseFloat(s.proportion) > 0)\n      .map((s: any) => s.name).join(' → ') ?? '0x',\n    data,\n  };\n}\n\n/** Well-known token decimals by address (lowercase) to avoid RPC calls in aggregator. */\nconst TOKEN_DECIMALS: Record<string, number> = {\n  // Base\n  '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913': 6,  // USDC (Base)\n  '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2': 6,  // USDT (Base)\n  // Ethereum\n  '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 6,  // USDC (ETH)\n  '0xdac17f958d2ee523a2206206994597c13d831ec7': 6,  // USDT (ETH)\n  // Polygon\n  '0x2791bca1f2de4661ed88a30c99a7a9449aa84174': 6,  // USDC (Polygon)\n  '0xc2132d05d31c914a87c6611c10748aeb04b58e8f': 6,  // USDT (Polygon)\n  // Arbitrum\n  '0xaf88d065e77c8cc2239327c5edb3a432268e5831': 6,  // USDC (Arbitrum)\n  '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9': 6,  // USDT (Arbitrum)\n  // Native ETH sentinel\n  '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee': 18,\n};\n\nfunction getKnownDecimals(token: string): number {\n  return TOKEN_DECIMALS[token.toLowerCase()] ?? 18;\n}\n\nasync function fetchQuoteParaSwap(\n  sellToken: string,\n  buyToken: string,\n  sellAmount: string,\n  chainId: number,\n  slippageBps: number,\n  timeoutMs: number,\n): Promise<SwapQuote> {\n  const params = new URLSearchParams({\n    srcToken: sellToken,\n    destToken: buyToken,\n    amount: sellAmount,\n    srcDecimals: String(getKnownDecimals(sellToken)),\n    destDecimals: String(getKnownDecimals(buyToken)),\n    side: 'SELL',\n    network: String(chainId),\n  });\n\n  const resp = await guardedFetch(\n    `https://apiv5.paraswap.io/prices?${params}`,\n    { signal: AbortSignal.timeout(timeoutMs) },\n  );\n\n  if (!resp.ok) throw new Error(`ParaSwap: ${resp.status} ${await resp.text()}`);\n  const data = (await resp.json()) as any;\n  const best = data.priceRoute;\n\n  return {\n    aggregator: 'ParaSwap',\n    sellToken,\n    buyToken,\n    sellAmount,\n    buyAmount: best?.destAmount ?? '0',\n    price: parseFloat(best?.destAmount ?? '0') / (parseFloat(sellAmount) || 1),\n    gasEstimate: best?.gasCost ?? '0',\n    route: best?.bestRoute?.[0]?.swaps?.map((s: any) => s.swapExchanges?.[0]?.exchange).join(' → ') ?? 'ParaSwap',\n    data: best,\n  };\n}\n\nasync function fetchQuoteOdos(\n  sellToken: string,\n  buyToken: string,\n  sellAmount: string,\n  chainId: number,\n  slippageBps: number,\n  timeoutMs: number,\n): Promise<SwapQuote> {\n  const resp = await guardedFetch('https://api.odos.xyz/sor/quote/v2', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      chainId,\n      inputTokens: [{ tokenAddress: sellToken, amount: sellAmount }],\n      outputTokens: [{ tokenAddress: buyToken, proportion: 1 }],\n      slippageLimitPercent: slippageBps / 100,\n      userAddr: '0x0000000000000000000000000000000000000000',\n    }),\n    signal: AbortSignal.timeout(timeoutMs),\n  });\n\n  if (!resp.ok) throw new Error(`Odos: ${resp.status} ${await resp.text()}`);\n  const data = (await resp.json()) as any;\n\n  return {\n    aggregator: 'Odos',\n    sellToken,\n    buyToken,\n    sellAmount,\n    buyAmount: data.outAmounts?.[0] ?? '0',\n    price: parseFloat(data.outAmounts?.[0] ?? '0') / (parseFloat(sellAmount) || 1),\n    gasEstimate: String(data.gasEstimate ?? 0),\n    gasCostUsd: data.gasEstimateValue,\n    netOutputUsd: (data.outValues?.[0] ?? 0) - (data.gasEstimateValue ?? 0),\n    route: `Odos (${data.pathViz?.length ?? 0} hops)`,\n    data,\n  };\n}\n\nasync function fetchQuoteKyber(\n  sellToken: string,\n  buyToken: string,\n  sellAmount: string,\n  chainId: number,\n  slippageBps: number,\n  timeoutMs: number,\n): Promise<SwapQuote> {\n  const chain = CHAIN_SLUG[chainId]?.kyberswap ?? 'base';\n  const params = new URLSearchParams({\n    tokenIn: sellToken,\n    tokenOut: buyToken,\n    amountIn: sellAmount,\n    saveGas: '0',\n    gasInclude: '1',\n  });\n\n  const resp = await guardedFetch(\n    `https://aggregator-api.kyberswap.com/${chain}/api/v1/routes?${params}`,\n    { signal: AbortSignal.timeout(timeoutMs) },\n  );\n\n  if (!resp.ok) throw new Error(`KyberSwap: ${resp.status} ${await resp.text()}`);\n  const data = (await resp.json()) as any;\n  const best = data.data?.routeSummary;\n\n  return {\n    aggregator: 'KyberSwap',\n    sellToken,\n    buyToken,\n    sellAmount,\n    buyAmount: best?.amountOut ?? '0',\n    price: parseFloat(best?.amountOut ?? '0') / (parseFloat(sellAmount) || 1),\n    gasEstimate: best?.gas ?? '0',\n    gasCostUsd: parseFloat(best?.gasUsd ?? '0'),\n    route: `KyberSwap (${best?.route?.length ?? 0} routes)`,\n    data: best,\n  };\n}\n\nasync function fetchQuote1inch(\n  sellToken: string,\n  buyToken: string,\n  sellAmount: string,\n  chainId: number,\n  slippageBps: number,\n  timeoutMs: number,\n): Promise<SwapQuote> {\n  const apiKey = getCredentialVault().getSecret('dex.1inch.apiKey', 'dex-aggregator');\n  if (!apiKey) throw new Error('1inch: ONEINCH_API_KEY not set');\n\n  const params = new URLSearchParams({\n    src: sellToken,\n    dst: buyToken,\n    amount: sellAmount,\n    slippage: String(slippageBps / 100), // 1inch uses percent, not bps\n    includeGas: 'true',\n  });\n\n  const resp = await guardedFetch(\n    `https://api.1inch.dev/swap/v6.0/${chainId}/quote?${params}`,\n    {\n      headers: {\n        Authorization: `Bearer ${apiKey}`,\n        Accept: 'application/json',\n      },\n      signal: AbortSignal.timeout(timeoutMs),\n    },\n  );\n\n  if (!resp.ok) throw new Error(`1inch: ${resp.status} ${await resp.text()}`);\n  const data = (await resp.json()) as any;\n\n  return {\n    aggregator: '1inch',\n    sellToken,\n    buyToken,\n    sellAmount,\n    buyAmount: data.dstAmount ?? '0',\n    price: parseFloat(data.dstAmount ?? '0') / (parseFloat(sellAmount) || 1),\n    gasEstimate: String(data.gas ?? 0),\n    route: data.protocols?.flat()?.flat()?.map((p: any) => p.name).filter(Boolean).join(' → ') || '1inch',\n    data,\n  };\n}\n\nasync function fetchQuoteOpenOcean(\n  sellToken: string,\n  buyToken: string,\n  sellAmount: string,\n  chainId: number,\n  slippageBps: number,\n  timeoutMs: number,\n): Promise<SwapQuote> {\n  const chain = CHAIN_SLUG[chainId]?.openocean ?? 'base';\n\n  const params = new URLSearchParams({\n    inTokenAddress: sellToken,\n    outTokenAddress: buyToken,\n    amount: sellAmount,\n    slippage: String(slippageBps / 100),\n    gasPrice: '5', // placeholder; OpenOcean needs gas price\n    account: '0x0000000000000000000000000000000000000000',\n  });\n\n  const resp = await guardedFetch(\n    `https://open-api.openocean.finance/v4/${chain}/quote?${params}`,\n    { signal: AbortSignal.timeout(timeoutMs) },\n  );\n\n  if (!resp.ok) throw new Error(`OpenOcean: ${resp.status} ${await resp.text()}`);\n  const data = (await resp.json()) as any;\n  const result = data.data;\n\n  return {\n    aggregator: 'OpenOcean',\n    sellToken,\n    buyToken,\n    sellAmount,\n    buyAmount: result?.outAmount ?? '0',\n    price: parseFloat(result?.outAmount ?? '0') / (parseFloat(sellAmount) || 1),\n    gasEstimate: String(result?.estimatedGas ?? 0),\n    gasCostUsd: result?.estimatedGasUsd ? parseFloat(result.estimatedGasUsd) : undefined,\n    route: result?.dexes?.map((d: any) => d.dex).join(' → ') || 'OpenOcean',\n    data: result,\n  };\n}\n\n// ── Aggregator Manager ──────────────────────────────────────────────────────\n\nexport class DexAggregator {\n  private chainId: number;\n  private slippageBps: number;\n  private timeoutMs: number;\n  private aggregators: Record<string, AggregatorConfig>;\n\n  constructor(config: DexAggregatorConfig = {}) {\n    this.chainId = config.chainId ?? 8453;\n    this.slippageBps = config.slippageBps ?? 50;\n    this.timeoutMs = config.timeoutMs ?? 5000;\n\n    // Merge user config with defaults\n    this.aggregators = { ...DEFAULT_AGGREGATORS };\n    if (config.aggregators) {\n      for (const [name, cfg] of Object.entries(config.aggregators)) {\n        if (cfg) {\n          this.aggregators[name] = { ...this.aggregators[name]!, ...cfg };\n        }\n      }\n    }\n  }\n\n  /** Get the list of enabled aggregator names. */\n  getEnabled(): string[] {\n    return Object.entries(this.aggregators)\n      .filter(([_, cfg]) => cfg.enabled !== false)\n      .filter(([_, cfg]) => !cfg.apiKeyEnv || process.env[cfg.apiKeyEnv!])\n      .map(([name]) => name);\n  }\n\n  /**\n   * Get quotes from all enabled aggregators in parallel.\n   * Returns all results (including errors) sorted by best output.\n   */\n  async getQuotes(\n    sellToken: string,\n    buyToken: string,\n    sellAmount: string,\n    chainId?: number,\n  ): Promise<SwapQuote[]> {\n    const chain = chainId ?? this.chainId;\n    const fetchers: Array<Promise<SwapQuote>> = [];\n\n    const enabled = this.getEnabled();\n\n    for (const name of enabled) {\n      const fetcher = this.fetchSingle(name, sellToken, buyToken, sellAmount, chain)\n        .catch((err): SwapQuote => ({\n          aggregator: name,\n          sellToken,\n          buyToken,\n          sellAmount,\n          buyAmount: '0',\n          price: 0,\n          gasEstimate: '0',\n          error: err instanceof Error ? err.message : String(err),\n        }));\n      fetchers.push(fetcher);\n    }\n\n    const results = await Promise.all(fetchers);\n\n    // Sort by buyAmount descending (best output first), errors last\n    return results.sort((a, b) => {\n      if (a.error && !b.error) return 1;\n      if (!a.error && b.error) return -1;\n      return BigInt(b.buyAmount || '0') > BigInt(a.buyAmount || '0') ? 1 : -1;\n    });\n  }\n\n  /**\n   * Get the single best quote across all aggregators.\n   * Optionally factors in gas cost (if netOutputUsd is available).\n   */\n  async getBestQuote(\n    sellToken: string,\n    buyToken: string,\n    sellAmount: string,\n    chainId?: number,\n  ): Promise<SwapQuote> {\n    const quotes = await this.getQuotes(sellToken, buyToken, sellAmount, chainId);\n    const valid = quotes.filter((q) => !q.error && q.buyAmount !== '0');\n\n    if (valid.length === 0) {\n      const errors = quotes.filter((q) => q.error).map((q) => `${q.aggregator}: ${q.error}`);\n      throw new Error(`No valid quotes. Errors: ${errors.join('; ')}`);\n    }\n\n    // Prefer net output (gas-inclusive) if available, else raw buyAmount\n    return valid.sort((a, b) => {\n      if (a.netOutputUsd != null && b.netOutputUsd != null) {\n        return b.netOutputUsd - a.netOutputUsd;\n      }\n      return BigInt(b.buyAmount) > BigInt(a.buyAmount) ? 1 : -1;\n    })[0]!;\n  }\n\n  private async fetchSingle(\n    name: string,\n    sellToken: string,\n    buyToken: string,\n    sellAmount: string,\n    chainId: number,\n  ): Promise<SwapQuote> {\n    switch (name) {\n      case '0x':\n        return fetchQuote0x(sellToken, buyToken, sellAmount, chainId, this.slippageBps, this.timeoutMs);\n      case 'paraswap':\n        return fetchQuoteParaSwap(sellToken, buyToken, sellAmount, chainId, this.slippageBps, this.timeoutMs);\n      case 'odos':\n        return fetchQuoteOdos(sellToken, buyToken, sellAmount, chainId, this.slippageBps, this.timeoutMs);\n      case 'kyberswap':\n        return fetchQuoteKyber(sellToken, buyToken, sellAmount, chainId, this.slippageBps, this.timeoutMs);\n      case '1inch':\n        return fetchQuote1inch(sellToken, buyToken, sellAmount, chainId, this.slippageBps, this.timeoutMs);\n      case 'openocean':\n        return fetchQuoteOpenOcean(sellToken, buyToken, sellAmount, chainId, this.slippageBps, this.timeoutMs);\n      default:\n        throw new Error(`Unknown aggregator: ${name}`);\n    }\n  }\n}\n\n// ── Singleton ───────────────────────────────────────────────────────────────\n\nlet _instance: DexAggregator | null = null;\n\nexport function getDexAggregator(config?: DexAggregatorConfig): DexAggregator {\n  if (!_instance) {\n    _instance = new DexAggregator(config);\n  }\n  return _instance;\n}\n\nexport function resetDexAggregator(): void {\n  _instance = null;\n}\n"],"mappings":"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"}