mod.rs 5.5 KB

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