package arbitrage_test import ( "context" "fmt" "log/slog" "math/big" "os" "time" "github.com/ethereum/go-ethereum/common" "github.com/your-org/mev-bot/pkg/arbitrage" "github.com/your-org/mev-bot/pkg/cache" "github.com/your-org/mev-bot/pkg/types" ) // ExampleDetector_BasicSetup demonstrates basic setup of the arbitrage detection system func ExampleDetector_BasicSetup() { // Create logger logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) // Create pool cache poolCache := cache.NewPoolCache() // Configure path finder pathFinderConfig := arbitrage.DefaultPathFinderConfig() pathFinderConfig.MaxHops = 3 pathFinderConfig.MinLiquidity = new(big.Int).Mul(big.NewInt(5000), big.NewInt(1e18)) pathFinder := arbitrage.NewPathFinder(poolCache, pathFinderConfig, logger) // Configure calculator calculatorConfig := arbitrage.DefaultCalculatorConfig() calculatorConfig.MinProfitWei = new(big.Int).Mul(big.NewInt(1), big.NewInt(1e17)) // 0.1 ETH calculatorConfig.MinROI = 0.03 // 3% gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(calculatorConfig, gasEstimator, logger) // Configure detector detectorConfig := arbitrage.DefaultDetectorConfig() detectorConfig.MaxPathsToEvaluate = 100 detectorConfig.OptimizeInput = true detector := arbitrage.NewDetector(detectorConfig, pathFinder, calculator, poolCache, logger) fmt.Printf("Arbitrage detection system initialized\n") fmt.Printf("Max paths to evaluate: %d\n", detectorConfig.MaxPathsToEvaluate) fmt.Printf("Min profit threshold: %s wei\n", calculatorConfig.MinProfitWei.String()) _ = detector // Use detector } // ExampleDetector_DetectOpportunities shows how to detect arbitrage opportunities func ExampleDetector_DetectOpportunities() { ctx := context.Background() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, // Reduce noise in example })) // Setup system poolCache := cache.NewPoolCache() pathFinder := arbitrage.NewPathFinder(poolCache, nil, logger) gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(nil, gasEstimator, logger) detector := arbitrage.NewDetector(nil, pathFinder, calculator, poolCache, logger) // Add sample pools to cache weth := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") usdc := common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8") pool1 := &types.PoolInfo{ Address: common.HexToAddress("0x1111"), Protocol: types.ProtocolUniswapV2, Token0: weth, Token1: usdc, Token0Decimals: 18, Token1Decimals: 6, Reserve0: new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18)), Reserve1: new(big.Int).Mul(big.NewInt(2000000), big.NewInt(1e6)), Liquidity: new(big.Int).Mul(big.NewInt(1000000), big.NewInt(1e18)), Fee: 30, IsActive: true, } pool2 := &types.PoolInfo{ Address: common.HexToAddress("0x2222"), Protocol: types.ProtocolUniswapV3, Token0: weth, Token1: usdc, Token0Decimals: 18, Token1Decimals: 6, Reserve0: new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18)), Reserve1: new(big.Int).Mul(big.NewInt(1900000), big.NewInt(1e6)), Liquidity: new(big.Int).Mul(big.NewInt(1000000), big.NewInt(1e18)), Fee: 30, IsActive: true, } _ = poolCache.Add(ctx, pool1) _ = poolCache.Add(ctx, pool2) // Detect opportunities opportunities, err := detector.DetectOpportunities(ctx, weth) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Found %d opportunities\n", len(opportunities)) for i, opp := range opportunities { fmt.Printf("Opportunity %d:\n", i+1) fmt.Printf(" Type: %s\n", opp.Type) fmt.Printf(" Net Profit: %s wei\n", opp.NetProfit.String()) fmt.Printf(" ROI: %.2f%%\n", opp.ROI*100) fmt.Printf(" Path Length: %d hops\n", len(opp.Path)) } } // ExampleDetector_MonitorSwaps demonstrates real-time swap monitoring func ExampleDetector_MonitorSwaps() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) // Setup system poolCache := cache.NewPoolCache() pathFinder := arbitrage.NewPathFinder(poolCache, nil, logger) gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(nil, gasEstimator, logger) detector := arbitrage.NewDetector(nil, pathFinder, calculator, poolCache, logger) // Create swap channel swapCh := make(chan *types.SwapEvent, 100) // Start monitoring in background go detector.MonitorSwaps(ctx, swapCh) // Simulate incoming swaps go func() { time.Sleep(500 * time.Millisecond) swap := &types.SwapEvent{ PoolAddress: common.HexToAddress("0x1111"), Protocol: types.ProtocolUniswapV2, TokenIn: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), TokenOut: common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"), AmountIn: big.NewInt(1e18), AmountOut: big.NewInt(2000e6), BlockNumber: 12345, } swapCh <- swap fmt.Println("Swap event sent to detector") time.Sleep(2 * time.Second) close(swapCh) }() // Wait for completion <-ctx.Done() fmt.Println("Monitoring complete") } // ExampleDetector_OpportunityStream shows how to consume the opportunity stream func ExampleDetector_OpportunityStream() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) // Setup system poolCache := cache.NewPoolCache() pathFinder := arbitrage.NewPathFinder(poolCache, nil, logger) gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(nil, gasEstimator, logger) detector := arbitrage.NewDetector(nil, pathFinder, calculator, poolCache, logger) // Get opportunity stream stream := detector.OpportunityStream() // Consume opportunities in background go func() { for { select { case <-ctx.Done(): return case opp, ok := <-stream: if !ok { return } fmt.Printf("Received opportunity: ID=%s, Profit=%s\n", opp.ID, opp.NetProfit.String()) } } }() // Simulate publishing opportunities go func() { time.Sleep(500 * time.Millisecond) opp := &arbitrage.Opportunity{ ID: "test-opp-1", Type: arbitrage.OpportunityTypeTwoPool, NetProfit: big.NewInt(1e17), } detector.PublishOpportunity(opp) time.Sleep(1 * time.Second) }() // Wait for completion <-ctx.Done() fmt.Println("Stream consumption complete") } // ExamplePathFinder_FindTwoPoolPaths shows how to find two-pool arbitrage paths func ExamplePathFinder_FindTwoPoolPaths() { ctx := context.Background() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) poolCache := cache.NewPoolCache() pathFinder := arbitrage.NewPathFinder(poolCache, nil, logger) weth := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") usdc := common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8") // Add pools with price discrepancy pool1 := &types.PoolInfo{ Address: common.HexToAddress("0x1111"), Protocol: types.ProtocolUniswapV2, Token0: weth, Token1: usdc, Token0Decimals: 18, Token1Decimals: 6, Reserve0: big.NewInt(1000e18), Reserve1: big.NewInt(2100000e6), // Higher price Liquidity: big.NewInt(1000000e18), Fee: 30, IsActive: true, } pool2 := &types.PoolInfo{ Address: common.HexToAddress("0x2222"), Protocol: types.ProtocolUniswapV3, Token0: weth, Token1: usdc, Token0Decimals: 18, Token1Decimals: 6, Reserve0: big.NewInt(1000e18), Reserve1: big.NewInt(1900000e6), // Lower price Liquidity: big.NewInt(1000000e18), Fee: 30, IsActive: true, } _ = poolCache.Add(ctx, pool1) _ = poolCache.Add(ctx, pool2) // Find two-pool paths paths, err := pathFinder.FindTwoPoolPaths(ctx, weth, usdc) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Found %d two-pool arbitrage paths\n", len(paths)) for i, path := range paths { fmt.Printf("Path %d: %d tokens, %d pools\n", i+1, len(path.Tokens), len(path.Pools)) } } // ExampleCalculator_CalculateProfitability shows profitability calculation func ExampleCalculator_CalculateProfitability() { ctx := context.Background() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(nil, gasEstimator, logger) weth := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") usdc := common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8") // Create test path pool := &types.PoolInfo{ Address: common.HexToAddress("0x1111"), Protocol: types.ProtocolUniswapV2, Token0: weth, Token1: usdc, Token0Decimals: 18, Token1Decimals: 6, Reserve0: big.NewInt(1000e18), Reserve1: big.NewInt(2000000e6), Liquidity: big.NewInt(1000000e18), Fee: 30, IsActive: true, } path := &arbitrage.Path{ Tokens: []common.Address{weth, usdc}, Pools: []*types.PoolInfo{pool}, Type: arbitrage.OpportunityTypeTwoPool, } // Calculate profitability inputAmount := big.NewInt(1e18) // 1 WETH gasPrice := big.NewInt(1e9) // 1 gwei opportunity, err := calculator.CalculateProfitability(ctx, path, inputAmount, gasPrice) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Input: %s wei\n", opportunity.InputAmount.String()) fmt.Printf("Output: %s wei\n", opportunity.OutputAmount.String()) fmt.Printf("Gross Profit: %s wei\n", opportunity.GrossProfit.String()) fmt.Printf("Gas Cost: %s wei\n", opportunity.GasCost.String()) fmt.Printf("Net Profit: %s wei\n", opportunity.NetProfit.String()) fmt.Printf("ROI: %.2f%%\n", opportunity.ROI*100) fmt.Printf("Price Impact: %.2f%%\n", opportunity.PriceImpact*100) fmt.Printf("Executable: %v\n", opportunity.Executable) } // ExampleGasEstimator_EstimateGasCost demonstrates gas estimation func ExampleGasEstimator_EstimateGasCost() { ctx := context.Background() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) gasEstimator := arbitrage.NewGasEstimator(nil, logger) // Create multi-hop path path := &arbitrage.Path{ Pools: []*types.PoolInfo{ {Protocol: types.ProtocolUniswapV2}, {Protocol: types.ProtocolUniswapV3}, {Protocol: types.ProtocolCurve}, }, } gasPrice := big.NewInt(2e9) // 2 gwei gasCost, err := gasEstimator.EstimateGasCost(ctx, path, gasPrice) if err != nil { fmt.Printf("Error: %v\n", err) return } // Calculate gas units gasUnits := new(big.Int).Div(gasCost, gasPrice) fmt.Printf("Path with %d hops\n", len(path.Pools)) fmt.Printf("Estimated gas: %s units\n", gasUnits.String()) fmt.Printf("Gas price: %s wei (%.2f gwei)\n", gasPrice.String(), float64(gasPrice.Int64())/1e9) fmt.Printf("Total cost: %s wei\n", gasCost.String()) // Convert to ETH costEth := new(big.Float).Quo( new(big.Float).SetInt(gasCost), new(big.Float).SetInt64(1e18), ) fmt.Printf("Cost in ETH: %s\n", costEth.Text('f', 6)) } // ExampleDetector_RankOpportunities shows opportunity ranking func ExampleDetector_RankOpportunities() { logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) poolCache := cache.NewPoolCache() pathFinder := arbitrage.NewPathFinder(poolCache, nil, logger) gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(nil, gasEstimator, logger) detector := arbitrage.NewDetector(nil, pathFinder, calculator, poolCache, logger) // Create sample opportunities with different priorities opportunities := []*arbitrage.Opportunity{ { ID: "low-priority", Priority: 50, NetProfit: big.NewInt(1e17), }, { ID: "high-priority", Priority: 500, NetProfit: big.NewInt(1e18), }, { ID: "medium-priority", Priority: 200, NetProfit: big.NewInt(5e17), }, } // Rank opportunities ranked := detector.RankOpportunities(opportunities) fmt.Println("Opportunities ranked by priority:") for i, opp := range ranked { fmt.Printf("%d. ID=%s, Priority=%d, Profit=%s wei\n", i+1, opp.ID, opp.Priority, opp.NetProfit.String()) } } // ExampleDetector_Statistics shows how to track statistics func ExampleDetector_Statistics() { ctx := context.Background() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })) poolCache := cache.NewPoolCache() pathFinder := arbitrage.NewPathFinder(poolCache, nil, logger) gasEstimator := arbitrage.NewGasEstimator(nil, logger) calculator := arbitrage.NewCalculator(nil, gasEstimator, logger) detector := arbitrage.NewDetector(nil, pathFinder, calculator, poolCache, logger) // Add sample pools weth := common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") usdc := common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8") pool := &types.PoolInfo{ Address: common.HexToAddress("0x1111"), Protocol: types.ProtocolUniswapV2, Token0: weth, Token1: usdc, Token0Decimals: 18, Token1Decimals: 6, Reserve0: big.NewInt(1000e18), Reserve1: big.NewInt(2000000e6), Liquidity: big.NewInt(1000000e18), Fee: 30, IsActive: true, } _ = poolCache.Add(ctx, pool) // Detect opportunities _, _ = detector.DetectOpportunities(ctx, weth) // Get statistics stats := detector.GetStats() fmt.Printf("Detection Statistics:\n") fmt.Printf(" Total Detected: %d\n", stats.TotalDetected) fmt.Printf(" Total Profitable: %d\n", stats.TotalProfitable) fmt.Printf(" Total Executable: %d\n", stats.TotalExecutable) if stats.MaxProfit != nil { fmt.Printf(" Max Profit: %s wei\n", stats.MaxProfit.String()) } if stats.AverageProfit != nil { fmt.Printf(" Average Profit: %s wei\n", stats.AverageProfit.String()) } }