use alloy::signers::local::PrivateKeySigner;
use clap::Parser;
use std::collections::HashSet;
use alloy::primitives::Address;
use oxidized_builder::common::error::AppError;
use oxidized_builder::common::logger::setup_logging;
use oxidized_builder::config::GlobalSettings;
use oxidized_builder::core::engine::Engine;
use oxidized_builder::core::nonce::NonceManager;
use oxidized_builder::core::portfolio::PortfolioManager;
use oxidized_builder::core::safety::SafetyGuard;
use oxidized_builder::core::simulation::Simulator;
use oxidized_builder::data::db::Database;
use oxidized_builder::network::gas::GasOracle;
use oxidized_builder::network::price_feed::PriceFeed;
use oxidized_builder::network::provider::ConnectionFactory;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Parser, Debug)]
#[command(author, version, about = "Oxidized Builder")]
struct Cli {
#[arg(long)]
config: Option<String>,
#[arg(long, default_value_t = false)]
dry_run: bool,
#[arg(long)]
metrics_port: Option<u16>,
#[arg(long, default_value_t = true)]
strategy_enabled: bool,
#[arg(long)]
slippage_bps: Option<u64>,
}
#[tokio::main]
async fn main() -> Result<(), AppError> {
let cli = Cli::parse();
let settings = GlobalSettings::load_with_path(cli.config.as_deref())?;
setup_logging(if settings.debug { "debug" } else { "info" }, false);
let database_url =
std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://on1builder.db".to_string());
let db = Database::new(&database_url).await?;
let chain_id = *settings.chains.get(0).unwrap_or(&1);
let rpc_url = settings.get_rpc_url(chain_id)?;
let ws_url = settings.get_ws_url(chain_id)?;
let http_provider = ConnectionFactory::http(&rpc_url)?;
let ws_provider = ConnectionFactory::ws(&ws_url).await?;
let wallet_address = settings.wallet_address;
let portfolio = Arc::new(PortfolioManager::new(http_provider.clone(), wallet_address));
let nonce_manager = NonceManager::new(http_provider.clone(), wallet_address);
let safety_guard = Arc::new(SafetyGuard::new());
let gas_oracle = GasOracle::new(http_provider.clone());
let chainlink_feeds = settings.chainlink_feeds_for_chain(chain_id)?;
if chainlink_feeds.is_empty() {
tracing::warn!("No Chainlink feeds configured for chain {}", chain_id);
}
let wrapped_native = oxidized_builder::common::constants::wrapped_native_for_chain(chain_id);
let price_feed = PriceFeed::new(http_provider.clone(), chainlink_feeds);
let simulator = Simulator::new(http_provider.clone());
let relay_url = settings.flashbots_relay_url();
let bundle_signer = PrivateKeySigner::from_str(&settings.bundle_signer_key())
.map_err(|e| AppError::Config(format!("Invalid bundle signer key: {}", e)))?;
let metrics_port: u16 = cli
.metrics_port
.or_else(|| {
std::env::var("METRICS_PORT")
.ok()
.and_then(|s| s.parse().ok())
})
.unwrap_or(settings.metrics_port);
let slippage_bps = cli.slippage_bps.unwrap_or(settings.slippage_bps);
let strategy_enabled = cli.strategy_enabled && settings.strategy_enabled;
let router_allowlist: HashSet<Address> = settings
.routers_for_chain(chain_id)?
.values()
.copied()
.collect();
if router_allowlist.is_empty() {
tracing::warn!("Router allowlist is empty for chain {}", chain_id);
}
let engine = Engine::new(
http_provider,
ws_provider,
db,
nonce_manager,
portfolio,
safety_guard,
cli.dry_run,
gas_oracle,
price_feed,
chain_id,
relay_url,
bundle_signer,
settings
.gas_cap_for_chain(chain_id)
.unwrap_or(settings.max_gas_price_gwei),
simulator,
metrics_port,
strategy_enabled,
slippage_bps,
router_allowlist,
wrapped_native,
settings.mev_share_stream_url.clone(),
settings.mev_share_history_limit,
settings.mev_share_enabled,
);
engine.run().await
}