package main import ( "context" "fmt" "os" "os/signal" "syscall" "time" "github.com/fraktal/mev-beta/internal/config" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/internal/ratelimit" "github.com/fraktal/mev-beta/pkg/market" "github.com/fraktal/mev-beta/pkg/metrics" "github.com/fraktal/mev-beta/pkg/monitor" "github.com/fraktal/mev-beta/pkg/scanner" "github.com/urfave/cli/v2" ) func main() { app := &cli.App{ Name: "mev-bot", Usage: "An MEV bot that monitors Arbitrum sequencer for swap opportunities", Commands: []*cli.Command{ { Name: "start", Usage: "Start the MEV bot", Action: func(c *cli.Context) error { return startBot() }, }, { Name: "scan", Usage: "Scan for potential arbitrage opportunities", Action: func(c *cli.Context) error { return scanOpportunities() }, }, }, } if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } func startBot() error { // Load configuration configFile := "config/config.yaml" if _, err := os.Stat("config/local.yaml"); err == nil { configFile = "config/local.yaml" } cfg, err := config.Load(configFile) if err != nil { return fmt.Errorf("failed to load config: %w", err) } // Initialize logger log := logger.New(cfg.Log.Level, cfg.Log.Format, cfg.Log.File) log.Debug(fmt.Sprintf("RPC Endpoint: %s", cfg.Arbitrum.RPCEndpoint)) log.Debug(fmt.Sprintf("WS Endpoint: %s", cfg.Arbitrum.WSEndpoint)) log.Debug(fmt.Sprintf("Chain ID: %d", cfg.Arbitrum.ChainID)) log.Info("Starting MEV bot with L2 message processing...") // Initialize metrics collector metricsCollector := metrics.NewMetricsCollector(log) // Start metrics server if enabled var metricsServer *metrics.MetricsServer if os.Getenv("METRICS_ENABLED") == "true" { metricsPort := os.Getenv("METRICS_PORT") if metricsPort == "" { metricsPort = "9090" } metricsServer = metrics.NewMetricsServer(metricsCollector, log, metricsPort) go func() { if err := metricsServer.Start(); err != nil { log.Error("Metrics server error: ", err) } }() } // Create rate limiter manager rateLimiter := ratelimit.NewLimiterManager(&cfg.Arbitrum) // Create market manager marketMgr := market.NewMarketManager(&cfg.Uniswap, log) // Create market scanner scanner := scanner.NewMarketScanner(&cfg.Bot, log) // Create Arbitrum monitor with concurrency support monitor, err := monitor.NewArbitrumMonitor( &cfg.Arbitrum, &cfg.Bot, log, rateLimiter, marketMgr, scanner, ) if err != nil { return fmt.Errorf("failed to create Arbitrum monitor: %w", err) } // Set up context with cancellation ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Set up signal handling for graceful shutdown sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // Start monitoring in a goroutine monitorDone := make(chan error, 1) go func() { monitorDone <- monitor.Start(ctx) }() log.Info("MEV bot started. Press Ctrl+C to stop.") // Wait for signal or monitor completion select { case <-sigChan: log.Info("Received shutdown signal...") case err := <-monitorDone: if err != nil { log.Error("Monitor error: ", err) } log.Info("Monitor stopped...") } // Cancel context to stop monitor cancel() // Create shutdown timeout context (5 seconds) shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) defer shutdownCancel() // Shutdown components with timeout shutdownDone := make(chan struct{}) go func() { defer close(shutdownDone) // Stop the monitor monitor.Stop() // Stop the scanner scanner.Stop() // Stop metrics server if running if metricsServer != nil { metricsServer.Stop() } }() // Wait for shutdown or timeout select { case <-shutdownDone: log.Info("MEV bot stopped gracefully.") case <-shutdownCtx.Done(): log.Error("Shutdown timeout exceeded, forcing exit...") os.Exit(1) } return nil } func scanOpportunities() error { fmt.Println("Scanning for arbitrage opportunities...") // TODO: Implement scanning logic return nil }