mevlog 0.9.2

EVM transactions monitoring and querying CLI/TUI powered by Revm
Documentation
mod cmd;
use clap::{Parser, Subcommand, ValueEnum};
#[cfg(feature = "mcp")]
use cmd::mcp::McpArgs;
#[cfg(feature = "seed-db")]
use cmd::seed_db::SeedDBArgs;
#[cfg(feature = "tui")]
use cmd::tui::TuiArgs;
use cmd::{
    chain_info::ChainInfoArgs, chains::ChainsArgs, debug_available::DebugAvailableArgs,
    search::SearchArgs, tx::TxArgs, update_db::UpdateDBArgs,
};
use eyre::Result;
use mevlog::misc::shared_init::OutputFormat;

#[derive(Clone, Debug, ValueEnum)]
pub enum ColorMode {
    Always,
    Auto,
    Never,
}

#[derive(Parser, Debug)]
#[command(
    version,
    about,
    long_about = "mevlog: EVM activity log monitoring CLI

https://github.com/pawurb/mevlog-rs"
)]
pub struct MLArgs {
    #[command(subcommand)]
    pub cmd: MLSubcommand,

    #[arg(long, value_enum, default_value = "auto", global = true)]
    pub color: ColorMode,

    #[arg(
        long,
        help = "Output format ('json', 'json-pretty')",
        default_value = "json-pretty",
        global = true
    )]
    pub format: OutputFormat,
}

#[derive(Subcommand, Debug)]
pub enum MLSubcommand {
    #[command(about = "Find txs matching filter conditions", alias = "s")]
    Search(Box<SearchArgs>),
    #[command(about = "Print transaction info", alias = "t")]
    Tx(TxArgs),
    #[command(about = "Update signatures database")]
    UpdateDB(UpdateDBArgs),
    #[command(about = "List all available chains from ChainList")]
    Chains(ChainsArgs),
    #[command(about = "Show detailed chain information")]
    ChainInfo(ChainInfoArgs),
    #[command(about = "Check if RPC supports debug tracing")]
    DebugAvailable(DebugAvailableArgs),
    #[cfg(feature = "mcp")]
    #[command(about = "Start MCP server")]
    Mcp(McpArgs),
    #[cfg(feature = "seed-db")]
    #[command(about = "[Dev] Seed signatures database from source file")]
    SeedDB(SeedDBArgs),
    #[cfg(feature = "tui")]
    #[command(about = "Run TUI")]
    Tui(TuiArgs),
}

#[tokio::main]
#[hotpath::main(percentiles = [95], limit = 20)]
async fn main() {
    let root_args = MLArgs::parse();
    hotpath::tokio_runtime!();

    #[cfg(feature = "tui")]
    mevlog::misc::utils::init_file_logs();
    #[cfg(not(feature = "tui"))]
    mevlog::misc::utils::init_std_logs();

    let format = root_args.format.clone();

    match execute(root_args).await {
        Ok(_) => {}
        Err(e) => {
            print_error(&e, &format);
            std::process::exit(1);
        }
    }
}

fn print_error(e: &eyre::Error, format: &OutputFormat) {
    let error_json = if std::env::var("RUST_BACKTRACE").is_ok() {
        serde_json::json!({
            "error": e.to_string(),
            "backtrace": format!("{e:#?}")
        })
    } else {
        serde_json::json!({
            "error": e.to_string()
        })
    };

    match format {
        OutputFormat::Json => {
            eprintln!("{}", serde_json::to_string(&error_json).unwrap());
        }
        OutputFormat::JsonPretty => {
            eprintln!("{}", serde_json::to_string_pretty(&error_json).unwrap());
        }
    }
}

type ML = MLSubcommand;

async fn execute(root_args: MLArgs) -> Result<()> {
    match root_args.color {
        ColorMode::Always => colored::control::set_override(true),
        ColorMode::Never => colored::control::set_override(false),
        ColorMode::Auto => {}
    }

    match root_args.cmd {
        ML::Tx(args) => {
            args.run(root_args.format).await?;
        }
        ML::Search(args) => {
            args.run(root_args.format).await?;
        }
        ML::UpdateDB(args) => {
            args.run().await?;
        }
        ML::Chains(args) => {
            args.run(root_args.format).await?;
        }
        ML::ChainInfo(args) => {
            args.run(root_args.format).await?;
        }
        ML::DebugAvailable(args) => {
            args.run().await?;
        }
        #[cfg(feature = "mcp")]
        ML::Mcp(args) => {
            args.run().await?;
        }
        #[cfg(feature = "seed-db")]
        ML::SeedDB(args) => {
            args.run().await?;
        }
        #[cfg(feature = "tui")]
        ML::Tui(args) => {
            args.run().await?;
        }
    }

    Ok(())
}

#[cfg(all(test, feature = "mcp"))]
mod tests {
    use super::{MLArgs, MLSubcommand};
    use clap::Parser;

    #[test]
    fn search_subcommand_accepts_conn_flags_after_subcommand_name() {
        let parsed = MLArgs::try_parse_from([
            "mevlog",
            "--format",
            "json",
            "search",
            "-b",
            "10:latest",
            "--rpc-url",
            "http://localhost:8545",
            "--chain-id",
            "1",
        ])
        .expect("search args should parse");

        match parsed.cmd {
            MLSubcommand::Search(_) => {}
            other => panic!("expected search command, got {other:?}"),
        }
    }
}