mod.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. //! Signatory CLI main logic
  2. //!
  3. //! This logic is in this file to be excluded for wasm
  4. use std::collections::HashMap;
  5. use std::net::SocketAddr;
  6. use std::path::PathBuf;
  7. use std::str::FromStr;
  8. use std::sync::Arc;
  9. use std::{env, fs};
  10. use anyhow::{bail, Result};
  11. use bip39::rand::{thread_rng, Rng};
  12. use bip39::Mnemonic;
  13. use cdk_common::database::MintKeysDatabase;
  14. use cdk_common::CurrencyUnit;
  15. use cdk_signatory::{db_signatory, start_grpc_server};
  16. #[cfg(feature = "sqlite")]
  17. use cdk_sqlite::MintSqliteDatabase;
  18. use clap::Parser;
  19. use tracing::Level;
  20. use tracing_subscriber::EnvFilter;
  21. const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
  22. const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
  23. /// Simple CLI application to interact with cashu
  24. #[derive(Parser)]
  25. #[command(name = "cashu-signatory")]
  26. #[command(author = "thesimplekid <tsk@thesimplekid.com>")]
  27. #[command(version = "0.1.0")]
  28. #[command(author, version, about, long_about = None)]
  29. struct Cli {
  30. /// Database engine to use (sqlite/redb)
  31. #[arg(short, long, default_value = "sqlite")]
  32. engine: String,
  33. /// Database password for sqlcipher
  34. #[arg(long)]
  35. password: Option<String>,
  36. /// Path to working dir
  37. #[arg(short, long)]
  38. work_dir: Option<PathBuf>,
  39. /// Logging level
  40. #[arg(short, long, default_value = "debug")]
  41. log_level: Level,
  42. #[arg(long, default_value = "127.0.0.1")]
  43. listen_addr: String,
  44. #[arg(long, default_value = "15060")]
  45. listen_port: u32,
  46. #[arg(long, short)]
  47. certs: Option<String>,
  48. /// Supported units with the format of name,fee and max_order
  49. #[arg(long, short, default_value = "sat,0,32")]
  50. units: Vec<String>,
  51. }
  52. /// Main function for the signatory standalone binary
  53. pub async fn cli_main() -> Result<()> {
  54. let args: Cli = Cli::parse();
  55. let default_filter = args.log_level;
  56. let supported_units = args
  57. .units
  58. .into_iter()
  59. .map(|unit| {
  60. let mut parts = unit.split(",").collect::<Vec<_>>();
  61. parts.reverse();
  62. let unit: CurrencyUnit = parts.pop().unwrap_or_default().parse()?;
  63. let fee = parts
  64. .pop()
  65. .map(|x| x.parse())
  66. .transpose()?
  67. .unwrap_or_default();
  68. let max_order = parts.pop().map(|x| x.parse()).transpose()?.unwrap_or(32);
  69. Ok::<(_, (_, _)), anyhow::Error>((unit, (fee, max_order)))
  70. })
  71. .collect::<Result<HashMap<_, _>, _>>()?;
  72. let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn";
  73. let env_filter = EnvFilter::new(format!("{default_filter},{sqlx_filter}"));
  74. // Parse input
  75. tracing_subscriber::fmt().with_env_filter(env_filter).init();
  76. let work_dir = match &args.work_dir {
  77. Some(work_dir) => work_dir.clone(),
  78. None => {
  79. let home_dir = home::home_dir().unwrap();
  80. home_dir.join(DEFAULT_WORK_DIR)
  81. }
  82. };
  83. let certs = Some(
  84. args.certs
  85. .map(|x| x.into())
  86. .unwrap_or_else(|| work_dir.clone()),
  87. );
  88. fs::create_dir_all(&work_dir)?;
  89. let localstore: Arc<dyn MintKeysDatabase<Err = cdk_common::database::Error> + Send + Sync> =
  90. match args.engine.as_str() {
  91. "sqlite" => {
  92. #[cfg(feature = "sqlite")]
  93. {
  94. let sql_path = work_dir.join("cdk-cli.sqlite");
  95. #[cfg(not(feature = "sqlcipher"))]
  96. let db = MintSqliteDatabase::new(&sql_path).await?;
  97. #[cfg(feature = "sqlcipher")]
  98. let db = {
  99. match args.password {
  100. Some(pass) => MintSqliteDatabase::new((&sql_path, pass)).await?,
  101. None => bail!("Missing database password"),
  102. }
  103. };
  104. Arc::new(db)
  105. }
  106. #[cfg(not(feature = "sqlite"))]
  107. {
  108. bail!("sqlite feature not enabled");
  109. }
  110. }
  111. _ => bail!("Unknown DB engine"),
  112. };
  113. let seed_path = work_dir.join("seed");
  114. let mnemonic = if let Ok(mnemonic) = env::var(ENV_MNEMONIC) {
  115. Mnemonic::from_str(&mnemonic)?
  116. } else {
  117. match fs::metadata(seed_path.clone()) {
  118. Ok(_) => {
  119. let contents = fs::read_to_string(seed_path.clone())?;
  120. Mnemonic::from_str(&contents)?
  121. }
  122. Err(_e) => {
  123. let mut rng = thread_rng();
  124. let random_bytes: [u8; 32] = rng.gen();
  125. let mnemonic = Mnemonic::from_entropy(&random_bytes)?;
  126. tracing::info!("Creating new seed");
  127. fs::write(seed_path, mnemonic.to_string())?;
  128. mnemonic
  129. }
  130. }
  131. };
  132. let seed = mnemonic.to_seed_normalized("");
  133. let signatory =
  134. db_signatory::DbSignatory::new(localstore, &seed, supported_units, Default::default())
  135. .await?;
  136. let socket_addr = SocketAddr::from_str(&format!("{}:{}", args.listen_addr, args.listen_port))?;
  137. start_grpc_server(Arc::new(signatory), socket_addr, certs).await?;
  138. Ok(())
  139. }