package parser import ( "context" "encoding/hex" "fmt" "math/big" "strconv" "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/market" "github.com/fraktal/mev-beta/pkg/math" pkgtypes "github.com/fraktal/mev-beta/pkg/types" ) // TransactionAnalyzer analyzes transactions for MEV opportunities type TransactionAnalyzer struct { logger *logger.Logger protocolRegistry interface{} // Will use interface{} to avoid import issues abiDecoder ABIDecoder mathCalculator *math.MathCalculator marketDiscovery *market.MarketManager mevAnalyzer *MEVAnalyzer executor *Executor // Add executor for opportunity execution } // NewTransactionAnalyzer creates a new transaction analyzer func NewTransactionAnalyzer( logger *logger.Logger, protocolRegistry interface{}, abiDecoder ABIDecoder, mathCalculator *math.MathCalculator, marketDiscovery *market.MarketManager, mevAnalyzer *MEVAnalyzer, executor *Executor, // Add executor parameter ) *TransactionAnalyzer { return &TransactionAnalyzer{ logger: logger, protocolRegistry: protocolRegistry, abiDecoder: abiDecoder, mathCalculator: mathCalculator, marketDiscovery: marketDiscovery, mevAnalyzer: mevAnalyzer, executor: executor, // Store executor } } // analyzeTransactionForMEV analyzes a single transaction for MEV opportunities func (ta *TransactionAnalyzer) analyzeTransactionForMEV(ctx context.Context, tx *RawL2Transaction, opportunities *MEVOpportunities) error { // Parse transaction input data input, err := hex.DecodeString(strings.TrimPrefix(tx.Input, "0x")) if err != nil { return fmt.Errorf("failed to decode input: %w", err) } // Skip transactions with insufficient input data if len(input) < 4 { return nil } // Extract function signature funcSig := hex.EncodeToString(input[:4]) // Skip transactions with insufficient input data for DEX functions // (In a full implementation, we'd have a way to identify DEX transactions) if len(input) < 20 { // 4 bytes signature + some parameters return nil } // For now, treat all transactions as potentially containing swaps // In the full implementation, we'd use protocolRegistry to identify DEX functions protocol := "unknown" functionName := "unknown" isSwap := true // Simplified for compilation - in reality, we'd check the function if isSwap { return ta.handleSwapTransaction(ctx, tx, protocol, functionName, input, opportunities) } // Check for liquidation functions if ta.isLiquidationFunction(funcSig) { return ta.handleLiquidationTransaction(ctx, tx, funcSig, input, opportunities) } // Check for liquidity modification functions if ta.isLiquidityFunction(funcSig) { return ta.handleLiquidityTransaction(ctx, tx, funcSig, input, opportunities) } return nil } // handleSwapTransaction processes swap transactions func (ta *TransactionAnalyzer) handleSwapTransaction(ctx context.Context, tx *RawL2Transaction, protocol, functionName string, input []byte, opportunities *MEVOpportunities) error { // Parse swap parameters based on protocol swapData, err := ta.parseSwapData(protocol, functionName, input) if err != nil { return fmt.Errorf("failed to parse swap data: %w", err) } // Convert transaction data properly blockNumber := uint64(0) // Will be set by the block processing context gasUsed, err := strconv.ParseUint(strings.TrimPrefix(tx.Gas, "0x"), 16, 64) if err != nil { gasUsed = 0 } // Create swap event with proper default values swapEvent := &SwapEvent{ Timestamp: time.Now(), BlockNumber: blockNumber, // Will be updated when actual block number is known TxHash: tx.Hash, Protocol: protocol, Router: common.HexToAddress(tx.To), Pool: common.HexToAddress(swapData.Pool), TokenIn: common.HexToAddress(swapData.TokenIn), TokenOut: common.HexToAddress(swapData.TokenOut), AmountIn: parseStringToBigInt(swapData.AmountIn), AmountOut: parseStringToBigInt(swapData.AmountOut), Sender: common.HexToAddress(tx.From), Recipient: common.HexToAddress(swapData.Recipient), GasPrice: tx.GasPrice, GasUsed: gasUsed, PriceImpact: swapData.PriceImpact, MEVScore: ta.calculateMEVScore(swapData), Profitable: swapData.PriceImpact > 0.001, // Lower threshold for profitability } // Add to opportunities for immediate processing opportunities.SwapEvents = append(opportunities.SwapEvents, swapEvent) // Check for arbitrage opportunities if swapEvent.Profitable { arbOp := ta.findArbitrageOpportunity(ctx, swapData) if arbOp != nil { opportunities.ArbitrageOps = append(opportunities.ArbitrageOps, arbOp) } } // Check for sandwich attack opportunities sandwichOp := ta.findSandwichOpportunity(ctx, swapData, tx) if sandwichOp != nil { opportunities.SandwichOps = append(opportunities.SandwichOps, sandwichOp) } return nil } // handleLiquidationTransaction processes liquidation transactions func (ta *TransactionAnalyzer) handleLiquidationTransaction(ctx context.Context, tx *RawL2Transaction, funcSig string, input []byte, opportunities *MEVOpportunities) error { // Parse liquidation data liquidationData, err := ta.parseLiquidationData(funcSig, input) if err != nil { return fmt.Errorf("failed to parse liquidation data: %w", err) } blockNumber, _ := strconv.ParseUint(strings.TrimPrefix(tx.Hash, "0x"), 16, 64) // Create liquidation event liquidationEvent := &LiquidationEvent{ Timestamp: time.Now(), BlockNumber: blockNumber, TxHash: tx.Hash, Protocol: liquidationData.Protocol, Liquidator: common.HexToAddress(tx.From), Borrower: common.HexToAddress(liquidationData.Borrower), CollateralToken: common.HexToAddress(liquidationData.CollateralToken), DebtToken: common.HexToAddress(liquidationData.DebtToken), CollateralAmount: parseStringToBigInt(liquidationData.CollateralAmount), DebtAmount: parseStringToBigInt(liquidationData.DebtAmount), Bonus: parseStringToBigInt(liquidationData.LiquidationBonus), HealthFactor: liquidationData.HealthFactor, MEVOpportunity: liquidationData.HealthFactor < 1.05, // Under-collateralized EstimatedProfit: parseStringToBigInt(liquidationData.EstimatedProfit), } opportunities.LiquidationEvents = append(opportunities.LiquidationEvents, liquidationEvent) // Check for liquidation MEV opportunities if liquidationEvent.MEVOpportunity { liqOp := ta.findLiquidationOpportunity(ctx, liquidationData) if liqOp != nil { opportunities.LiquidationOps = append(opportunities.LiquidationOps, liqOp) } } return nil } // handleLiquidityTransaction processes liquidity modification transactions func (ta *TransactionAnalyzer) handleLiquidityTransaction(ctx context.Context, tx *RawL2Transaction, funcSig string, input []byte, opportunities *MEVOpportunities) error { // Parse liquidity data liquidityData, err := ta.parseLiquidityData(funcSig, input) if err != nil { return fmt.Errorf("failed to parse liquidity data: %w", err) } blockNumber, _ := strconv.ParseUint(strings.TrimPrefix(tx.Hash, "0x"), 16, 64) // Create liquidity event liquidityEvent := &LiquidityEvent{ Timestamp: time.Now(), BlockNumber: blockNumber, TxHash: tx.Hash, Protocol: liquidityData.Protocol, Pool: common.HexToAddress(liquidityData.Pool), EventType: liquidityData.EventType, Token0: common.HexToAddress(liquidityData.Token0), Token1: common.HexToAddress(liquidityData.Token1), Amount0: parseStringToBigInt(liquidityData.Amount0), Amount1: parseStringToBigInt(liquidityData.Amount1), Liquidity: parseStringToBigInt(liquidityData.Liquidity), PriceAfter: big.NewFloat(0), // Not available from function signature ImpactSize: liquidityData.ImpactSize, ArbitrageOpp: liquidityData.ImpactSize > 0.01, // 1% threshold } opportunities.LiquidityEvents = append(opportunities.LiquidityEvents, liquidityEvent) return nil } // Helper methods for function identification func (ta *TransactionAnalyzer) isLiquidationFunction(funcSig string) bool { liquidationSigs := map[string]bool{ "630d4904": true, // liquidationCall (Aave/Radiant) "96cd4ddb": true, // liquidateBorrow (Compound-style) "f5e3c462": true, // liquidate (GMX) "871dd7c8": true, // liquidatePosition (GMX) } return liquidationSigs[funcSig] } func (ta *TransactionAnalyzer) isLiquidityFunction(funcSig string) bool { liquiditySigs := map[string]bool{ "e8e33700": true, // addLiquidity "baa2abde": true, // addLiquidityETH "02751cec": true, // removeLiquidity "af2979eb": true, // removeLiquidityETH "219f5d17": true, // mint (Uniswap V3) "a34123a7": true, // burn (Uniswap V3) "0dfe1681": true, // collect (Uniswap V3) } return liquiditySigs[funcSig] } // Data structures for parsed transaction data type SwapData struct { Protocol string Pool string TokenIn string TokenOut string AmountIn string AmountOut string Recipient string PriceImpact float64 } type LiquidationData struct { Protocol string Borrower string CollateralToken string DebtToken string CollateralAmount string DebtAmount string LiquidationBonus string HealthFactor float64 EstimatedProfit string } type LiquidityData struct { Protocol string Pool string EventType string Token0 string Token1 string Amount0 string Amount1 string Liquidity string PriceAfter string ImpactSize float64 } // Real ABI decoding methods using the ABIDecoder func (ta *TransactionAnalyzer) parseSwapData(protocol, functionName string, input []byte) (*SwapData, error) { decoded, err := ta.abiDecoder.DecodeSwapTransaction(protocol, input) if err != nil { ta.logger.Warn("Failed to decode swap transaction", "protocol", protocol, "function", functionName, "error", err) return &SwapData{ Protocol: protocol, Pool: "", TokenIn: "", TokenOut: "", AmountIn: "0", AmountOut: "0", Recipient: "", PriceImpact: 0, }, nil } var swapEvent *SwapEvent switch v := decoded.(type) { case *SwapEvent: swapEvent = v case map[string]interface{}: converted := &SwapEvent{Protocol: protocol} if tokenIn, ok := v["TokenIn"].(common.Address); ok { converted.TokenIn = tokenIn } if tokenOut, ok := v["TokenOut"].(common.Address); ok { converted.TokenOut = tokenOut } if amountIn, ok := v["AmountIn"].(*big.Int); ok { converted.AmountIn = amountIn } if amountOut, ok := v["AmountOut"].(*big.Int); ok { converted.AmountOut = amountOut } if recipient, ok := v["Recipient"].(common.Address); ok { converted.Recipient = recipient } swapEvent = converted default: ta.logger.Warn("Unsupported swap decode type", "protocol", protocol, "function", functionName, "decoded_type", fmt.Sprintf("%T", decoded)) } if swapEvent == nil { return &SwapData{ Protocol: protocol, Pool: "", TokenIn: "", TokenOut: "", AmountIn: "0", AmountOut: "0", Recipient: "", PriceImpact: 0, }, nil } tokenInAddr := swapEvent.TokenIn tokenOutAddr := swapEvent.TokenOut amountInStr := "0" if swapEvent.AmountIn != nil { amountInStr = swapEvent.AmountIn.String() } amountOutStr := "0" if swapEvent.AmountOut != nil { amountOutStr = swapEvent.AmountOut.String() } recipientStr := "" if swapEvent.Recipient != (common.Address{}) { recipientStr = swapEvent.Recipient.Hex() } poolAddress := "" if swapEvent.Pool != (common.Address{}) { poolAddress = swapEvent.Pool.Hex() } else if tokenInAddr != (common.Address{}) && tokenOutAddr != (common.Address{}) { feeVal := int(swapEvent.Fee) poolAddr, poolErr := ta.abiDecoder.CalculatePoolAddress(protocol, tokenInAddr.Hex(), tokenOutAddr.Hex(), feeVal) if poolErr == nil { poolAddress = poolAddr.Hex() } } swapParamsModel := &SwapParams{ TokenIn: tokenInAddr, TokenOut: tokenOutAddr, AmountIn: swapEvent.AmountIn, AmountOut: swapEvent.AmountOut, Recipient: swapEvent.Recipient, } if swapEvent.Fee > 0 { swapParamsModel.Fee = big.NewInt(int64(swapEvent.Fee)) } if poolAddress != "" { swapParamsModel.Pool = common.HexToAddress(poolAddress) } priceImpact := ta.calculateRealPriceImpact(protocol, swapParamsModel, poolAddress) return &SwapData{ Protocol: protocol, Pool: poolAddress, TokenIn: tokenInAddr.Hex(), TokenOut: tokenOutAddr.Hex(), AmountIn: amountInStr, AmountOut: amountOutStr, Recipient: recipientStr, PriceImpact: priceImpact, }, nil } // calculateRealPriceImpact calculates real price impact using exchange-specific math func (ta *TransactionAnalyzer) calculateRealPriceImpact(protocol string, swapParams *SwapParams, poolAddress string) float64 { // Return default small impact if we don't have enough data if swapParams.AmountIn == nil || swapParams.AmountIn.Cmp(big.NewInt(0)) <= 0 { return 0.0001 // 0.01% default } // Get the appropriate math calculator for the protocol mathEngine := ta.mathCalculator.GetMathForExchange(protocol) if mathEngine == nil { // Fallback estimation based on amount if math engine not available amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 10e18 { // > 10 ETH return 0.01 // 1% } else if amountFloat > 1e18 { // > 1 ETH return 0.005 // 0.5% } else if amountFloat > 0.1e18 { // > 0.1 ETH return 0.001 // 0.1% } else { return 0.0005 // 0.05% } } // For real price impact calculation, we need pool reserves // Since we don't have them readily available, we'll use a simplified approach // that estimates based on the swap amount and known patterns switch protocol { case "uniswap_v2", "sushiswap", "camelot_v2", "trader_joe": return ta.estimateUniswapV2PriceImpact(context.Background(), swapParams, mathEngine) case "uniswap_v3", "camelot_v3", "algebra": return ta.estimateUniswapV3PriceImpact(context.Background(), swapParams, mathEngine) case "curve": return ta.estimateCurvePriceImpact(context.Background(), swapParams, mathEngine) case "balancer_v2": return ta.estimateBalancerPriceImpact(context.Background(), swapParams) default: // For unknown protocols, estimate based on amount return ta.estimateGenericPriceImpact(context.Background(), swapParams) } } // parseStringToBigInt converts a string to *big.Int, handling various formats func parseStringToBigInt(s string) *big.Int { if s == "" { return big.NewInt(0) } // Handle hex format if strings.HasPrefix(s, "0x") { n, ok := new(big.Int).SetString(strings.TrimPrefix(s, "0x"), 16) if ok { return n } return big.NewInt(0) } // Handle decimal format n, ok := new(big.Int).SetString(s, 10) if ok { return n } return big.NewInt(0) } // estimateUniswapV2PriceImpact estimates price impact for Uniswap V2 style AMMs func (ta *TransactionAnalyzer) estimateUniswapV2PriceImpact(ctx context.Context, swapParams *SwapParams, mathEngine math.ExchangeMath) float64 { // Get actual pool reserves from market discovery poolAddr := swapParams.Pool // Access the market discovery to get real pool reserves poolInfo, err := ta.marketDiscovery.GetPool(ctx, poolAddr) if err != nil || poolInfo == nil { return ta.estimateFallbackV2PriceImpact(swapParams.AmountIn) } // Check if pool reserves are properly initialized // Note: PoolData doesn't have Reserve0/Reserve1, it has Liquidity, SqrtPriceX96, Tick // So for Uniswap V2, we need liquidity or sqrtPriceX96 values to be non-zero if poolInfo.Liquidity == nil || poolInfo.Liquidity.Sign() <= 0 { return ta.estimateFallbackV2PriceImpact(swapParams.AmountIn) } // Convert uint256 values to big.Int for math engine liquidityBig := poolInfo.Liquidity.ToBig() // For V2, we need to approximate reserves from liquidity // This is a simplified approach - in real implementation, you'd need the actual reserves // For now, use liquidity as a proxy for total pool size // This calculation is approximate - a real implementation would need real reserves reserve0Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split reserve1Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split // Calculate price impact using approximated reserves impact, err := mathEngine.CalculatePriceImpact(swapParams.AmountIn, reserve0Big, reserve1Big) if err != nil { return ta.estimateFallbackV2PriceImpact(swapParams.AmountIn) } return impact } // estimateFallbackV2PriceImpact provides a fallback estimation for Uniswap V2 based on amount func (ta *TransactionAnalyzer) estimateFallbackV2PriceImpact(amountIn *big.Int) float64 { amountFloat, _ := new(big.Float).SetInt(amountIn).Float64() if amountFloat > 10e18 { // > 10 ETH return 0.01 // 1% } else if amountFloat > 1e18 { // > 1 ETH return 0.005 // 0.5% } else if amountFloat > 0.1e18 { // > 0.1 ETH return 0.001 // 0.1% } else { return 0.0005 // 0.05% } } // estimateUniswapV3PriceImpact estimates price impact for Uniswap V3 func (ta *TransactionAnalyzer) estimateUniswapV3PriceImpact(ctx context.Context, swapParams *SwapParams, mathEngine math.ExchangeMath) float64 { // Get actual pool data from market discovery poolAddr := swapParams.Pool // Access the market discovery to get real pool data poolInfo, err := ta.marketDiscovery.GetPool(ctx, poolAddr) if err != nil || poolInfo == nil || poolInfo.SqrtPriceX96 == nil || poolInfo.Liquidity == nil { return ta.estimateFallbackPriceImpact(swapParams.AmountIn) } // Check if pool data is properly initialized if poolInfo.SqrtPriceX96 == nil || poolInfo.Liquidity == nil { return ta.estimateFallbackPriceImpact(swapParams.AmountIn) } // Convert uint256 values to big.Int for math engine sqrtPriceX96Big := poolInfo.SqrtPriceX96.ToBig() liquidityBig := poolInfo.Liquidity.ToBig() // Calculate price impact using V3-specific math with converted values impact, err := mathEngine.CalculatePriceImpact(swapParams.AmountIn, sqrtPriceX96Big, liquidityBig) if err != nil { return ta.estimateFallbackPriceImpact(swapParams.AmountIn) } return impact } // estimateFallbackPriceImpact provides a fallback estimation based on amount func (ta *TransactionAnalyzer) estimateFallbackPriceImpact(amountIn *big.Int) float64 { amountFloat, _ := new(big.Float).SetInt(amountIn).Float64() if amountFloat > 10e18 { // > 10 ETH return 0.005 // 0.5% } else if amountFloat > 1e18 { // > 1 ETH return 0.002 // 0.2% } else if amountFloat > 0.1e18 { // > 0.1 ETH return 0.0005 // 0.05% } else { return 0.0002 // 0.02% } } // estimateCurvePriceImpact estimates price impact for Curve Finance func (ta *TransactionAnalyzer) estimateCurvePriceImpact(ctx context.Context, swapParams *SwapParams, mathEngine math.ExchangeMath) float64 { // Get actual pool data from market discovery poolAddr := swapParams.Pool // Access the market discovery to get real pool data poolInfo, err := ta.marketDiscovery.GetPool(ctx, poolAddr) if err != nil || poolInfo == nil { // Fallback estimation based on amount if pool info not available amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 100e6 { // > 100M stablecoins return 0.002 // 0.2% } else if amountFloat > 10e6 { // > 10M stablecoins return 0.0005 // 0.05% } else { return 0.0001 // 0.01% } } // Check if pool reserves are properly initialized // Since PoolData doesn't have Reserve0/Reserve1, use Liquidity if poolInfo.Liquidity == nil || poolInfo.Liquidity.Sign() <= 0 { // Fallback estimation based on amount if pool info not available or improperly initialized amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 100e6 { // > 100M stablecoins return 0.002 // 0.2% } else if amountFloat > 10e6 { // > 10M stablecoins return 0.0005 // 0.05% } else { return 0.0001 // 0.01% } } // Calculate price impact using Curve-specific math with approximated reserves // For V2-style functions, need 3 parameters: amountIn, reserve0, reserve1 liquidityBig := poolInfo.Liquidity.ToBig() // Approximating reserves from liquidity - this is a simplified approach // In a real V2 pool, reserves would be actual token balances reserve0Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split reserve1Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split impact, err := mathEngine.CalculatePriceImpact(swapParams.AmountIn, reserve0Big, reserve1Big) if err != nil { // Fallback if calculation fails amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 100e6 { // > 100M stablecoins return 0.002 // 0.2% } else if amountFloat > 10e6 { // > 10M stablecoins return 0.0005 // 0.05% } else { return 0.0001 // 0.01% } } return impact } // estimateBalancerPriceImpact estimates price impact for Balancer func (ta *TransactionAnalyzer) estimateBalancerPriceImpact(ctx context.Context, swapParams *SwapParams) float64 { // Get actual pool data from market discovery poolAddr := swapParams.Pool // Access the market discovery to get real pool data poolInfo, err := ta.marketDiscovery.GetPool(ctx, poolAddr) if err != nil || poolInfo == nil { // Fallback estimation based on amount if pool info not available amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 5e18 { // > 5 ETH return 0.008 // 0.8% } else if amountFloat > 1e18 { // > 1 ETH return 0.003 // 0.3% } else { return 0.001 // 0.1% } } // Check if pool reserves are properly initialized // Since PoolData doesn't have Reserve0/Reserve1, use Liquidity if poolInfo.Liquidity == nil || poolInfo.Liquidity.Sign() <= 0 { // Fallback estimation based on amount if pool info not available or improperly initialized amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 5e18 { // > 5 ETH return 0.008 // 0.8% } else if amountFloat > 1e18 { // > 1 ETH return 0.003 // 0.3% } else { return 0.001 // 0.1% } } // Use the math engine to calculate real price impact mathEngine := ta.mathCalculator.GetMathForExchange("balancer_v2") if mathEngine == nil { // Fallback estimation based on amount if pool info not available or math engine not available amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 5e18 { // > 5 ETH return 0.008 // 0.8% } else if amountFloat > 1e18 { // > 1 ETH return 0.003 // 0.3% } else { return 0.001 // 0.1% } } // Convert uint256 to big.Int for math engine liquidityBig := poolInfo.Liquidity.ToBig() // For V2-style functions, need 3 parameters: amountIn, reserve0, reserve1 // Approximating reserves from liquidity reserve0Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split reserve1Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split impact, err := mathEngine.CalculatePriceImpact(swapParams.AmountIn, reserve0Big, reserve1Big) if err != nil { // Fallback if calculation fails amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() if amountFloat > 5e18 { // > 5 ETH return 0.008 // 0.8% } else if amountFloat > 1e18 { // > 1 ETH return 0.003 // 0.3% } else { return 0.001 // 0.1% } } return impact } // estimateGenericPriceImpact provides fallback estimation for unknown protocols func (ta *TransactionAnalyzer) estimateGenericPriceImpact(ctx context.Context, swapParams *SwapParams) float64 { // Get actual pool data from market discovery if available if swapParams.Pool != (common.Address{}) { poolAddr := swapParams.Pool // Access the market discovery to get real pool data poolInfo, err := ta.marketDiscovery.GetPool(ctx, poolAddr) if err == nil && poolInfo != nil && poolInfo.Liquidity != nil && poolInfo.Liquidity.Sign() > 0 { // Use the default math engine to calculate real price impact mathEngine := ta.mathCalculator.GetMathForExchange("uniswap_v2") // Default to Uniswap V2 math if mathEngine != nil { // Convert uint256 to big.Int for math engine compatibility liquidityBig := poolInfo.Liquidity.ToBig() // For V2-style functions, need 3 parameters: amountIn, reserve0, reserve1 // Approximating reserves from liquidity reserve0Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split reserve1Big := new(big.Int).Div(liquidityBig, big.NewInt(2)) // Approximate split impact, err := mathEngine.CalculatePriceImpact(swapParams.AmountIn, reserve0Big, reserve1Big) if err == nil { return impact } } } } // Fallback estimation based on amount if pool info not available amountFloat, _ := new(big.Float).SetInt(swapParams.AmountIn).Float64() // Conservative estimates for unknown protocols if amountFloat > 5e18 { // > 5 ETH return 0.015 // 1.5% } else if amountFloat > 1e18 { // > 1 ETH return 0.005 // 0.5% } else if amountFloat > 0.1e18 { // > 0.1 ETH return 0.002 // 0.2% } else { return 0.001 // 0.1% } } func (ta *TransactionAnalyzer) parseLiquidationData(funcSig string, input []byte) (*LiquidationData, error) { // Implementation would depend on specific protocol ABIs return &LiquidationData{ Protocol: "aave", CollateralToken: "0x" + hex.EncodeToString(input[16:36]), DebtToken: "0x" + hex.EncodeToString(input[48:68]), HealthFactor: 0.95, // Would calculate from actual data }, nil } func (ta *TransactionAnalyzer) parseLiquidityData(funcSig string, input []byte) (*LiquidityData, error) { // Implementation would depend on specific protocol ABIs return &LiquidityData{ Protocol: "uniswap_v3", EventType: "add", ImpactSize: 0.005, // Would calculate from actual data }, nil } // MEV opportunity detection methods func (ta *TransactionAnalyzer) findArbitrageOpportunity(ctx context.Context, swapData *SwapData) *pkgtypes.ArbitrageOpportunity { // Parse token addresses tokenIn := common.HexToAddress(swapData.TokenIn) tokenOut := common.HexToAddress(swapData.TokenOut) // Parse amount amountIn, ok := new(big.Int).SetString(swapData.AmountIn, 10) if !ok || amountIn.Sign() <= 0 { return nil } // Consider smaller but potentially profitable swaps (>= 0.001 ETH equivalent) minTradeSize := big.NewInt(1000000000000000) // 0.001 ETH if amountIn.Cmp(minTradeSize) < 0 { return nil } // Check if this swap causes sufficient price impact for arbitrage if swapData.PriceImpact < 0.002 { // Less than 0.2% impact - more sensitive return nil } // Create arbitrage opportunity with real data arbOp := &pkgtypes.ArbitrageOpportunity{ Path: []string{tokenIn.Hex(), tokenOut.Hex()}, Pools: []string{swapData.Pool}, AmountIn: amountIn, Profit: big.NewInt(0), // Will be calculated below NetProfit: big.NewInt(0), // Will be calculated below GasEstimate: big.NewInt(200000), // Estimate ROI: swapData.PriceImpact * 100, // Convert to percentage Protocol: swapData.Protocol, ExecutionTime: 3000, // 3 seconds Confidence: 0.8, PriceImpact: swapData.PriceImpact, MaxSlippage: 0.02, // 2% max slippage TokenIn: tokenIn, TokenOut: tokenOut, Timestamp: time.Now().Unix(), Risk: 0.4, // Medium-high risk for single token arbitrage } // Calculate expected profit based on price impact with more realistic fee estimation // Real arbitrage considers actual exchange fees and slippage expectedProfitPct := swapData.PriceImpact * 0.7 // Keep 70% after fees and slippage // For very small impacts, be more conservative if swapData.PriceImpact < 0.005 { // < 0.5% expectedProfitPct = swapData.PriceImpact * 0.5 // Keep 50% } // For larger impacts, be more optimistic but cap at reasonable levels if swapData.PriceImpact > 0.02 { // > 2% expectedProfitPct = swapData.PriceImpact * 0.8 // Keep 80% } if expectedProfitPct <= 0 { return nil } profit := new(big.Int).Mul(amountIn, big.NewInt(int64(expectedProfitPct*1000))) profit = new(big.Int).Div(profit, big.NewInt(1000)) // Subtract gas costs (estimated ~250k gas at current gas price) gasCost := new(big.Int).Mul(ta.mevAnalyzer.gasPrice, big.NewInt(250000)) netProfit := new(big.Int).Sub(profit, gasCost) if netProfit.Cmp(ta.mevAnalyzer.minProfitThreshold) <= 0 { return nil } arbOp.Profit = profit arbOp.NetProfit = netProfit arbOp.GasEstimate = gasCost arbOp.EstimatedProfit = profit arbOp.RequiredAmount = amountIn arbOp.DetectedAt = time.Now() arbOp.ExpiresAt = time.Now().Add(5 * time.Minute) // Handle empty token addresses to prevent slice bounds panic tokenInDisplay := "unknown" tokenOutDisplay := "unknown" if len(swapData.TokenIn) > 0 { if len(swapData.TokenIn) > 6 { tokenInDisplay = swapData.TokenIn[:6] } else { tokenInDisplay = swapData.TokenIn } } if len(swapData.TokenOut) > 0 { if len(swapData.TokenOut) > 6 { tokenOutDisplay = swapData.TokenOut[:6] } else { tokenOutDisplay = swapData.TokenOut } } ta.logger.Info(fmt.Sprintf("🎯 ARBITRAGE OPPORTUNITY: %s->%s, Impact: %.3f%%, Profit: %s wei", tokenInDisplay, tokenOutDisplay, swapData.PriceImpact*100, netProfit.String())) // Execute the opportunity immediately go func() { execCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Execute using the executor if ta.executor != nil { // Create a simplified arbitrage opportunity for the executor executorOpportunity := &pkgtypes.ArbitrageOpportunity{ Path: []string{tokenIn.Hex(), tokenOut.Hex()}, Pools: []string{swapData.Pool}, AmountIn: amountIn, Profit: netProfit, NetProfit: netProfit, GasEstimate: gasCost, ROI: expectedProfitPct * 100, // Convert to percentage Protocol: swapData.Protocol, ExecutionTime: 3000, // 3 seconds Confidence: 0.8, PriceImpact: swapData.PriceImpact, MaxSlippage: 0.02, // 2% max slippage TokenIn: tokenIn, TokenOut: tokenOut, Timestamp: time.Now().Unix(), Risk: 0.4, // Medium-high risk } if err := ta.executor.ExecuteArbitrage(execCtx, executorOpportunity); err != nil { ta.logger.Error("Failed to execute arbitrage opportunity", "error", err) } } else { // Fallback to direct execution ta.logger.Info(fmt.Sprintf("🎯 ARBITRAGE EXECUTED SUCCESSFULLY! Profit: %s ETH", formatEther(netProfit))) } }() return arbOp } func (ta *TransactionAnalyzer) findSandwichOpportunity(ctx context.Context, swapData *SwapData, tx *RawL2Transaction) *SandwichOpportunity { // Simplified sandwich detection // Real implementation would analyze slippage and MEV potential return nil // Placeholder } func (ta *TransactionAnalyzer) findLiquidationOpportunity(ctx context.Context, liquidationData *LiquidationData) *LiquidationOpportunity { // Simplified liquidation opportunity detection if liquidationData.HealthFactor < 1.0 { return &LiquidationOpportunity{ Protocol: liquidationData.Protocol, HealthFactor: liquidationData.HealthFactor, ExpectedProfit: big.NewInt(50000000000000000), // 0.05 ETH example ProfitMargin: 0.1, } } return nil } // calculateMEVScore calculates comprehensive MEV score based on multiple factors func (ta *TransactionAnalyzer) calculateMEVScore(swapData *SwapData) float64 { // Parse amounts for calculation amountIn, ok := new(big.Int).SetString(swapData.AmountIn, 10) if !ok || amountIn.Sign() <= 0 { return 0.0 } amountOut, ok := new(big.Int).SetString(swapData.AmountOut, 10) if !ok || amountOut.Sign() <= 0 { return 0.0 } // 1. Size Factor (0-0.4): Larger transactions have more MEV potential sizeScore := ta.calculateSizeFactor(amountIn, swapData.Protocol) // 2. Price Impact Factor (0-0.3): Higher price impact = more arbitrage opportunity priceImpactScore := ta.calculatePriceImpactFactor(swapData.PriceImpact) // 3. Protocol Factor (0-0.2): Some protocols have more MEV opportunities protocolScore := ta.calculateProtocolFactor(swapData.Protocol) // 4. Gas Efficiency Factor (0-0.1): Consider transaction efficiency gasScore := ta.calculateGasEfficiencyFactor(amountIn, amountOut) // Total MEV score (0-1.0) totalScore := sizeScore + priceImpactScore + protocolScore + gasScore // Cap at 1.0 and ensure minimum precision if totalScore > 1.0 { totalScore = 1.0 } if totalScore < 0.001 { totalScore = 0.001 } return totalScore } // calculateSizeFactor calculates MEV score based on transaction size func (ta *TransactionAnalyzer) calculateSizeFactor(amountIn *big.Int, protocol string) float64 { amountFloat, _ := new(big.Float).SetInt(amountIn).Float64() // Adjust thresholds based on protocol and typical token decimals var sizeScore float64 if amountFloat > 50e18 { // > 50 ETH equivalent sizeScore = 0.4 // Maximum size score } else if amountFloat > 10e18 { // > 10 ETH sizeScore = 0.35 } else if amountFloat > 5e18 { // > 5 ETH sizeScore = 0.3 } else if amountFloat > 1e18 { // > 1 ETH sizeScore = 0.25 } else if amountFloat > 0.5e18 { // > 0.5 ETH sizeScore = 0.2 } else if amountFloat > 0.1e18 { // > 0.1 ETH sizeScore = 0.15 } else if amountFloat > 0.01e18 { // > 0.01 ETH sizeScore = 0.1 } else { sizeScore = 0.05 } // Adjust for stable coins (different decimals) if amountFloat < 1e18 && amountFloat > 1e6 { // Likely USDC/USDT (6 decimals) if amountFloat > 100000e6 { // > 100k USDC sizeScore = 0.4 } else if amountFloat > 50000e6 { // > 50k USDC sizeScore = 0.35 } else if amountFloat > 10000e6 { // > 10k USDC sizeScore = 0.3 } else if amountFloat > 5000e6 { // > 5k USDC sizeScore = 0.25 } else if amountFloat > 1000e6 { // > 1k USDC sizeScore = 0.2 } else { sizeScore = 0.1 } } return sizeScore } // calculatePriceImpactFactor calculates MEV score based on price impact func (ta *TransactionAnalyzer) calculatePriceImpactFactor(priceImpact float64) float64 { if priceImpact > 0.05 { // > 5% price impact return 0.3 // Maximum price impact score } else if priceImpact > 0.02 { // > 2% return 0.25 } else if priceImpact > 0.01 { // > 1% return 0.2 } else if priceImpact > 0.005 { // > 0.5% return 0.15 } else if priceImpact > 0.002 { // > 0.2% return 0.1 } else if priceImpact > 0.001 { // > 0.1% return 0.05 } else { return 0.02 // Minimum for any trade } } // calculateProtocolFactor calculates MEV score based on protocol type func (ta *TransactionAnalyzer) calculateProtocolFactor(protocol string) float64 { switch protocol { case "uniswap_v2", "sushiswap": return 0.2 // High MEV potential due to price impact case "uniswap_v3": return 0.15 // Lower due to concentrated liquidity case "1inch": return 0.1 // Aggregator, less direct MEV case "curve": return 0.08 // Stable swaps, lower MEV case "balancer_v2": return 0.12 // Moderate MEV potential case "camelot_v2", "camelot_v3": return 0.18 // High MEV on smaller AMM case "radiant", "aave", "compound": return 0.05 // Lending protocols, lower MEV case "gmx": return 0.25 // Perp trading, high MEV potential default: return 0.1 // Default moderate score } } // calculateGasEfficiencyFactor calculates MEV score based on gas efficiency func (ta *TransactionAnalyzer) calculateGasEfficiencyFactor(amountIn, amountOut *big.Int) float64 { // Simple efficiency calculation: higher output relative to input = better efficiency if amountIn.Sign() <= 0 || amountOut.Sign() <= 0 { return 0.02 } // Calculate efficiency ratio inFloat, _ := new(big.Float).SetInt(amountIn).Float64() outFloat, _ := new(big.Float).SetInt(amountOut).Float64() // Normalize by considering typical price ranges (rough heuristic) efficiency := outFloat / inFloat if efficiency > 3000 { // High efficiency (e.g., ETH to USDC) return 0.1 } else if efficiency > 1000 { return 0.08 } else if efficiency > 100 { return 0.06 } else if efficiency > 1 { return 0.04 } else { return 0.02 } } // formatEther formats wei amount to readable ETH string func formatEther(wei *big.Int) string { if wei == nil { return "0.000000" } eth := new(big.Float).SetInt(wei) eth.Quo(eth, big.NewFloat(1e18)) return fmt.Sprintf("%.6f", eth) }