main.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #[cfg(not(target_arch = "wasm32"))]
  2. mod cli {
  3. use std::collections::HashMap;
  4. use std::net::SocketAddr;
  5. use std::path::PathBuf;
  6. use std::str::FromStr;
  7. use std::sync::Arc;
  8. use std::{env, fs};
  9. use anyhow::{bail, Result};
  10. use bip39::rand::{thread_rng, Rng};
  11. use bip39::Mnemonic;
  12. use cashu::CurrencyUnit;
  13. use cdk_common::database::{MintAuthDatabase, MintKeysDatabase};
  14. #[cfg(feature = "redb")]
  15. use cdk_redb::MintRedbDatabase;
  16. use cdk_signatory::{db_signatory, grpc_server};
  17. use cdk_sqlite::mint::MintSqliteAuthDatabase;
  18. use cdk_sqlite::MintSqliteDatabase;
  19. use clap::Parser;
  20. use tracing::Level;
  21. use tracing_subscriber::EnvFilter;
  22. const DEFAULT_WORK_DIR: &str = ".cdk-signatory";
  23. const ENV_MNEMONIC: &str = "CDK_MINTD_MNEMONIC";
  24. /// Simple CLI application to interact with cashu
  25. #[derive(Parser)]
  26. #[command(name = "cashu-signatory")]
  27. #[command(author = "thesimplekid <tsk@thesimplekid.com>")]
  28. #[command(version = "0.1.0")]
  29. #[command(author, version, about, long_about = None)]
  30. struct Cli {
  31. /// Database engine to use (sqlite/redb)
  32. #[arg(short, long, default_value = "sqlite")]
  33. engine: String,
  34. /// Database password for sqlcipher
  35. #[arg(long)]
  36. password: Option<String>,
  37. /// Path to working dir
  38. #[arg(short, long)]
  39. work_dir: Option<PathBuf>,
  40. /// Logging level
  41. #[arg(short, long, default_value = "error")]
  42. log_level: Level,
  43. #[arg(long, default_value = "127.0.0.1")]
  44. listen_addr: String,
  45. #[arg(long, default_value = "15060")]
  46. listen_port: u32,
  47. #[arg(long, short)]
  48. certs: Option<String>,
  49. /// Supported units with the format of name,fee and max_order
  50. #[arg(long, short, default_value = "sat,0,32")]
  51. units: Vec<String>,
  52. }
  53. pub async fn 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, auth_localstore): (
  90. Arc<dyn MintKeysDatabase<Err = cdk_common::database::Error> + Send + Sync>,
  91. Arc<dyn MintAuthDatabase<Err = cdk_common::database::Error> + Send + Sync>,
  92. ) = match args.engine.as_str() {
  93. "sqlite" => {
  94. let sql_path = work_dir.join("cdk-cli.sqlite");
  95. #[cfg(not(feature = "sqlcipher"))]
  96. let db = (
  97. MintSqliteDatabase::new(&sql_path).await?,
  98. MintSqliteAuthDatabase::new(&sql_path).await?,
  99. );
  100. #[cfg(feature = "sqlcipher")]
  101. let db = {
  102. match args.password {
  103. Some(pass) => (
  104. MintSqliteDatabase::new(&sql_path, pass).await?,
  105. MintSqliteAuthDatabase::new(&sql_path).await?,
  106. ),
  107. None => bail!("Missing database password"),
  108. }
  109. };
  110. (Arc::new(db.0), Arc::new(db.1))
  111. }
  112. "redb" => {
  113. #[cfg(feature = "redb")]
  114. {
  115. let redb_path = work_dir.join("cdk-cli.redb");
  116. let db = Arc::new(MintRedbDatabase::new(&redb_path)?);
  117. (db.clone(), db)
  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 = db_signatory::DbSignatory::new(
  147. localstore,
  148. Some(auth_localstore),
  149. &seed,
  150. supported_units,
  151. Default::default(),
  152. )
  153. .await?;
  154. let socket_addr =
  155. SocketAddr::from_str(&format!("{}:{}", args.listen_addr, args.listen_port))?;
  156. grpc_server(signatory, socket_addr, certs).await?;
  157. Ok(())
  158. }
  159. }
  160. fn main() {
  161. #[cfg(target_arch = "wasm32")]
  162. println!("Not supported in wasm32");
  163. #[cfg(not(target_arch = "wasm32"))]
  164. {
  165. use tokio::runtime::Runtime;
  166. let rt = Runtime::new().unwrap();
  167. rt.block_on(async {
  168. cli::main().await.unwrap();
  169. });
  170. }
  171. }