mint_rpc_cli.rs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. //! Mint RPC CLI
  2. use std::path::PathBuf;
  3. use anyhow::{anyhow, Result};
  4. use cdk_common::grpc::VERSION_HEADER;
  5. use cdk_mint_rpc::cdk_mint_client::CdkMintClient;
  6. use cdk_mint_rpc::mint_rpc_cli::subcommands;
  7. use cdk_mint_rpc::GetInfoRequest;
  8. use clap::{Parser, Subcommand};
  9. use tonic::metadata::MetadataValue;
  10. use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
  11. use tonic::Request;
  12. /// Helper function to add version header to a request
  13. fn with_version_header<T>(mut request: Request<T>) -> Request<T> {
  14. request.metadata_mut().insert(
  15. VERSION_HEADER,
  16. MetadataValue::from_static(cdk_common::MINT_RPC_PROTOCOL_VERSION),
  17. );
  18. request
  19. }
  20. use tracing_subscriber::EnvFilter;
  21. /// Common CLI arguments for CDK binaries
  22. #[derive(Parser, Debug)]
  23. pub struct CommonArgs {
  24. /// Enable logging (default is false)
  25. #[arg(long, default_value_t = false)]
  26. pub enable_logging: bool,
  27. /// Logging level when enabled (default is debug)
  28. #[arg(long, default_value = "debug")]
  29. pub log_level: tracing::Level,
  30. }
  31. /// Initialize logging based on CLI arguments
  32. pub fn init_logging(enable_logging: bool, log_level: tracing::Level) {
  33. if enable_logging {
  34. let default_filter = log_level.to_string();
  35. // Common filters to reduce noise
  36. let sqlx_filter = "sqlx=warn";
  37. let hyper_filter = "hyper=warn";
  38. let h2_filter = "h2=warn";
  39. let rustls_filter = "rustls=warn";
  40. let reqwest_filter = "reqwest=warn";
  41. let env_filter = EnvFilter::new(format!(
  42. "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{rustls_filter},{reqwest_filter}"
  43. ));
  44. // Ok if successful, Err if already initialized
  45. let _ = tracing_subscriber::fmt()
  46. .with_env_filter(env_filter)
  47. .with_ansi(false)
  48. .try_init();
  49. }
  50. }
  51. const DEFAULT_WORK_DIR: &str = ".cdk-mint-rpc-cli";
  52. #[derive(Parser)]
  53. #[command(version, about, long_about = None)]
  54. struct Cli {
  55. #[command(flatten)]
  56. common: CommonArgs,
  57. /// Address of RPC server
  58. #[arg(short, long, default_value = "https://127.0.0.1:8086")]
  59. addr: String,
  60. /// Path to working dir
  61. #[arg(short, long)]
  62. work_dir: Option<PathBuf>,
  63. #[command(subcommand)]
  64. command: Commands,
  65. }
  66. #[derive(Subcommand)]
  67. enum Commands {
  68. /// Get info
  69. GetInfo,
  70. /// Update motd
  71. UpdateMotd(subcommands::UpdateMotdCommand),
  72. /// Update short description
  73. UpdateShortDescription(subcommands::UpdateShortDescriptionCommand),
  74. /// Update long description
  75. UpdateLongDescription(subcommands::UpdateLongDescriptionCommand),
  76. /// Update name
  77. UpdateName(subcommands::UpdateNameCommand),
  78. /// Update icon url
  79. UpdateIconUrl(subcommands::UpdateIconUrlCommand),
  80. /// Add Url
  81. AddUrl(subcommands::AddUrlCommand),
  82. /// Remove Url
  83. RemoveUrl(subcommands::RemoveUrlCommand),
  84. /// Add contact
  85. AddContact(subcommands::AddContactCommand),
  86. /// Remove contact
  87. RemoveContact(subcommands::RemoveContactCommand),
  88. /// Update nut04
  89. UpdateNut04(subcommands::UpdateNut04Command),
  90. /// Update nut05
  91. UpdateNut05(subcommands::UpdateNut05Command),
  92. /// Update quote ttl
  93. UpdateQuoteTtl(subcommands::UpdateQuoteTtlCommand),
  94. /// Get quote ttl
  95. GetQuoteTtl,
  96. /// Update Nut04 quote
  97. UpdateNut04QuoteState(subcommands::UpdateNut04QuoteCommand),
  98. /// Rotate next keyset
  99. RotateNextKeyset(subcommands::RotateNextKeysetCommand),
  100. }
  101. #[tokio::main]
  102. async fn main() -> Result<()> {
  103. let args: Cli = Cli::parse();
  104. // Initialize logging based on CLI arguments
  105. init_logging(args.common.enable_logging, args.common.log_level);
  106. let cli = Cli::parse();
  107. let work_dir = match &args.work_dir {
  108. Some(work_dir) => work_dir.clone(),
  109. None => {
  110. let home_dir = home::home_dir().ok_or(anyhow!("Could not find home dir"))?;
  111. home_dir.join(DEFAULT_WORK_DIR)
  112. }
  113. };
  114. std::fs::create_dir_all(&work_dir)?;
  115. tracing::debug!("Using work dir: {}", work_dir.display());
  116. let channel = if work_dir.join("tls").is_dir() {
  117. if rustls::crypto::CryptoProvider::get_default().is_none() {
  118. let _ = rustls::crypto::ring::default_provider().install_default();
  119. }
  120. // TLS directory exists, configure TLS
  121. let server_root_ca_cert = std::fs::read_to_string(work_dir.join("tls/ca.pem"))?;
  122. let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert);
  123. let client_cert = std::fs::read_to_string(work_dir.join("tls/client.pem"))?;
  124. let client_key = std::fs::read_to_string(work_dir.join("tls/client.key"))?;
  125. let client_identity = Identity::from_pem(client_cert, client_key);
  126. let tls = ClientTlsConfig::new()
  127. .ca_certificate(server_root_ca_cert)
  128. .identity(client_identity);
  129. Channel::from_shared(cli.addr.to_string())?
  130. .tls_config(tls)?
  131. .connect()
  132. .await?
  133. } else {
  134. // No TLS directory, skip TLS configuration
  135. Channel::from_shared(cli.addr.to_string())?
  136. .connect()
  137. .await?
  138. };
  139. // Create client
  140. let mut client = CdkMintClient::new(channel);
  141. match cli.command {
  142. Commands::GetInfo => {
  143. let response = client
  144. .get_info(with_version_header(Request::new(GetInfoRequest {})))
  145. .await?;
  146. let info = response.into_inner();
  147. println!(
  148. "name: {}",
  149. info.name.unwrap_or("None".to_string())
  150. );
  151. println!(
  152. "version: {}",
  153. info.version.unwrap_or("None".to_string())
  154. );
  155. println!(
  156. "description: {}",
  157. info.description.unwrap_or("None".to_string())
  158. );
  159. println!(
  160. "long description: {}",
  161. info.long_description.unwrap_or("None".to_string())
  162. );
  163. println!("motd: {}", info.motd.unwrap_or("None".to_string()));
  164. println!("icon_url: {}", info.icon_url.unwrap_or("None".to_string()));
  165. for url in info.urls {
  166. println!("mint_url: {url}");
  167. }
  168. for contact in info.contact {
  169. println!("method: {}, info: {}", contact.method, contact.info);
  170. }
  171. println!("total issued: {} sat", info.total_issued);
  172. println!("total redeemed: {} sat", info.total_redeemed);
  173. }
  174. Commands::UpdateMotd(sub_command_args) => {
  175. subcommands::update_motd(&mut client, &sub_command_args).await?;
  176. }
  177. Commands::UpdateShortDescription(sub_command_args) => {
  178. subcommands::update_short_description(&mut client, &sub_command_args).await?;
  179. }
  180. Commands::UpdateLongDescription(sub_command_args) => {
  181. subcommands::update_long_description(&mut client, &sub_command_args).await?;
  182. }
  183. Commands::UpdateName(sub_command_args) => {
  184. subcommands::update_name(&mut client, &sub_command_args).await?;
  185. }
  186. Commands::UpdateIconUrl(sub_command_args) => {
  187. subcommands::update_icon_url(&mut client, &sub_command_args).await?;
  188. }
  189. Commands::AddUrl(sub_command_args) => {
  190. subcommands::add_url(&mut client, &sub_command_args).await?;
  191. }
  192. Commands::RemoveUrl(sub_command_args) => {
  193. subcommands::remove_url(&mut client, &sub_command_args).await?;
  194. }
  195. Commands::AddContact(sub_command_args) => {
  196. subcommands::add_contact(&mut client, &sub_command_args).await?;
  197. }
  198. Commands::RemoveContact(sub_command_args) => {
  199. subcommands::remove_contact(&mut client, &sub_command_args).await?;
  200. }
  201. Commands::UpdateNut04(sub_command_args) => {
  202. subcommands::update_nut04(&mut client, &sub_command_args).await?;
  203. }
  204. Commands::UpdateNut05(sub_command_args) => {
  205. subcommands::update_nut05(&mut client, &sub_command_args).await?;
  206. }
  207. Commands::GetQuoteTtl => {
  208. subcommands::get_quote_ttl(&mut client).await?;
  209. }
  210. Commands::UpdateQuoteTtl(sub_command_args) => {
  211. subcommands::update_quote_ttl(&mut client, &sub_command_args).await?;
  212. }
  213. Commands::UpdateNut04QuoteState(sub_command_args) => {
  214. subcommands::update_nut04_quote_state(&mut client, &sub_command_args).await?;
  215. }
  216. Commands::RotateNextKeyset(sub_command_args) => {
  217. subcommands::rotate_next_keyset(&mut client, &sub_command_args).await?;
  218. }
  219. }
  220. Ok(())
  221. }