/** * Quote Aggregator Service * Aggregates quotes from multiple DEX protocols for price comparison */ import { ethers } from 'ethers'; import { SwapProvider, Quote } from './liquidity-engine.service'; export interface AggregatedQuote { provider: SwapProvider; amountOut: bigint; priceImpact: number; gasEstimate: bigint; effectiveOutput: bigint; // amountOut - gasCost timestamp: number; } export class QuoteAggregator { private provider: ethers.Provider; private enhancedSwapRouter: ethers.Contract; constructor(provider: ethers.Provider, enhancedSwapRouterAddress: string) { this.provider = provider; this.enhancedSwapRouter = new ethers.Contract( enhancedSwapRouterAddress, [], // ABI would go here provider ); } /** * Aggregate quotes from all providers */ async aggregateQuotes( inputToken: string, outputToken: string, amount: bigint ): Promise { try { const result = await this.enhancedSwapRouter.getQuotes(outputToken, amount); const providers = result[0] as SwapProvider[]; const amounts = result[1] as bigint[]; const quotes: AggregatedQuote[] = []; const gasPrice = await this.provider.getFeeData(); for (let i = 0; i < providers.length; i++) { if (amounts[i] > 0n) { const gasEstimate = await this.estimateGasForProvider(providers[i]); const gasCost = gasEstimate * (gasPrice.gasPrice || 0n); const effectiveOutput = amounts[i] - gasCost; const priceImpact = this.calculatePriceImpact(amount, amounts[i]); quotes.push({ provider: providers[i], amountOut: amounts[i], priceImpact, gasEstimate, effectiveOutput, timestamp: Date.now(), }); } } // Sort by effective output (best first) return quotes.sort((a, b) => { if (a.effectiveOutput > b.effectiveOutput) return -1; if (a.effectiveOutput < b.effectiveOutput) return 1; return 0; }); } catch (error) { console.error('Error aggregating quotes:', error); return []; } } /** * Get best quote (highest effective output) */ async getBestQuote( inputToken: string, outputToken: string, amount: bigint ): Promise { const quotes = await this.aggregateQuotes(inputToken, outputToken, amount); return quotes.length > 0 ? quotes[0] : null; } /** * Compare quotes side by side */ async compareQuotes( inputToken: string, outputToken: string, amount: bigint ): Promise<{ best: AggregatedQuote | null; all: AggregatedQuote[]; savings: Map; // Savings vs worst quote }> { const quotes = await this.aggregateQuotes(inputToken, outputToken, amount); if (quotes.length === 0) { return { best: null, all: [], savings: new Map() }; } const best = quotes[0]; const worst = quotes[quotes.length - 1]; const savings = new Map(); quotes.forEach(quote => { savings.set(quote.provider, quote.effectiveOutput - worst.effectiveOutput); }); return { best, all: quotes, savings }; } /** * Calculate price impact */ private calculatePriceImpact(amountIn: bigint, amountOut: bigint): number { // Simplified - assumes 1:1 for stablecoins const expectedOut = amountIn; const impact = Number(amountOut - expectedOut) / Number(expectedOut) * 100; return Math.abs(impact); } /** * Estimate gas for provider */ private async estimateGasForProvider(provider: SwapProvider): Promise { const gasEstimates: Record = { [SwapProvider.UniswapV3]: 150000n, [SwapProvider.Curve]: 200000n, [SwapProvider.Dodoex]: 180000n, [SwapProvider.Balancer]: 220000n, [SwapProvider.OneInch]: 250000n, }; return gasEstimates[provider] || 200000n; } } export default QuoteAggregator;