main.rs 8.0 KB

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