{"version":3,"file":"gas-estimator.mjs","names":[],"sources":["../../../src/services/gas-estimator.ts"],"sourcesContent":["/**\n * Gas Estimation Service — real-time gas prices, cost estimation, and\n * gas-inclusive swap comparison.\n *\n * Provides:\n * - Multi-chain gas price tracking (fast/standard/slow)\n * - Transaction cost estimation in USD\n * - Gas-inclusive swap comparison (net output = output - gas cost)\n * - EIP-1559 priority fee estimation\n * - Historical gas trend tracking\n *\n * Uses on-chain RPC calls for accurate data, not third-party gas APIs.\n */\n\nimport { getRpcManager } from './rpc-provider.js';\nimport { getTokenPriceUsd } from './dexscreener-service.js';\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface GasPrice {\n  /** Base fee in gwei. */\n  baseFee: number;\n  /** Slow priority fee in gwei (10th percentile). */\n  slow: number;\n  /** Standard priority fee in gwei (50th percentile). */\n  standard: number;\n  /** Fast priority fee in gwei (90th percentile). */\n  fast: number;\n  /** Total gas price for slow/standard/fast (baseFee + priority). */\n  totalSlow: number;\n  totalStandard: number;\n  totalFast: number;\n  /** Chain native token price in USD. */\n  nativeTokenPriceUsd: number;\n  chain: string;\n  chainId: number;\n  timestamp: number;\n}\n\nexport interface GasCostEstimate {\n  /** Gas units for this operation. */\n  gasUnits: number;\n  /** Cost at slow speed in USD. */\n  costSlowUsd: number;\n  /** Cost at standard speed in USD. */\n  costStandardUsd: number;\n  /** Cost at fast speed in USD. */\n  costFastUsd: number;\n  /** Cost in native token (standard speed). */\n  costNativeToken: number;\n  operation: string;\n}\n\nexport interface SwapComparison {\n  aggregator: string;\n  outputAmount: string;\n  outputValueUsd: number;\n  gasCostUsd: number;\n  netOutputUsd: number;\n  rank: number;\n}\n\nexport interface GasEstimatorConfig {\n  /** Number of recent blocks to sample for priority fees. Default: 5. */\n  blockSamples?: number;\n  /** Cache TTL for gas prices in ms. Default: 12000 (12s, ~1 block). */\n  cacheTtlMs?: number;\n}\n\n// ── Common Gas Limits ───────────────────────────────────────────────────────\n\n/** Typical gas limits for common EVM operations. */\nexport const GAS_LIMITS: Record<string, number> = {\n  ETH_TRANSFER: 21_000,\n  ERC20_TRANSFER: 65_000,\n  ERC20_APPROVE: 46_000,\n  UNISWAP_V2_SWAP: 150_000,\n  UNISWAP_V3_SWAP: 184_000,\n  UNISWAP_V4_SWAP: 170_000,\n  DEX_SWAP_SIMPLE: 200_000,\n  DEX_SWAP_MULTI_HOP: 350_000,\n  ADD_LIQUIDITY_V3: 500_000,\n  REMOVE_LIQUIDITY_V3: 300_000,\n  BRIDGE_DEPOSIT: 250_000,\n  PERMIT2_APPROVE: 80_000,\n  CONTRACT_DEPLOY: 2_000_000,\n  NFT_MINT: 120_000,\n};\n\n// ── Chain Native Tokens ─────────────────────────────────────────────────────\n\nconst NATIVE_TOKEN: Record<number, string> = {\n  1: 'ETH', 8453: 'ETH', 42161: 'ETH', 10: 'ETH', 137: 'MATIC',\n};\n\nconst CHAIN_NAMES: Record<number, string> = {\n  1: 'ethereum', 8453: 'base', 42161: 'arbitrum', 10: 'optimism', 137: 'polygon',\n};\n\n// ── Service ─────────────────────────────────────────────────────────────────\n\nexport class GasEstimator {\n  private config: Required<GasEstimatorConfig>;\n  private cache: Map<number, { gasPrice: GasPrice; expiresAt: number }> = new Map();\n\n  constructor(config: GasEstimatorConfig = {}) {\n    this.config = {\n      blockSamples: config.blockSamples ?? 5,\n      cacheTtlMs: config.cacheTtlMs ?? 12_000,\n    };\n  }\n\n  /**\n   * Get current gas prices for a chain (with caching).\n   * Uses EIP-1559 fee history for accurate priority fee estimation.\n   */\n  async getGasPrice(chainId = 8453): Promise<GasPrice> {\n    // Check cache\n    const cached = this.cache.get(chainId);\n    if (cached && Date.now() < cached.expiresAt) {\n      return cached.gasPrice;\n    }\n\n    const rpcManager = getRpcManager();\n    const client = await rpcManager.getClient(chainId);\n\n    // Get fee history (EIP-1559 chains) and native token price in parallel\n    const [feeHistory, nativePrice] = await Promise.all([\n      client.getFeeHistory({\n        blockCount: this.config.blockSamples,\n        rewardPercentiles: [10, 50, 90],\n      }).catch(() => null),\n      this.getNativeTokenPrice(chainId),\n    ]);\n\n    let baseFee: number;\n    let slow: number;\n    let standard: number;\n    let fast: number;\n\n    if (feeHistory?.baseFeePerGas?.length) {\n      // EIP-1559 chain: use fee history\n      const latestBaseFee = feeHistory.baseFeePerGas[feeHistory.baseFeePerGas.length - 1]!;\n      baseFee = Number(latestBaseFee) / 1e9; // wei → gwei\n\n      // Average the reward percentiles across recent blocks\n      const rewards = feeHistory.reward ?? [];\n      if (rewards.length > 0) {\n        slow = this.averageReward(rewards, 0);\n        standard = this.averageReward(rewards, 1);\n        fast = this.averageReward(rewards, 2);\n      } else {\n        slow = 0.01;\n        standard = 0.05;\n        fast = 0.1;\n      }\n    } else {\n      // Legacy chain or fee history unavailable: use gasPrice\n      const gasPrice = await client.getGasPrice();\n      baseFee = Number(gasPrice) / 1e9;\n      slow = 0;\n      standard = 0;\n      fast = baseFee * 0.1; // estimate 10% tip\n    }\n\n    const gasPrice: GasPrice = {\n      baseFee: Math.round(baseFee * 1000) / 1000,\n      slow: Math.round(slow * 1000) / 1000,\n      standard: Math.round(standard * 1000) / 1000,\n      fast: Math.round(fast * 1000) / 1000,\n      totalSlow: Math.round((baseFee + slow) * 1000) / 1000,\n      totalStandard: Math.round((baseFee + standard) * 1000) / 1000,\n      totalFast: Math.round((baseFee + fast) * 1000) / 1000,\n      nativeTokenPriceUsd: nativePrice,\n      chain: CHAIN_NAMES[chainId] ?? String(chainId),\n      chainId,\n      timestamp: Date.now(),\n    };\n\n    // Cache the result\n    this.cache.set(chainId, { gasPrice, expiresAt: Date.now() + this.config.cacheTtlMs });\n    return gasPrice;\n  }\n\n  /**\n   * Estimate the gas cost for a specific operation in USD.\n   */\n  async estimateCost(\n    operation: keyof typeof GAS_LIMITS | number,\n    chainId = 8453,\n  ): Promise<GasCostEstimate> {\n    const gasUnits = typeof operation === 'number' ? operation : GAS_LIMITS[operation] ?? 200_000;\n    const opName = typeof operation === 'string' ? operation : `custom (${operation} gas)`;\n    const gas = await this.getGasPrice(chainId);\n\n    const costSlowNative = (gas.totalSlow * gasUnits) / 1e9;\n    const costStdNative = (gas.totalStandard * gasUnits) / 1e9;\n    const costFastNative = (gas.totalFast * gasUnits) / 1e9;\n\n    return {\n      gasUnits,\n      costSlowUsd: Math.round(costSlowNative * gas.nativeTokenPriceUsd * 10000) / 10000,\n      costStandardUsd: Math.round(costStdNative * gas.nativeTokenPriceUsd * 10000) / 10000,\n      costFastUsd: Math.round(costFastNative * gas.nativeTokenPriceUsd * 10000) / 10000,\n      costNativeToken: Math.round(costStdNative * 1e8) / 1e8,\n      operation: opName,\n    };\n  }\n\n  /**\n   * Compare swap quotes with gas costs factored in.\n   * Takes raw aggregator quotes and produces a gas-inclusive ranking.\n   */\n  async compareSwapsGasInclusive(\n    quotes: Array<{\n      aggregator: string;\n      buyAmount: string;\n      buyTokenPriceUsd: number;\n      buyTokenDecimals: number;\n      gasEstimate?: string;\n    }>,\n    chainId = 8453,\n  ): Promise<SwapComparison[]> {\n    const gas = await this.getGasPrice(chainId);\n\n    const comparisons: SwapComparison[] = quotes.map((q) => {\n      const outputHuman = Number(q.buyAmount) / 10 ** q.buyTokenDecimals;\n      const outputValueUsd = outputHuman * q.buyTokenPriceUsd;\n\n      const gasUnits = parseInt(q.gasEstimate ?? '200000', 10);\n      const gasCostNative = (gas.totalStandard * gasUnits) / 1e9;\n      const gasCostUsd = gasCostNative * gas.nativeTokenPriceUsd;\n\n      return {\n        aggregator: q.aggregator,\n        outputAmount: q.buyAmount,\n        outputValueUsd: Math.round(outputValueUsd * 100) / 100,\n        gasCostUsd: Math.round(gasCostUsd * 10000) / 10000,\n        netOutputUsd: Math.round((outputValueUsd - gasCostUsd) * 100) / 100,\n        rank: 0, // computed after sorting\n      };\n    });\n\n    // Sort by net output descending\n    comparisons.sort((a, b) => b.netOutputUsd - a.netOutputUsd);\n    comparisons.forEach((c, i) => { c.rank = i + 1; });\n\n    return comparisons;\n  }\n\n  /**\n   * Get gas cost estimates for common operations on a chain.\n   * Useful for the `/gas` command or a setup screen.\n   */\n  async getCommonCosts(chainId = 8453): Promise<{\n    gasPrice: GasPrice;\n    costs: GasCostEstimate[];\n  }> {\n    const gasPrice = await this.getGasPrice(chainId);\n    const ops: Array<keyof typeof GAS_LIMITS> = [\n      'ETH_TRANSFER', 'ERC20_TRANSFER', 'DEX_SWAP_SIMPLE',\n      'DEX_SWAP_MULTI_HOP', 'BRIDGE_DEPOSIT', 'ADD_LIQUIDITY_V3',\n    ];\n\n    const costs = await Promise.all(ops.map((op) => this.estimateCost(op, chainId)));\n    return { gasPrice, costs };\n  }\n\n  /** Clear the gas price cache. */\n  clearCache(): void {\n    this.cache.clear();\n  }\n\n  // ── Private helpers ───────────────────────────────────────────────────\n\n  private averageReward(rewards: bigint[][], index: number): number {\n    const values = rewards.map((r) => Number(r[index] ?? 0n) / 1e9);\n    return values.reduce((a, b) => a + b, 0) / values.length;\n  }\n\n  private async getNativeTokenPrice(chainId: number): Promise<number> {\n    const token = NATIVE_TOKEN[chainId] ?? 'ETH';\n    try {\n      const { priceUsd } = await getTokenPriceUsd(token, CHAIN_NAMES[chainId] ?? 'base');\n      return priceUsd;\n    } catch {\n      return 0;\n    }\n  }\n}\n\n// ── Singleton ───────────────────────────────────────────────────────────────\n\nlet _instance: GasEstimator | null = null;\n\nexport function getGasEstimator(config?: GasEstimatorConfig): GasEstimator {\n  if (!_instance) {\n    _instance = new GasEstimator(config);\n  }\n  return _instance;\n}\n\nexport function resetGasEstimator(): void {\n  _instance = null;\n}\n"],"mappings":"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"}