package config import ( "fmt" "net/url" "os" "regexp" "strconv" "strings" "time" "gopkg.in/yaml.v3" ) // Config represents the application configuration type Config struct { Arbitrum ArbitrumConfig `yaml:"arbitrum"` Bot BotConfig `yaml:"bot"` Uniswap UniswapConfig `yaml:"uniswap"` Log LogConfig `yaml:"log"` Database DatabaseConfig `yaml:"database"` Ethereum EthereumConfig `yaml:"ethereum"` Contracts ContractsConfig `yaml:"contracts"` Arbitrage ArbitrageConfig `yaml:"arbitrage"` } // ArbitrumConfig represents the Arbitrum node configuration type ArbitrumConfig struct { // Primary RPC endpoint RPCEndpoint string `yaml:"rpc_endpoint"` // WebSocket endpoint for Arbitrum node (optional) WSEndpoint string `yaml:"ws_endpoint"` // Chain ID for Arbitrum (42161 for mainnet) ChainID int64 `yaml:"chain_id"` // Rate limiting configuration for RPC endpoint RateLimit RateLimitConfig `yaml:"rate_limit"` // Fallback RPC endpoints FallbackEndpoints []EndpointConfig `yaml:"fallback_endpoints"` } // EndpointConfig represents a fallback RPC endpoint configuration type EndpointConfig struct { // RPC endpoint URL URL string `yaml:"url"` // Rate limiting configuration for this endpoint RateLimit RateLimitConfig `yaml:"rate_limit"` } // RateLimitConfig represents rate limiting configuration type RateLimitConfig struct { // Maximum requests per second RequestsPerSecond int `yaml:"requests_per_second"` // Maximum concurrent requests MaxConcurrent int `yaml:"max_concurrent"` // Burst size for rate limiting Burst int `yaml:"burst"` } // BotConfig represents the bot configuration type BotConfig struct { // Enable or disable the bot Enabled bool `yaml:"enabled"` // Polling interval in seconds PollingInterval int `yaml:"polling_interval"` // Minimum profit threshold in USD MinProfitThreshold float64 `yaml:"min_profit_threshold"` // Gas price multiplier (for faster transactions) GasPriceMultiplier float64 `yaml:"gas_price_multiplier"` // Maximum number of concurrent workers for processing MaxWorkers int `yaml:"max_workers"` // Buffer size for channels ChannelBufferSize int `yaml:"channel_buffer_size"` // Timeout for RPC calls in seconds RPCTimeout int `yaml:"rpc_timeout"` } // UniswapConfig represents the Uniswap configuration type UniswapConfig struct { // Factory contract address FactoryAddress string `yaml:"factory_address"` // Position manager contract address PositionManagerAddress string `yaml:"position_manager_address"` // Supported fee tiers FeeTiers []int64 `yaml:"fee_tiers"` // Cache configuration for pool data Cache CacheConfig `yaml:"cache"` } // CacheConfig represents caching configuration type CacheConfig struct { // Enable or disable caching Enabled bool `yaml:"enabled"` // Cache expiration time in seconds Expiration int `yaml:"expiration"` // Maximum cache size MaxSize int `yaml:"max_size"` } // LogConfig represents the logging configuration type LogConfig struct { // Log level (debug, info, warn, error) Level string `yaml:"level"` // Log format (json, text) Format string `yaml:"format"` // Log file path (empty for stdout) File string `yaml:"file"` } // DatabaseConfig represents the database configuration type DatabaseConfig struct { // Database file path File string `yaml:"file"` // Maximum number of open connections MaxOpenConnections int `yaml:"max_open_connections"` // Maximum number of idle connections MaxIdleConnections int `yaml:"max_idle_connections"` } // EthereumConfig represents the Ethereum account configuration type EthereumConfig struct { // Private key for transaction signing PrivateKey string `yaml:"private_key"` // Account address AccountAddress string `yaml:"account_address"` // Gas price multiplier (for faster transactions) GasPriceMultiplier float64 `yaml:"gas_price_multiplier"` } // ContractsConfig represents the smart contract addresses type ContractsConfig struct { // Arbitrage executor contract address ArbitrageExecutor string `yaml:"arbitrage_executor"` // Flash swapper contract address FlashSwapper string `yaml:"flash_swapper"` // Authorized caller addresses AuthorizedCallers []string `yaml:"authorized_callers"` // Authorized DEX addresses AuthorizedDEXes []string `yaml:"authorized_dexes"` } // Load loads the configuration from a file func Load(filename string) (*Config, error) { // Read the config file data, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } // Expand environment variables in the raw YAML expandedData := expandEnvVars(string(data)) // Parse the YAML var config Config if err := yaml.Unmarshal([]byte(expandedData), &config); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) } // Override with environment variables if they exist config.OverrideWithEnv() return &config, nil } // expandEnvVars expands ${VAR} and $VAR patterns in the given string func expandEnvVars(s string) string { // Pattern to match ${VAR} and $VAR envVarPattern := regexp.MustCompile(`\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)`) return envVarPattern.ReplaceAllStringFunc(s, func(match string) string { var varName string // Handle ${VAR} format if strings.HasPrefix(match, "${") && strings.HasSuffix(match, "}") { varName = match[2 : len(match)-1] } else if strings.HasPrefix(match, "$") { // Handle $VAR format varName = match[1:] } // Get environment variable value if value := os.Getenv(varName); value != "" { return value } // Return empty string if environment variable is not set // This prevents invalid YAML when variables are missing return "" }) } // OverrideWithEnv overrides configuration with environment variables func (c *Config) OverrideWithEnv() { // Override RPC endpoint if rpcEndpoint := os.Getenv("ARBITRUM_RPC_ENDPOINT"); rpcEndpoint != "" { c.Arbitrum.RPCEndpoint = rpcEndpoint } // Override WebSocket endpoint if wsEndpoint := os.Getenv("ARBITRUM_WS_ENDPOINT"); wsEndpoint != "" { c.Arbitrum.WSEndpoint = wsEndpoint } // Override fallback endpoints from environment if fallbackEndpoints := os.Getenv("ARBITRUM_FALLBACK_ENDPOINTS"); fallbackEndpoints != "" { endpoints := strings.Split(fallbackEndpoints, ",") c.Arbitrum.FallbackEndpoints = make([]EndpointConfig, 0, len(endpoints)) for _, endpoint := range endpoints { endpoint = strings.TrimSpace(endpoint) if endpoint != "" { c.Arbitrum.FallbackEndpoints = append(c.Arbitrum.FallbackEndpoints, EndpointConfig{ URL: endpoint, RateLimit: RateLimitConfig{ RequestsPerSecond: 100, MaxConcurrent: 10, Burst: 20, }, }) } } } // Override rate limit settings if rps := os.Getenv("RPC_REQUESTS_PER_SECOND"); rps != "" { if val, err := strconv.Atoi(rps); err == nil { c.Arbitrum.RateLimit.RequestsPerSecond = val } } if maxConcurrent := os.Getenv("RPC_MAX_CONCURRENT"); maxConcurrent != "" { if val, err := strconv.Atoi(maxConcurrent); err == nil { c.Arbitrum.RateLimit.MaxConcurrent = val } } // Override bot settings if maxWorkers := os.Getenv("BOT_MAX_WORKERS"); maxWorkers != "" { if val, err := strconv.Atoi(maxWorkers); err == nil { c.Bot.MaxWorkers = val } } if channelBufferSize := os.Getenv("BOT_CHANNEL_BUFFER_SIZE"); channelBufferSize != "" { if val, err := strconv.Atoi(channelBufferSize); err == nil { c.Bot.ChannelBufferSize = val } } // Override Ethereum settings if privateKey := os.Getenv("ETHEREUM_PRIVATE_KEY"); privateKey != "" { c.Ethereum.PrivateKey = privateKey } if accountAddress := os.Getenv("ETHEREUM_ACCOUNT_ADDRESS"); accountAddress != "" { c.Ethereum.AccountAddress = accountAddress } if gasPriceMultiplier := os.Getenv("ETHEREUM_GAS_PRICE_MULTIPLIER"); gasPriceMultiplier != "" { if val, err := strconv.ParseFloat(gasPriceMultiplier, 64); err == nil { c.Ethereum.GasPriceMultiplier = val } } // Override contract addresses if arbitrageExecutor := os.Getenv("CONTRACT_ARBITRAGE_EXECUTOR"); arbitrageExecutor != "" { c.Contracts.ArbitrageExecutor = arbitrageExecutor } if flashSwapper := os.Getenv("CONTRACT_FLASH_SWAPPER"); flashSwapper != "" { c.Contracts.FlashSwapper = flashSwapper } } // ValidateEnvironmentVariables validates all required environment variables func (c *Config) ValidateEnvironmentVariables() error { // Validate RPC endpoint if c.Arbitrum.RPCEndpoint == "" { return fmt.Errorf("ARBITRUM_RPC_ENDPOINT environment variable is required") } if err := validateRPCEndpoint(c.Arbitrum.RPCEndpoint); err != nil { return fmt.Errorf("invalid ARBITRUM_RPC_ENDPOINT: %w", err) } // Validate WebSocket endpoint if provided if c.Arbitrum.WSEndpoint != "" { if err := validateRPCEndpoint(c.Arbitrum.WSEndpoint); err != nil { return fmt.Errorf("invalid ARBITRUM_WS_ENDPOINT: %w", err) } } // Validate Ethereum private key if c.Ethereum.PrivateKey == "" { return fmt.Errorf("ETHEREUM_PRIVATE_KEY environment variable is required") } // Validate account address if c.Ethereum.AccountAddress == "" { return fmt.Errorf("ETHEREUM_ACCOUNT_ADDRESS environment variable is required") } // Validate contract addresses if c.Contracts.ArbitrageExecutor == "" { return fmt.Errorf("CONTRACT_ARBITRAGE_EXECUTOR environment variable is required") } if c.Contracts.FlashSwapper == "" { return fmt.Errorf("CONTRACT_FLASH_SWAPPER environment variable is required") } // Validate numeric values if c.Arbitrum.RateLimit.RequestsPerSecond < 0 { return fmt.Errorf("RPC_REQUESTS_PER_SECOND must be non-negative") } if c.Arbitrum.RateLimit.MaxConcurrent < 0 { return fmt.Errorf("RPC_MAX_CONCURRENT must be non-negative") } if c.Bot.MaxWorkers <= 0 { return fmt.Errorf("BOT_MAX_WORKERS must be positive") } if c.Bot.ChannelBufferSize < 0 { return fmt.Errorf("BOT_CHANNEL_BUFFER_SIZE must be non-negative") } if c.Ethereum.GasPriceMultiplier < 0 { return fmt.Errorf("ETHEREUM_GAS_PRICE_MULTIPLIER must be non-negative") } return nil } // validateRPCEndpoint validates RPC endpoint URL for security and format func validateRPCEndpoint(endpoint string) error { if endpoint == "" { return fmt.Errorf("RPC endpoint cannot be empty") } u, err := url.Parse(endpoint) if err != nil { return fmt.Errorf("invalid RPC endpoint URL: %w", err) } // Check for valid schemes switch u.Scheme { case "http", "https", "ws", "wss": // Valid schemes default: return fmt.Errorf("invalid RPC scheme: %s (must be http, https, ws, or wss)", u.Scheme) } // Check for localhost/private networks in production if strings.Contains(u.Hostname(), "localhost") || strings.Contains(u.Hostname(), "127.0.0.1") { // Allow localhost only if explicitly enabled if os.Getenv("MEV_BOT_ALLOW_LOCALHOST") != "true" { return fmt.Errorf("localhost RPC endpoints not allowed in production (set MEV_BOT_ALLOW_LOCALHOST=true to override)") } } // Validate hostname is not empty if u.Hostname() == "" { return fmt.Errorf("RPC endpoint must have a valid hostname") } return nil } // ArbitrageConfig represents the arbitrage service configuration type ArbitrageConfig struct { // Enable or disable arbitrage service Enabled bool `yaml:"enabled"` // Contract addresses ArbitrageContractAddress string `yaml:"arbitrage_contract_address"` FlashSwapContractAddress string `yaml:"flash_swap_contract_address"` // Profitability settings MinProfitWei int64 `yaml:"min_profit_wei"` MinROIPercent float64 `yaml:"min_roi_percent"` MinSignificantSwapSize int64 `yaml:"min_significant_swap_size"` SlippageTolerance float64 `yaml:"slippage_tolerance"` // Scanning configuration MinScanAmountWei int64 `yaml:"min_scan_amount_wei"` MaxScanAmountWei int64 `yaml:"max_scan_amount_wei"` // Gas configuration MaxGasPriceWei int64 `yaml:"max_gas_price_wei"` // Execution limits MaxConcurrentExecutions int `yaml:"max_concurrent_executions"` MaxOpportunitiesPerEvent int `yaml:"max_opportunities_per_event"` // Timing settings OpportunityTTL time.Duration `yaml:"opportunity_ttl"` MaxPathAge time.Duration `yaml:"max_path_age"` StatsUpdateInterval time.Duration `yaml:"stats_update_interval"` // Pool discovery configuration PoolDiscoveryConfig PoolDiscoveryConfig `yaml:"pool_discovery"` } // PoolDiscoveryConfig represents pool discovery service configuration type PoolDiscoveryConfig struct { // Enable or disable pool discovery Enabled bool `yaml:"enabled"` // Block range to scan for new pools BlockRange uint64 `yaml:"block_range"` // Polling interval for new pools PollingInterval time.Duration `yaml:"polling_interval"` // DEX factory addresses to monitor FactoryAddresses []string `yaml:"factory_addresses"` // Minimum liquidity threshold for pools MinLiquidityWei int64 `yaml:"min_liquidity_wei"` // Cache configuration CacheSize int `yaml:"cache_size"` CacheTTL time.Duration `yaml:"cache_ttl"` }