176 lines
4.1 KiB
Go
176 lines
4.1 KiB
Go
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
|
|
}
|