/** * Liquidity Engine Service * Intelligent routing and liquidity management with decision logic */ import { ethers } from 'ethers'; export enum SwapProvider { UniswapV3 = 0, Curve = 1, Dodoex = 2, Balancer = 3, OneInch = 4, } export enum SwapSize { Small = 0, // < $10k Medium = 1, // $10k - $100k Large = 2, // > $100k } export interface Quote { provider: SwapProvider; amountOut: bigint; slippage: number; gasEstimate: bigint; confidence: number; // 0-100 route: string[]; } export interface RoutingDecision { provider: SwapProvider; route: string[]; expectedOutput: bigint; slippage: number; gasEstimate: bigint; confidence: number; reasoning: string; } export interface LiquidityDecisionMap { sizeThresholds: { small: { max: number; providers: SwapProvider[] }; medium: { max: number; providers: SwapProvider[] }; large: { providers: SwapProvider[] }; }; slippageRules: { lowSlippage: { max: number; prefer: SwapProvider }; mediumSlippage: { max: number; prefer: SwapProvider }; highSlippage: { prefer: SwapProvider }; }; liquidityRules: { highLiquidity: { min: number; prefer: SwapProvider }; mediumLiquidity: { prefer: SwapProvider }; lowLiquidity: { prefer: SwapProvider }; }; timeRules: { highVolatility: { prefer: SwapProvider }; normal: { prefer: SwapProvider }; }; } export class LiquidityEngine { private provider: ethers.Provider; private enhancedSwapRouter: ethers.Contract; private decisionMap: LiquidityDecisionMap; constructor( provider: ethers.Provider, enhancedSwapRouterAddress: string, decisionMap?: LiquidityDecisionMap ) { this.provider = provider; this.enhancedSwapRouter = new ethers.Contract( enhancedSwapRouterAddress, [], // ABI would go here provider ); // Default decision map this.decisionMap = decisionMap || this.getDefaultDecisionMap(); } /** * Find best route for a swap */ async findBestRoute( inputToken: string, outputToken: string, amount: bigint, maxSlippage: number = 0.5 ): Promise { // 1. Get quotes from all providers const quotes = await this.getQuotes(inputToken, outputToken, amount); if (quotes.length === 0) { throw new Error('No quotes available'); } // 2. Determine swap size category const sizeCategory = this.getSwapSize(amount); // 3. Apply decision logic const decision = this.applyDecisionLogic(quotes, sizeCategory, maxSlippage); return decision; } /** * Get quotes from all enabled providers */ async getQuotes( 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: Quote[] = []; for (let i = 0; i < providers.length; i++) { if (amounts[i] > 0n) { const slippage = this.calculateSlippage(amount, amounts[i]); const gasEstimate = await this.estimateGas(providers[i], amount); const confidence = this.calculateConfidence(providers[i], slippage, gasEstimate); quotes.push({ provider: providers[i], amountOut: amounts[i], slippage, gasEstimate, confidence, route: [inputToken, outputToken], // Simplified, would be actual route }); } } return quotes.sort((a, b) => { // Sort by best effective output (considering gas) const aEffective = a.amountOut - a.gasEstimate; const bEffective = b.amountOut - b.gasEstimate; return aEffective > bEffective ? -1 : 1; }); } catch (error) { console.error('Error getting quotes:', error); return []; } } /** * Apply decision logic to select best route */ applyDecisionLogic( quotes: Quote[], sizeCategory: SwapSize, maxSlippage: number ): RoutingDecision { // Filter by size-based preferences const preferredProviders = this.getPreferredProviders(sizeCategory); const filteredQuotes = quotes.filter(q => preferredProviders.includes(q.provider)); // Filter by slippage rules const slippageFiltered = this.applySlippageRules(filteredQuotes, maxSlippage); // Select best quote const bestQuote = slippageFiltered[0] || quotes[0]; return { provider: bestQuote.provider, route: bestQuote.route, expectedOutput: bestQuote.amountOut, slippage: bestQuote.slippage, gasEstimate: bestQuote.gasEstimate, confidence: bestQuote.confidence, reasoning: this.generateReasoning(bestQuote, sizeCategory), }; } /** * Get preferred providers for swap size */ getPreferredProviders(sizeCategory: SwapSize): SwapProvider[] { switch (sizeCategory) { case SwapSize.Small: return this.decisionMap.sizeThresholds.small.providers; case SwapSize.Medium: return this.decisionMap.sizeThresholds.medium.providers; case SwapSize.Large: return this.decisionMap.sizeThresholds.large.providers; } } /** * Apply slippage-based routing rules */ applySlippageRules(quotes: Quote[], maxSlippage: number): Quote[] { if (maxSlippage <= this.decisionMap.slippageRules.lowSlippage.max) { // Prefer low slippage provider return quotes.filter(q => q.slippage <= this.decisionMap.slippageRules.lowSlippage.max) .sort((a, b) => a.slippage - b.slippage); } else if (maxSlippage <= this.decisionMap.slippageRules.mediumSlippage.max) { return quotes.filter(q => q.slippage <= this.decisionMap.slippageRules.mediumSlippage.max); } else { // High slippage - use preferred provider return quotes.filter(q => q.provider === this.decisionMap.slippageRules.highSlippage.prefer); } } /** * Determine swap size category */ getSwapSize(amount: bigint): SwapSize { // Assuming 1 ETH = $2000 for size calculation const usdValue = Number(amount) * 2000 / 1e18; if (usdValue < 10000) return SwapSize.Small; if (usdValue < 100000) return SwapSize.Medium; return SwapSize.Large; } /** * Calculate slippage percentage */ calculateSlippage(amountIn: bigint, amountOut: bigint): number { // Simplified - would use actual price oracle const expectedOut = amountIn; // 1:1 for stablecoins const slippage = Number(amountOut - expectedOut) / Number(expectedOut) * 100; return Math.abs(slippage); } /** * Estimate gas for swap */ async estimateGas(provider: SwapProvider, amount: bigint): Promise { // Gas estimates per provider (would be dynamic in production) const gasEstimates: Record = { [SwapProvider.UniswapV3]: 150000n, [SwapProvider.Curve]: 200000n, [SwapProvider.Dodoex]: 180000n, [SwapProvider.Balancer]: 220000n, [SwapProvider.OneInch]: 250000n, }; return gasEstimates[provider] || 200000n; } /** * Calculate confidence score (0-100) */ calculateConfidence( provider: SwapProvider, slippage: number, gasEstimate: bigint ): number { let confidence = 100; // Reduce confidence based on slippage if (slippage > 1) confidence -= 20; else if (slippage > 0.5) confidence -= 10; // Reduce confidence based on gas (higher gas = lower confidence) const gasPenalty = Number(gasEstimate) > 200000 ? 10 : 0; confidence -= gasPenalty; // Provider-specific adjustments if (provider === SwapProvider.Dodoex) confidence += 5; // PMM is more reliable if (provider === SwapProvider.OneInch) confidence -= 5; // Aggregation can be less reliable return Math.max(0, Math.min(100, confidence)); } /** * Generate reasoning for decision */ generateReasoning(quote: Quote, sizeCategory: SwapSize): string { const sizeStr = SwapSize[sizeCategory]; const providerStr = SwapProvider[quote.provider]; return `Selected ${providerStr} for ${sizeStr} swap. ` + `Expected output: ${quote.amountOut.toString()}, ` + `Slippage: ${quote.slippage.toFixed(2)}%, ` + `Confidence: ${quote.confidence}%`; } /** * Get default decision map */ getDefaultDecisionMap(): LiquidityDecisionMap { return { sizeThresholds: { small: { max: 10000, providers: [SwapProvider.UniswapV3, SwapProvider.Dodoex], }, medium: { max: 100000, providers: [SwapProvider.Dodoex, SwapProvider.Balancer, SwapProvider.UniswapV3], }, large: { providers: [SwapProvider.Dodoex, SwapProvider.Curve, SwapProvider.Balancer], }, }, slippageRules: { lowSlippage: { max: 0.1, prefer: SwapProvider.Dodoex }, mediumSlippage: { max: 0.5, prefer: SwapProvider.Balancer }, highSlippage: { prefer: SwapProvider.Curve }, }, liquidityRules: { highLiquidity: { min: 1000000, prefer: SwapProvider.UniswapV3 }, mediumLiquidity: { prefer: SwapProvider.Dodoex }, lowLiquidity: { prefer: SwapProvider.Curve }, }, timeRules: { highVolatility: { prefer: SwapProvider.Dodoex }, normal: { prefer: SwapProvider.UniswapV3 }, }, }; } /** * Update decision map */ updateDecisionMap(newMap: Partial): void { this.decisionMap = { ...this.decisionMap, ...newMap }; } /** * Get current decision map */ getDecisionMap(): LiquidityDecisionMap { return this.decisionMap; } } export default LiquidityEngine;