use std::path::PathBuf; use anyhow::{anyhow, Result}; use cdk_mint_rpc::cdk_mint_client::CdkMintClient; use cdk_mint_rpc::mint_rpc_cli::subcommands; use cdk_mint_rpc::GetInfoRequest; use clap::{Parser, Subcommand}; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; use tonic::Request; use tracing::Level; use tracing_subscriber::EnvFilter; const DEFAULT_WORK_DIR: &str = ".cdk-mint-rpc-cli"; #[derive(Parser)] #[command(version, about, long_about = None)] struct Cli { /// Address of RPC server #[arg(short, long, default_value = "https://127.0.0.1:8086")] addr: String, /// Logging level #[arg(short, long, default_value = "debug")] log_level: Level, /// Path to working dir #[arg(short, long)] work_dir: Option, #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Get info GetInfo, /// Update motd UpdateMotd(subcommands::UpdateMotdCommand), /// Update short description UpdateShortDescription(subcommands::UpdateShortDescriptionCommand), /// Update long description UpdateLongDescription(subcommands::UpdateLongDescriptionCommand), /// Update name UpdateName(subcommands::UpdateNameCommand), /// Update icon url UpdateIconUrl(subcommands::UpdateIconUrlCommand), /// Add Url AddUrl(subcommands::AddUrlCommand), /// Remove Url RemoveUrl(subcommands::RemoveUrlCommand), /// Add contact AddContact(subcommands::AddContactCommand), /// Remove contact RemoveContact(subcommands::RemoveContactCommand), /// Update nut04 UpdateNut04(subcommands::UpdateNut04Command), /// Update nut05 UpdateNut05(subcommands::UpdateNut05Command), /// Update quote ttl UpdateQuoteTtl(subcommands::UpdateQuoteTtlCommand), /// Get quote ttl GetQuoteTtl, /// Update Nut04 quote UpdateNut04QuoteState(subcommands::UpdateNut04QuoteCommand), /// Rotate next keyset RotateNextKeyset(subcommands::RotateNextKeysetCommand), } #[tokio::main] async fn main() -> Result<()> { let args: Cli = Cli::parse(); let default_filter = args.log_level; let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn"; let env_filter = EnvFilter::new(format!("{},{}", default_filter, sqlx_filter)); // Parse input tracing_subscriber::fmt().with_env_filter(env_filter).init(); let cli = Cli::parse(); let work_dir = match &args.work_dir { Some(work_dir) => work_dir.clone(), None => { let home_dir = home::home_dir().ok_or(anyhow!("Could not find home dir"))?; home_dir.join(DEFAULT_WORK_DIR) } }; std::fs::create_dir_all(&work_dir)?; tracing::debug!("Using work dir: {}", work_dir.display()); let channel = if work_dir.join("tls").is_dir() { // TLS directory exists, configure TLS let server_root_ca_cert = std::fs::read_to_string(work_dir.join("tls/ca.pem")).unwrap(); let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert); let client_cert = std::fs::read_to_string(work_dir.join("tls/client.pem"))?; let client_key = std::fs::read_to_string(work_dir.join("tls/client.key"))?; let client_identity = Identity::from_pem(client_cert, client_key); let tls = ClientTlsConfig::new() .ca_certificate(server_root_ca_cert) .identity(client_identity); Channel::from_shared(cli.addr.to_string())? .tls_config(tls)? .connect() .await? } else { // No TLS directory, skip TLS configuration Channel::from_shared(cli.addr.to_string())? .connect() .await? }; let mut client = CdkMintClient::new(channel); match cli.command { Commands::GetInfo => { let response = client.get_info(Request::new(GetInfoRequest {})).await?; let info = response.into_inner(); println!( "name: {}", info.name.unwrap_or("None".to_string()) ); println!( "version: {}", info.version.unwrap_or("None".to_string()) ); println!( "description: {}", info.description.unwrap_or("None".to_string()) ); println!( "long description: {}", info.long_description.unwrap_or("None".to_string()) ); println!("motd: {}", info.motd.unwrap_or("None".to_string())); println!("icon_url: {}", info.icon_url.unwrap_or("None".to_string())); for url in info.urls { println!("mint_url: {}", url); } for contact in info.contact { println!("method: {}, info: {}", contact.method, contact.info); } println!("total issued: {} sat", info.total_issued); println!("total redeemed: {} sat", info.total_redeemed); } Commands::UpdateMotd(sub_command_args) => { subcommands::update_motd(&mut client, &sub_command_args).await?; } Commands::UpdateShortDescription(sub_command_args) => { subcommands::update_short_description(&mut client, &sub_command_args).await?; } Commands::UpdateLongDescription(sub_command_args) => { subcommands::update_long_description(&mut client, &sub_command_args).await?; } Commands::UpdateName(sub_command_args) => { subcommands::update_name(&mut client, &sub_command_args).await?; } Commands::UpdateIconUrl(sub_command_args) => { subcommands::update_icon_url(&mut client, &sub_command_args).await?; } Commands::AddUrl(sub_command_args) => { subcommands::add_url(&mut client, &sub_command_args).await?; } Commands::RemoveUrl(sub_command_args) => { subcommands::remove_url(&mut client, &sub_command_args).await?; } Commands::AddContact(sub_command_args) => { subcommands::add_contact(&mut client, &sub_command_args).await?; } Commands::RemoveContact(sub_command_args) => { subcommands::remove_contact(&mut client, &sub_command_args).await?; } Commands::UpdateNut04(sub_command_args) => { subcommands::update_nut04(&mut client, &sub_command_args).await?; } Commands::UpdateNut05(sub_command_args) => { subcommands::update_nut05(&mut client, &sub_command_args).await?; } Commands::GetQuoteTtl => { subcommands::get_quote_ttl(&mut client).await?; } Commands::UpdateQuoteTtl(sub_command_args) => { subcommands::update_quote_ttl(&mut client, &sub_command_args).await?; } Commands::UpdateNut04QuoteState(sub_command_args) => { subcommands::update_nut04_quote_state(&mut client, &sub_command_args).await?; } Commands::RotateNextKeyset(sub_command_args) => { subcommands::rotate_next_keyset(&mut client, &sub_command_args).await?; } } Ok(()) }