diff --git a/pkg/marketdata/logger.go b/pkg/marketdata/logger.go index cf5004d..f60cafa 100644 --- a/pkg/marketdata/logger.go +++ b/pkg/marketdata/logger.go @@ -18,6 +18,24 @@ import ( "github.com/fraktal/mev-beta/pkg/events" ) +// Helper functions to safely convert potentially nil pointers to strings + +// safeStringBigInt safely converts a *big.Int to string, returning "0" if nil +func safeStringBigInt(n *big.Int) string { + if n == nil { + return "0" + } + return n.String() +} + +// safeStringUint256 safely converts a *uint256.Int to string, returning "0" if nil +func safeStringUint256(n *uint256.Int) string { + if n == nil { + return "0" + } + return n.String() +} + // MarketDataLogger provides comprehensive logging of swap and liquidity events type MarketDataLogger struct { logger *logger.Logger @@ -161,16 +179,16 @@ func (mdl *MarketDataLogger) LogSwapEvent(ctx context.Context, event events.Even "token1": token1Symbol, "token0Address": swapData.Token0.Hex(), "token1Address": swapData.Token1.Hex(), - "amount0In": swapData.Amount0In.String(), - "amount1In": swapData.Amount1In.String(), - "amount0Out": swapData.Amount0Out.String(), - "amount1Out": swapData.Amount1Out.String(), + "amount0In": safeStringBigInt(swapData.Amount0In), + "amount1In": safeStringBigInt(swapData.Amount1In), + "amount0Out": safeStringBigInt(swapData.Amount0Out), + "amount1Out": safeStringBigInt(swapData.Amount1Out), "amountInUSD": swapData.AmountInUSD, "amountOutUSD": swapData.AmountOutUSD, "feeUSD": swapData.FeeUSD, "priceImpact": swapData.PriceImpact, - "sqrtPriceX96": swapData.SqrtPriceX96.String(), - "liquidity": swapData.Liquidity.String(), + "sqrtPriceX96": safeStringUint256(swapData.SqrtPriceX96), + "liquidity": safeStringUint256(swapData.Liquidity), "tick": swapData.Tick, } @@ -239,9 +257,9 @@ func (mdl *MarketDataLogger) LogLiquidityEvent(ctx context.Context, event events "token1": token1Symbol, "token0Address": liquidityData.Token0.Hex(), "token1Address": liquidityData.Token1.Hex(), - "amount0": liquidityData.Amount0.String(), - "amount1": liquidityData.Amount1.String(), - "liquidity": liquidityData.Liquidity.String(), + "amount0": safeStringBigInt(liquidityData.Amount0), + "amount1": safeStringBigInt(liquidityData.Amount1), + "liquidity": safeStringUint256(liquidityData.Liquidity), "amount0USD": liquidityData.Amount0USD, "amount1USD": liquidityData.Amount1USD, "totalUSD": liquidityData.TotalUSD, diff --git a/pkg/scanner/market/scanner.go b/pkg/scanner/market/scanner.go index d6a691e..e0213fb 100644 --- a/pkg/scanner/market/scanner.go +++ b/pkg/scanner/market/scanner.go @@ -554,13 +554,33 @@ func (s *MarketScanner) findTriangularArbitrageOpportunities(event events.Event) // Close the loop by adding the first token at the end tokenPaths = append(tokenPaths, path.Tokens[0].Hex()) + // Properly initialize all required fields + now := time.Now() opportunity := stypes.ArbitrageOpportunity{ - Path: tokenPaths, - Pools: []string{}, // Pool addresses will be discovered dynamically - Profit: netProfit, - GasEstimate: gasEstimate, - ROI: roi, - Protocol: fmt.Sprintf("Triangular_%s", path.Name), + ID: fmt.Sprintf("arb_%d_%s", now.Unix(), path.Tokens[0].Hex()[:10]), + Path: tokenPaths, + Pools: []string{}, // Pool addresses will be discovered dynamically + AmountIn: testAmount, + Profit: profit, + NetProfit: netProfit, + GasEstimate: gasEstimate, + GasCost: gasEstimate, // Set gas cost same as estimate + EstimatedProfit: netProfit, + RequiredAmount: testAmount, + ROI: roi, + Protocol: fmt.Sprintf("Triangular_%s", path.Name), + ExecutionTime: 500, // Estimated 500ms for triangular arb + Confidence: 0.5, // Medium confidence for triangular + PriceImpact: 0.0, // Will be calculated dynamically + MaxSlippage: 2.0, // 2% max slippage + TokenIn: path.Tokens[0], + TokenOut: path.Tokens[0], // Circular, starts and ends with same token + Timestamp: now.Unix(), + DetectedAt: now, + ExpiresAt: now.Add(5 * time.Second), // 5 second expiry + Urgency: 5, // Medium urgency + Risk: 0.3, // Low-medium risk + Profitable: netProfit.Sign() > 0, } opportunities = append(opportunities, opportunity) @@ -1111,8 +1131,12 @@ func (s *MarketScanner) fetchPoolData(poolAddress string) (*CachedData, error) { LastUpdated: time.Now(), } + liquidityStr := "0" + if poolState.Liquidity != nil { + liquidityStr = poolState.Liquidity.String() + } s.logger.Info(fmt.Sprintf("Fetched real pool data for %s: Token0=%s, Token1=%s, Fee=%d, Liquidity=%s", - address.Hex(), poolState.Token0.Hex(), poolState.Token1.Hex(), poolState.Fee, poolState.Liquidity.String())) + address.Hex(), poolState.Token0.Hex(), poolState.Token1.Hex(), poolState.Fee, liquidityStr)) return poolData, nil } @@ -1309,8 +1333,20 @@ func (s *MarketScanner) calculateUniswapV3Output(amountIn *big.Int, pool *Cached // Subtract fee from output finalAmountOut := new(big.Int).Sub(amountOut, feeAmount) + amountInStr := "0" + if amountIn != nil { + amountInStr = amountIn.String() + } + amountOutStr := "0" + if amountOut != nil { + amountOutStr = amountOut.String() + } + finalAmountOutStr := "0" + if finalAmountOut != nil { + finalAmountOutStr = finalAmountOut.String() + } s.logger.Debug(fmt.Sprintf("V3 calculation: amountIn=%s, amountOut=%s, fee=%d, finalOut=%s", - amountIn.String(), amountOut.String(), fee, finalAmountOut.String())) + amountInStr, amountOutStr, fee, finalAmountOutStr)) return finalAmountOut, nil } diff --git a/pkg/scanner/swap/analyzer.go b/pkg/scanner/swap/analyzer.go index cec75fd..bc41fe6 100644 --- a/pkg/scanner/swap/analyzer.go +++ b/pkg/scanner/swap/analyzer.go @@ -429,17 +429,31 @@ func (s *SwapAnalyzer) calculatePriceMovement(event events.Event, poolData *mark priceDiff := new(big.Float).Sub(swapPrice, currentPrice) priceDiff.Abs(priceDiff) - priceImpactFloat := new(big.Float).Quo(priceDiff, currentPrice) - priceImpact, _ = priceImpactFloat.Float64() + // Check if currentPrice is zero to prevent division by zero + zero := new(big.Float).SetFloat64(0.0) + if currentPrice.Cmp(zero) != 0 { + priceImpactFloat := new(big.Float).Quo(priceDiff, currentPrice) + priceImpact, _ = priceImpactFloat.Float64() + + // Validate: reject impossible price impacts (>1000% = 10.0) + if priceImpact > 10.0 { + s.logger.Warn(fmt.Sprintf("Price impact too large (%.2f), capping at 0", priceImpact)) + priceImpact = 0.0 + } + } } + // Use absolute values for amounts (UniswapV3 uses signed int256, but amounts should be positive) + amountIn := new(big.Int).Abs(event.Amount0) + amountOut := new(big.Int).Abs(event.Amount1) + movement := &market.PriceMovement{ Token0: event.Token0.Hex(), Token1: event.Token1.Hex(), Pool: event.PoolAddress.Hex(), Protocol: event.Protocol, - AmountIn: event.Amount0, - AmountOut: event.Amount1, + AmountIn: amountIn, + AmountOut: amountOut, PriceBefore: currentPrice, PriceAfter: currentPrice, // For now, assume same price (could be calculated based on swap) PriceImpact: priceImpact, @@ -448,7 +462,11 @@ func (s *SwapAnalyzer) calculatePriceMovement(event events.Event, poolData *mark Timestamp: time.Now(), } - s.logger.Debug(fmt.Sprintf("Price movement calculated: impact=%.6f%%, amount_in=%s", priceImpact*100, event.Amount0.String())) + amount0Str := "0" + if event.Amount0 != nil { + amount0Str = event.Amount0.String() + } + s.logger.Debug(fmt.Sprintf("Price movement calculated: impact=%.6f%%, amount_in=%s", priceImpact*100, amount0Str)) return movement, nil } @@ -501,27 +519,79 @@ func (s *SwapAnalyzer) findArbitrageOpportunities(event events.Event, movement * // Calculate price difference priceDiff := new(big.Float).Sub(currentPrice, relatedPrice) + // Check if relatedPrice is zero to prevent division by zero + zero := new(big.Float).SetFloat64(0.0) + if relatedPrice.Cmp(zero) == 0 { + s.logger.Debug(fmt.Sprintf("Skipping pool %s: related price is zero", pool.Address.Hex())) + continue + } + priceDiffRatio := new(big.Float).Quo(priceDiff, relatedPrice) // If there's a significant price difference, we might have an arbitrage opportunity priceDiffFloat, _ := priceDiffRatio.Float64() + + // Validate: reject impossible price differences (>1000% = 10.0) + if priceDiffFloat > 10.0 || priceDiffFloat < -10.0 { + s.logger.Debug(fmt.Sprintf("Skipping pool %s: price difference too large (%.2f)", pool.Address.Hex(), priceDiffFloat)) + continue + } + + // Take absolute value for comparison + priceDiffAbs := priceDiffFloat + if priceDiffAbs < 0 { + priceDiffAbs = -priceDiffAbs + } // Lower threshold for Arbitrum where spreads are smaller arbitrageThreshold := 0.001 // 0.1% threshold instead of 0.5% - if priceDiffFloat > arbitrageThreshold { + if priceDiffAbs > arbitrageThreshold { // Estimate potential profit estimatedProfit := marketScanner.EstimateProfit(event, pool, priceDiffFloat) if estimatedProfit != nil && estimatedProfit.Sign() > 0 { - opp := stypes.ArbitrageOpportunity{ - Path: []string{event.Token0.Hex(), event.Token1.Hex()}, - Pools: []string{event.PoolAddress.Hex(), pool.Address.Hex()}, - Profit: estimatedProfit, - GasEstimate: big.NewInt(300000), // Estimated gas cost - ROI: priceDiffFloat * 100, // Convert to percentage - Protocol: fmt.Sprintf("%s->%s", event.Protocol, pool.Protocol), + // Calculate gas cost in wei (300k gas * current gas price estimate) + gasPrice := big.NewInt(100000000) // 0.1 gwei default + gasUnits := big.NewInt(300000) + gasCost := new(big.Int).Mul(gasPrice, gasUnits) + + // Calculate net profit after gas + netProfit := new(big.Int).Sub(estimatedProfit, gasCost) + + // Only create opportunity if still profitable after gas + if netProfit.Sign() > 0 { + now := time.Now() + // Use a reasonable test amount (e.g., 0.1 ETH in wei) + testAmount := big.NewInt(100000000000000000) // 0.1 ETH + + opp := stypes.ArbitrageOpportunity{ + ID: fmt.Sprintf("arb_%d_%s", now.Unix(), event.PoolAddress.Hex()[:10]), + Path: []string{event.Token0.Hex(), event.Token1.Hex()}, + Pools: []string{event.PoolAddress.Hex(), pool.Address.Hex()}, + AmountIn: testAmount, + Profit: estimatedProfit, + NetProfit: netProfit, + GasEstimate: gasUnits, + GasCost: gasCost, + EstimatedProfit: netProfit, + RequiredAmount: testAmount, + ROI: priceDiffAbs * 100, // Convert to percentage (use absolute value) + Protocol: fmt.Sprintf("%s->%s", event.Protocol, pool.Protocol), + ExecutionTime: 200, // Estimated 200ms for direct arb + Confidence: 0.7, // Higher confidence for direct arb + PriceImpact: priceDiffAbs, // Use absolute value for price impact + MaxSlippage: 1.0, // 1% max slippage + TokenIn: event.Token0, + TokenOut: event.Token1, + Timestamp: now.Unix(), + DetectedAt: now, + ExpiresAt: now.Add(3 * time.Second), // 3 second expiry for direct arb + Urgency: 7, // Higher urgency + Risk: 0.2, // Lower risk + Profitable: true, + } + opportunities = append(opportunities, opp) + s.logger.Info(fmt.Sprintf("Found arbitrage opportunity: %+v", opp)) } - opportunities = append(opportunities, opp) - s.logger.Info(fmt.Sprintf("Found arbitrage opportunity: %+v", opp)) } } }