receive.rs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. use std::collections::HashSet;
  2. use std::path::Path;
  3. use std::str::FromStr;
  4. use std::time::Duration;
  5. use anyhow::{anyhow, Result};
  6. use cdk::mint_url::MintUrl;
  7. use cdk::nuts::{SecretKey, Token};
  8. use cdk::util::unix_time;
  9. use cdk::wallet::multi_mint_wallet::MultiMintWallet;
  10. use cdk::wallet::{MultiMintReceiveOptions, ReceiveOptions};
  11. use cdk::Amount;
  12. use clap::Args;
  13. use nostr_sdk::nips::nip04;
  14. use nostr_sdk::{Filter, Keys, Kind, Timestamp};
  15. use crate::nostr_storage;
  16. use crate::utils::get_or_create_wallet;
  17. #[derive(Args)]
  18. pub struct ReceiveSubCommand {
  19. /// Cashu Token
  20. token: Option<String>,
  21. /// Signing Key
  22. #[arg(short, long, action = clap::ArgAction::Append)]
  23. signing_key: Vec<String>,
  24. /// Nostr key
  25. #[arg(short, long)]
  26. nostr_key: Option<String>,
  27. /// Nostr relay
  28. #[arg(short, long, action = clap::ArgAction::Append)]
  29. relay: Vec<String>,
  30. /// Unix time to query nostr from
  31. #[arg(long)]
  32. since: Option<u64>,
  33. /// Preimage
  34. #[arg(short, long, action = clap::ArgAction::Append)]
  35. preimage: Vec<String>,
  36. /// Allow receiving from untrusted mints (mints not already in the wallet)
  37. #[arg(long, default_value = "false")]
  38. allow_untrusted: bool,
  39. /// Transfer tokens from untrusted mints to this mint
  40. #[arg(long, value_name = "MINT_URL")]
  41. transfer_to: Option<String>,
  42. }
  43. pub async fn receive(
  44. multi_mint_wallet: &MultiMintWallet,
  45. sub_command_args: &ReceiveSubCommand,
  46. work_dir: &Path,
  47. ) -> Result<()> {
  48. let mut signing_keys = Vec::new();
  49. if !sub_command_args.signing_key.is_empty() {
  50. let mut s_keys: Vec<SecretKey> = sub_command_args
  51. .signing_key
  52. .iter()
  53. .map(|s| {
  54. if s.starts_with("nsec") {
  55. let nostr_key = nostr_sdk::SecretKey::from_str(s).expect("Invalid secret key");
  56. SecretKey::from_str(&nostr_key.to_secret_hex())
  57. } else {
  58. SecretKey::from_str(s)
  59. }
  60. })
  61. .collect::<Result<Vec<SecretKey>, _>>()?;
  62. signing_keys.append(&mut s_keys);
  63. }
  64. let amount = match &sub_command_args.token {
  65. Some(token_str) => {
  66. receive_token(
  67. multi_mint_wallet,
  68. token_str,
  69. &signing_keys,
  70. &sub_command_args.preimage,
  71. sub_command_args.allow_untrusted,
  72. sub_command_args.transfer_to.as_deref(),
  73. )
  74. .await?
  75. }
  76. None => {
  77. //wallet.add_p2pk_signing_key(nostr_signing_key).await;
  78. let nostr_key = match sub_command_args.nostr_key.as_ref() {
  79. Some(nostr_key) => {
  80. let secret_key = nostr_sdk::SecretKey::from_str(nostr_key)?;
  81. let secret_key = SecretKey::from_str(&secret_key.to_secret_hex())?;
  82. Some(secret_key)
  83. }
  84. None => None,
  85. };
  86. let nostr_key =
  87. nostr_key.ok_or(anyhow!("Nostr key required if token is not provided"))?;
  88. signing_keys.push(nostr_key.clone());
  89. let relays = sub_command_args.relay.clone();
  90. let since =
  91. nostr_storage::get_nostr_last_checked(work_dir, &nostr_key.public_key()).await?;
  92. let tokens = nostr_receive(relays, nostr_key.clone(), since).await?;
  93. // Store the current time as last checked
  94. nostr_storage::store_nostr_last_checked(
  95. work_dir,
  96. &nostr_key.public_key(),
  97. unix_time() as u32,
  98. )
  99. .await?;
  100. let mut total_amount = Amount::ZERO;
  101. for token_str in &tokens {
  102. match receive_token(
  103. multi_mint_wallet,
  104. token_str,
  105. &signing_keys,
  106. &sub_command_args.preimage,
  107. sub_command_args.allow_untrusted,
  108. sub_command_args.transfer_to.as_deref(),
  109. )
  110. .await
  111. {
  112. Ok(amount) => {
  113. total_amount += amount;
  114. }
  115. Err(err) => {
  116. println!("{err}");
  117. }
  118. }
  119. }
  120. total_amount
  121. }
  122. };
  123. println!("Received: {amount}");
  124. Ok(())
  125. }
  126. async fn receive_token(
  127. multi_mint_wallet: &MultiMintWallet,
  128. token_str: &str,
  129. signing_keys: &[SecretKey],
  130. preimage: &[String],
  131. allow_untrusted: bool,
  132. transfer_to: Option<&str>,
  133. ) -> Result<Amount> {
  134. let token: Token = Token::from_str(token_str)?;
  135. let mint_url = token.mint_url()?;
  136. // Parse transfer_to mint URL if provided
  137. let transfer_to_mint = if let Some(mint_str) = transfer_to {
  138. Some(MintUrl::from_str(mint_str)?)
  139. } else {
  140. None
  141. };
  142. // Check if the mint is already trusted
  143. let is_trusted = multi_mint_wallet.get_wallet(&mint_url).await.is_some();
  144. // If mint is not trusted and we don't allow untrusted, add it first (old behavior)
  145. if !is_trusted && !allow_untrusted {
  146. get_or_create_wallet(multi_mint_wallet, &mint_url).await?;
  147. }
  148. // Create multi-mint receive options
  149. let multi_mint_options = MultiMintReceiveOptions::default()
  150. .allow_untrusted(allow_untrusted)
  151. .transfer_to_mint(transfer_to_mint)
  152. .receive_options(ReceiveOptions {
  153. p2pk_signing_keys: signing_keys.to_vec(),
  154. preimages: preimage.to_vec(),
  155. ..Default::default()
  156. });
  157. let amount = multi_mint_wallet
  158. .receive(token_str, multi_mint_options)
  159. .await?;
  160. Ok(amount)
  161. }
  162. /// Receive tokens sent to nostr pubkey via dm
  163. async fn nostr_receive(
  164. relays: Vec<String>,
  165. nostr_signing_key: SecretKey,
  166. since: Option<u32>,
  167. ) -> Result<HashSet<String>> {
  168. let verifying_key = nostr_signing_key.public_key();
  169. let x_only_pubkey = verifying_key.x_only_public_key();
  170. let nostr_pubkey = nostr_sdk::PublicKey::from_hex(&x_only_pubkey.to_string())?;
  171. let since = since.map(|s| Timestamp::from(s as u64));
  172. let filter = match since {
  173. Some(since) => Filter::new()
  174. .pubkey(nostr_pubkey)
  175. .kind(Kind::EncryptedDirectMessage)
  176. .since(since),
  177. None => Filter::new()
  178. .pubkey(nostr_pubkey)
  179. .kind(Kind::EncryptedDirectMessage),
  180. };
  181. let client = nostr_sdk::Client::default();
  182. client.connect().await;
  183. let events = client
  184. .fetch_events_from(relays, filter, Duration::from_secs(30))
  185. .await?;
  186. let mut tokens: HashSet<String> = HashSet::new();
  187. let keys = Keys::from_str(&(nostr_signing_key).to_secret_hex())?;
  188. for event in events {
  189. if event.kind == Kind::EncryptedDirectMessage {
  190. if let Ok(msg) = nip04::decrypt(keys.secret_key(), &event.pubkey, event.content) {
  191. if let Some(token) = cdk::wallet::util::token_from_text(&msg) {
  192. tokens.insert(token.to_string());
  193. }
  194. } else {
  195. tracing::error!("Impossible to decrypt direct message");
  196. }
  197. }
  198. }
  199. Ok(tokens)
  200. }