use std::path::PathBuf;
use clap::Parser;
use fynd_rpc::config::defaults;
#[derive(Parser, PartialEq, Debug)]
#[command(name = "fynd", version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(clap::Subcommand, PartialEq, Debug)]
pub enum Commands {
Serve(Box<ServeArgs>),
Openapi,
}
#[derive(clap::Args, PartialEq, Debug)]
pub struct ServeArgs {
#[arg(short, long, default_value = "Ethereum")]
pub chain: String,
#[arg(long, default_value = defaults::HTTP_HOST, env)]
pub http_host: String,
#[arg(long, default_value_t = defaults::HTTP_PORT, env)]
pub http_port: u16,
#[arg(long, env)]
pub tycho_url: Option<String>,
#[arg(long, env)]
pub tycho_api_key: Option<String>,
#[arg(long)]
pub disable_tls: bool,
#[arg(long, env)]
pub rpc_url: Option<String>,
#[arg(short, long, value_delimiter = ',', value_name = "PROTO1,PROTO2")]
pub protocols: Vec<String>,
#[arg(long, default_value_t = defaults::MIN_TVL)]
pub min_tvl: f64,
#[arg(long, default_value_t = defaults::TVL_BUFFER_RATIO)]
pub tvl_buffer_ratio: f64,
#[arg(long, default_value_t = defaults::MIN_TOKEN_QUALITY)]
pub min_token_quality: i32,
#[arg(long, default_value_t = defaults::TRADED_N_DAYS_AGO)]
pub traded_n_days_ago: u64,
#[arg(long, default_value_t = defaults::GAS_REFRESH_INTERVAL.as_secs())]
pub gas_refresh_interval_secs: u64,
#[arg(long, default_value_t = defaults::RECONNECT_DELAY.as_secs())]
pub reconnect_delay_secs: u64,
#[arg(long, default_value_t = defaults::WORKER_ROUTER_TIMEOUT_MS)]
pub worker_router_timeout_ms: u64,
#[arg(long, default_value_t = defaults::ROUTER_MIN_RESPONSES)]
pub worker_router_min_responses: usize,
#[arg(short, long, env, default_value = "worker_pools.toml")]
pub worker_pools_config: PathBuf,
#[arg(long, env)]
pub blocklist_config: Option<PathBuf>,
#[arg(long)]
pub gas_price_stale_threshold_secs: Option<u64>,
#[arg(long)]
pub enable_price_guard: bool,
#[arg(long, default_value_t = 300)]
pub price_guard_lower_tolerance_bps: u32,
#[arg(long, default_value_t = 10_000)]
pub price_guard_upper_tolerance_bps: u32,
#[arg(long, default_value_t = false)]
pub price_guard_fail_on_provider_error: bool,
#[arg(long, default_value_t = false)]
pub price_guard_fail_on_token_price_not_found: bool,
}
#[cfg(test)]
mod cli_tests {
use super::*;
#[test]
fn test_arg_parsing() {
let cli = Cli::try_parse_from(vec![
"fynd",
"serve",
"--chain",
"Ethereum",
"--http-host",
"127.0.0.1",
"--http-port",
"8080",
"--tycho-api-key",
"test-key",
"--rpc-url",
"https://rpc.example.com",
"--tycho-url",
"wss://custom.tycho.url",
"--protocols",
"uniswap_v2,uniswap_v3",
"--min-tvl",
"20.0",
"--worker-pools-config",
"new_worker_pools.toml",
])
.expect("parse errored");
let Commands::Serve(args) = cli.command else {
panic!("expected Serve command");
};
assert_eq!(args.chain, "Ethereum");
assert_eq!(args.http_host, "127.0.0.1");
assert_eq!(args.http_port, 8080);
assert_eq!(args.tycho_api_key, Some("test-key".to_string()));
assert_eq!(args.rpc_url, Some("https://rpc.example.com".to_string()));
assert_eq!(args.tycho_url, Some("wss://custom.tycho.url".to_string()));
assert_eq!(args.protocols, vec!["uniswap_v2", "uniswap_v3"]);
assert_eq!(args.min_tvl, 20.0);
assert_eq!(args.worker_pools_config, PathBuf::from("new_worker_pools.toml"));
assert_eq!(args.blocklist_config, None);
}
#[test]
fn test_arg_parsing_defaults() {
std::env::remove_var("RPC_URL");
std::env::remove_var("TYCHO_API_KEY");
std::env::remove_var("TYCHO_URL");
std::env::remove_var("HTTP_HOST");
std::env::remove_var("HTTP_PORT");
let cli = Cli::try_parse_from(vec!["fynd", "serve"]).expect("parse errored");
let Commands::Serve(args) = cli.command else {
panic!("expected Serve command");
};
assert_eq!(args.chain, "Ethereum");
assert_eq!(args.http_host, "0.0.0.0");
assert_eq!(args.http_port, 3000);
assert_eq!(args.tycho_api_key, None);
assert_eq!(args.rpc_url, None);
assert_eq!(args.tycho_url, None);
assert!(args.protocols.is_empty());
assert_eq!(args.min_tvl, 10.0);
assert_eq!(args.tvl_buffer_ratio, 1.1);
assert_eq!(args.gas_refresh_interval_secs, 30);
assert_eq!(args.reconnect_delay_secs, 5);
assert_eq!(args.worker_router_timeout_ms, 100);
assert_eq!(args.worker_router_min_responses, 0);
assert_eq!(args.blocklist_config, None);
}
#[test]
fn test_arg_parsing_default_worker_pools() {
let cli = Cli::try_parse_from(vec!["fynd", "serve", "--tycho-api-key", "test-key"])
.expect("parse errored");
let Commands::Serve(args) = cli.command else {
panic!("expected Serve command");
};
assert_eq!(args.worker_pools_config, PathBuf::from("worker_pools.toml"));
}
#[test]
fn test_openapi_subcommand() {
let cli = Cli::try_parse_from(vec!["fynd", "openapi"]).expect("parse errored");
assert_eq!(cli.command, Commands::Openapi);
}
}