main.rs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. use std::fs;
  2. use std::path::PathBuf;
  3. use std::str::FromStr;
  4. use std::sync::Arc;
  5. use anyhow::{bail, Result};
  6. use bip39::Mnemonic;
  7. use cdk::cdk_database;
  8. use cdk::cdk_database::WalletDatabase;
  9. use cdk::wallet::client::HttpClient;
  10. use cdk::wallet::{MultiMintWallet, Wallet};
  11. use cdk_redb::WalletRedbDatabase;
  12. use cdk_sqlite::WalletSqliteDatabase;
  13. use clap::{Parser, Subcommand};
  14. use rand::Rng;
  15. use tracing::Level;
  16. use tracing_subscriber::EnvFilter;
  17. use url::Url;
  18. mod sub_commands;
  19. const DEFAULT_WORK_DIR: &str = ".cdk-cli";
  20. /// Simple CLI application to interact with cashu
  21. #[derive(Parser)]
  22. #[command(name = "cashu-tool")]
  23. #[command(author = "thesimplekid <tsk@thesimplekid.com>")]
  24. #[command(version = "0.1.0")]
  25. #[command(author, version, about, long_about = None)]
  26. struct Cli {
  27. /// Database engine to use (sqlite/redb)
  28. #[arg(short, long, default_value = "sqlite")]
  29. engine: String,
  30. /// Path to working dir
  31. #[arg(short, long)]
  32. work_dir: Option<PathBuf>,
  33. /// Logging level
  34. #[arg(short, long, default_value = "error")]
  35. log_level: Level,
  36. /// NWS Proxy
  37. #[arg(short, long)]
  38. proxy: Option<Url>,
  39. #[command(subcommand)]
  40. command: Commands,
  41. }
  42. #[derive(Subcommand)]
  43. enum Commands {
  44. /// Decode a token
  45. DecodeToken(sub_commands::decode_token::DecodeTokenSubCommand),
  46. /// Balance
  47. Balance,
  48. /// Pay bolt11 invoice
  49. Pay(sub_commands::melt::MeltSubCommand),
  50. /// Claim pending mint quotes that have been paid
  51. MintPending,
  52. /// Receive token
  53. Receive(sub_commands::receive::ReceiveSubCommand),
  54. /// Send
  55. Send(sub_commands::send::SendSubCommand),
  56. /// Check if wallet balance is spendable
  57. CheckSpendable,
  58. /// View mint info
  59. MintInfo(sub_commands::mint_info::MintInfoSubcommand),
  60. /// Mint proofs via bolt11
  61. Mint(sub_commands::mint::MintSubCommand),
  62. /// Burn Spent tokens
  63. Burn(sub_commands::burn::BurnSubCommand),
  64. /// Restore proofs from seed
  65. Restore(sub_commands::restore::RestoreSubCommand),
  66. /// Update Mint Url
  67. UpdateMintUrl(sub_commands::update_mint_url::UpdateMintUrlSubCommand),
  68. }
  69. #[tokio::main]
  70. async fn main() -> Result<()> {
  71. let args: Cli = Cli::parse();
  72. let default_filter = args.log_level;
  73. let sqlx_filter = "sqlx=warn";
  74. let env_filter = EnvFilter::new(format!("{},{}", default_filter, sqlx_filter));
  75. // Parse input
  76. tracing_subscriber::fmt().with_env_filter(env_filter).init();
  77. let work_dir = match &args.work_dir {
  78. Some(work_dir) => work_dir.clone(),
  79. None => {
  80. let home_dir = home::home_dir().unwrap();
  81. home_dir.join(DEFAULT_WORK_DIR)
  82. }
  83. };
  84. fs::create_dir_all(&work_dir)?;
  85. let localstore: Arc<dyn WalletDatabase<Err = cdk_database::Error> + Send + Sync> =
  86. match args.engine.as_str() {
  87. "sqlite" => {
  88. let sql_path = work_dir.join("cdk-cli.sqlite");
  89. let sql = WalletSqliteDatabase::new(&sql_path).await?;
  90. sql.migrate().await;
  91. Arc::new(sql)
  92. }
  93. "redb" => {
  94. let redb_path = work_dir.join("cdk-cli.redb");
  95. Arc::new(WalletRedbDatabase::new(&redb_path)?)
  96. }
  97. _ => bail!("Unknown DB engine"),
  98. };
  99. let seed_path = work_dir.join("seed");
  100. let mnemonic = match fs::metadata(seed_path.clone()) {
  101. Ok(_) => {
  102. let contents = fs::read_to_string(seed_path.clone())?;
  103. Mnemonic::from_str(&contents)?
  104. }
  105. Err(_e) => {
  106. let mut rng = rand::thread_rng();
  107. let random_bytes: [u8; 32] = rng.gen();
  108. let mnemnic = Mnemonic::from_entropy(&random_bytes)?;
  109. tracing::info!("Using randomly generated seed you will not be able to restore");
  110. mnemnic
  111. }
  112. };
  113. let mut wallets: Vec<Wallet> = Vec::new();
  114. let mints = localstore.get_mints().await?;
  115. for (mint, _) in mints {
  116. let mut wallet = Wallet::new(
  117. &mint.to_string(),
  118. cdk::nuts::CurrencyUnit::Sat,
  119. localstore.clone(),
  120. &mnemonic.to_seed_normalized(""),
  121. None,
  122. )?;
  123. if let Some(proxy_url) = args.proxy.as_ref() {
  124. wallet.set_client(HttpClient::with_proxy(proxy_url.clone(), None, true)?);
  125. }
  126. wallets.push(wallet);
  127. }
  128. let multi_mint_wallet = MultiMintWallet::new(wallets);
  129. match &args.command {
  130. Commands::DecodeToken(sub_command_args) => {
  131. sub_commands::decode_token::decode_token(sub_command_args)
  132. }
  133. Commands::Balance => sub_commands::balance::balance(&multi_mint_wallet).await,
  134. Commands::Pay(sub_command_args) => {
  135. sub_commands::melt::pay(&multi_mint_wallet, sub_command_args).await
  136. }
  137. Commands::Receive(sub_command_args) => {
  138. sub_commands::receive::receive(
  139. &multi_mint_wallet,
  140. localstore,
  141. &mnemonic.to_seed_normalized(""),
  142. sub_command_args,
  143. )
  144. .await
  145. }
  146. Commands::Send(sub_command_args) => {
  147. sub_commands::send::send(&multi_mint_wallet, sub_command_args).await
  148. }
  149. Commands::CheckSpendable => {
  150. sub_commands::check_spent::check_spent(&multi_mint_wallet).await
  151. }
  152. Commands::MintInfo(sub_command_args) => {
  153. sub_commands::mint_info::mint_info(args.proxy, sub_command_args).await
  154. }
  155. Commands::Mint(sub_command_args) => {
  156. sub_commands::mint::mint(
  157. &multi_mint_wallet,
  158. &mnemonic.to_seed_normalized(""),
  159. localstore,
  160. sub_command_args,
  161. )
  162. .await
  163. }
  164. Commands::MintPending => {
  165. sub_commands::pending_mints::mint_pending(&multi_mint_wallet).await
  166. }
  167. Commands::Burn(sub_command_args) => {
  168. sub_commands::burn::burn(&multi_mint_wallet, sub_command_args).await
  169. }
  170. Commands::Restore(sub_command_args) => {
  171. sub_commands::restore::restore(&multi_mint_wallet, sub_command_args).await
  172. }
  173. Commands::UpdateMintUrl(sub_command_args) => {
  174. sub_commands::update_mint_url::update_mint_url(&multi_mint_wallet, sub_command_args)
  175. .await
  176. }
  177. }
  178. }