receive.rs 6.7 KB

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