x402-cli 1.1.1

A comprehensive developer CLI for project scaffolding, wallet management, and payment testing.
Documentation
pub mod deploy;
pub mod facilitator;
pub mod project;
pub mod test;
pub mod wallet;

use anyhow::Result;
use clap::Parser;
use colored::Colorize;

pub use facilitator::Facilitator;
pub use project::Project;
pub use wallet::Wallet;

#[derive(Parser)]
pub enum WalletCommands {
    #[command(name = "create")]
    Create {
        #[arg(short, long, default_value = "testnet")]
        network: String,
    },
    #[command(name = "import")]
    Import {
        #[arg(short, long)]
        private_key: String,
        #[arg(short, long, default_value = "testnet")]
        network: String,
    },
}

#[derive(Parser)]
pub enum FacilitatorCommands {
    #[command(name = "start")]
    Start {
        #[arg(short, long, default_value = "3001")]
        port: u16,
        #[arg(long)]
        wallet: Option<String>,
        #[arg(long)]
        private_key: Option<String>,
        #[arg(short, long, default_value = "testnet")]
        network: String,
    },
    #[command(name = "stop")]
    Stop,
}

#[derive(Parser)]
pub enum TestCommands {
    #[command(name = "payment")]
    Payment {
        #[arg(short, long)]
        api: String,
        #[arg(short, long, default_value = "1000")]
        amount: u64,
    },
}

pub async fn init(name: String, chain: String, framework: String) -> Result<()> {
    println!(
        "{}",
        format!("Initializing x402 project: {}", name.cyan()).bold()
    );

    let project = Project::new(name.clone(), chain, framework);

    println!("{}", "  Creating project structure...".dimmed());
    project.create_directories()?;

    println!("{}", "  Creating configuration files...".dimmed());
    project.create_config_files()?;

    println!("{}", "  Installing dependencies...".dimmed());
    project.install_dependencies()?;

    project.generate_readme()?;

    println!(
        "{}",
        format!("✓ Project initialized: {}", name.green()).bold()
    );

    println!(
        "{}",
        format!("  Project location: {}/", name.cyan()).dimmed()
    );

    Ok(())
}

pub async fn handle_wallet(command: WalletCommands) -> Result<()> {
    match command {
        WalletCommands::Create { network } => {
            println!("{}", "Creating wallet...".cyan());

            let wallet = Wallet::create(&network).await?;

            wallet.save_to_file()?;

            wallet.fund_from_faucet().await?;

            println!(
                "{}",
                format!("  Wallet Address: {}", wallet.address.cyan()).dimmed()
            );

            Ok(())
        }
        WalletCommands::Import { private_key, network } => {
            println!("{}", "Importing wallet...".cyan());

            let wallet = Wallet::import(&private_key, &network)?;

            wallet.save_to_file()?;

            println!(
                "{}",
                format!("  Wallet Address: {}", wallet.address.cyan()).dimmed()
            );

            Ok(())
        }
    }
}

pub async fn handle_facilitator(command: FacilitatorCommands) -> Result<()> {
    match command {
        FacilitatorCommands::Start { port, wallet, private_key, network } => {
            let wallet = if let Some(private_key) = private_key {
                Wallet::import(&private_key, &network)?
            } else if let Some(wallet_address) = wallet {
                Wallet::load_from_address(&wallet_address)?
            } else {
                Wallet::find_default()?
            };

            let _facilitator = Facilitator::start(port, wallet)?;

            println!("{}", "  Start facilitator in background...".dimmed());
            println!(
                "{}",
                "  Run `x402 facilitator stop` to stop".yellow().dimmed()
            );

            Ok(())
        }
        FacilitatorCommands::Stop => {
            Facilitator::stop()?;
            println!("{}", "✓ Facilitator stopped".green().bold());
            Ok(())
        }
    }
}

pub async fn handle_test(command: TestCommands) -> Result<()> {
    match command {
        TestCommands::Payment { api, amount } => {
            println!("{}", "Testing payment flow...".cyan());
            println!("{}", format!("  API URL: {}", api.cyan()).dimmed());
            println!("{}", format!("  Amount: {}", amount));

            test::test_payment_flow(&api, amount).await?;

            Ok(())
        }
    }
}

pub async fn deploy(provider: String) -> Result<()> {
    println!("{}", format!("Deploying to {}", provider.cyan()).bold());

    deploy::deploy(&provider).await?;

    Ok(())
}