pay_request.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. use std::io::{self, Write};
  2. use anyhow::{anyhow, Result};
  3. use cdk::nuts::nut18::TransportType;
  4. use cdk::nuts::{PaymentRequest, PaymentRequestPayload, Token};
  5. use cdk::wallet::{MultiMintWallet, SendOptions};
  6. use clap::Args;
  7. use nostr_sdk::nips::nip19::Nip19Profile;
  8. use nostr_sdk::{Client as NostrClient, EventBuilder, FromBech32, Keys};
  9. use reqwest::Client;
  10. #[derive(Args)]
  11. pub struct PayRequestSubCommand {
  12. payment_request: PaymentRequest,
  13. }
  14. pub async fn pay_request(
  15. multi_mint_wallet: &MultiMintWallet,
  16. sub_command_args: &PayRequestSubCommand,
  17. ) -> Result<()> {
  18. let payment_request = &sub_command_args.payment_request;
  19. let unit = &payment_request.unit;
  20. let amount = match payment_request.amount {
  21. Some(amount) => amount,
  22. None => {
  23. println!("Enter the amount you would like to pay");
  24. let mut user_input = String::new();
  25. let stdin = io::stdin();
  26. io::stdout().flush().unwrap();
  27. stdin.read_line(&mut user_input)?;
  28. let amount: u64 = user_input.trim().parse()?;
  29. amount.into()
  30. }
  31. };
  32. let request_mints = &payment_request.mints;
  33. let wallet_mints = multi_mint_wallet.get_wallets().await;
  34. // Wallets where unit, balance and mint match request
  35. let mut matching_wallets = vec![];
  36. for wallet in wallet_mints.iter() {
  37. let balance = wallet.total_balance().await?;
  38. if let Some(request_mints) = request_mints {
  39. if !request_mints.contains(&wallet.mint_url) {
  40. continue;
  41. }
  42. }
  43. if let Some(unit) = unit {
  44. if &wallet.unit != unit {
  45. continue;
  46. }
  47. }
  48. if balance >= amount {
  49. matching_wallets.push(wallet);
  50. }
  51. }
  52. let matching_wallet = matching_wallets.first().unwrap();
  53. let transports = payment_request
  54. .transports
  55. .clone()
  56. .ok_or(anyhow!("Cannot pay request without transport"))?;
  57. // We prefer nostr transport if it is available to hide ip.
  58. let transport = transports
  59. .iter()
  60. .find(|t| t._type == TransportType::Nostr)
  61. .or_else(|| {
  62. transports
  63. .iter()
  64. .find(|t| t._type == TransportType::HttpPost)
  65. });
  66. let prepared_send = matching_wallet
  67. .prepare_send(
  68. amount,
  69. SendOptions {
  70. include_fee: true,
  71. ..Default::default()
  72. },
  73. )
  74. .await?;
  75. let token = matching_wallet.send(prepared_send, None).await?;
  76. // We need the keysets information to properly convert from token proof to proof
  77. let keysets_info = match matching_wallet
  78. .localstore
  79. .get_mint_keysets(token.mint_url()?)
  80. .await?
  81. {
  82. Some(keysets_info) => keysets_info,
  83. None => matching_wallet.load_mint_keysets().await?, // Hit the keysets endpoint if we don't have the keysets for this Mint
  84. };
  85. let proofs = token.proofs(&keysets_info)?;
  86. if let Some(transport) = transport {
  87. let payload = PaymentRequestPayload {
  88. id: payment_request.payment_id.clone(),
  89. memo: None,
  90. mint: matching_wallet.mint_url.clone(),
  91. unit: matching_wallet.unit.clone(),
  92. proofs,
  93. };
  94. match transport._type {
  95. TransportType::Nostr => {
  96. let keys = Keys::generate();
  97. let client = NostrClient::new(keys);
  98. let nprofile = Nip19Profile::from_bech32(&transport.target)?;
  99. println!("{:?}", nprofile.relays);
  100. let rumor = EventBuilder::new(
  101. nostr_sdk::Kind::from_u16(14),
  102. serde_json::to_string(&payload)?,
  103. )
  104. .build(nprofile.public_key);
  105. let relays = nprofile.relays;
  106. for relay in relays.iter() {
  107. client.add_write_relay(relay).await?;
  108. }
  109. client.connect().await;
  110. let gift_wrap = client
  111. .gift_wrap_to(relays, &nprofile.public_key, rumor, None)
  112. .await?;
  113. println!(
  114. "Published event {} succufully to {}",
  115. gift_wrap.val,
  116. gift_wrap
  117. .success
  118. .iter()
  119. .map(|s| s.to_string())
  120. .collect::<Vec<_>>()
  121. .join(", ")
  122. );
  123. if !gift_wrap.failed.is_empty() {
  124. println!(
  125. "Could not publish to {:?}",
  126. gift_wrap
  127. .failed
  128. .keys()
  129. .map(|relay| relay.to_string())
  130. .collect::<Vec<_>>()
  131. .join(", ")
  132. );
  133. }
  134. }
  135. TransportType::HttpPost => {
  136. let client = Client::new();
  137. let res = client
  138. .post(transport.target.clone())
  139. .json(&payload)
  140. .send()
  141. .await?;
  142. let status = res.status();
  143. if status.is_success() {
  144. println!("Successfully posted payment");
  145. } else {
  146. println!("{res:?}");
  147. println!("Error posting payment");
  148. }
  149. }
  150. }
  151. } else {
  152. // If no transport is available, print the token
  153. let token = Token::new(
  154. matching_wallet.mint_url.clone(),
  155. proofs,
  156. None,
  157. matching_wallet.unit.clone(),
  158. );
  159. println!("Token: {token}");
  160. }
  161. Ok(())
  162. }