use std::io::{self, Write}; use anyhow::{anyhow, Result}; use cdk::{ amount::SplitTarget, nuts::{nut18::TransportType, PaymentRequest, PaymentRequestPayload}, wallet::{MultiMintWallet, SendKind}, }; use clap::Args; use nostr_sdk::{nips::nip19::Nip19Profile, Client as NostrClient, EventBuilder, FromBech32, Keys}; use reqwest::Client; #[derive(Args)] pub struct PayRequestSubCommand { payment_request: PaymentRequest, } pub async fn pay_request( multi_mint_wallet: &MultiMintWallet, sub_command_args: &PayRequestSubCommand, ) -> Result<()> { let payment_request = &sub_command_args.payment_request; let unit = payment_request.unit; let amount = match payment_request.amount { Some(amount) => amount, None => { println!("Enter the amount you would like to pay"); let mut user_input = String::new(); let stdin = io::stdin(); io::stdout().flush().unwrap(); stdin.read_line(&mut user_input)?; let amount: u64 = user_input.trim().parse()?; amount.into() } }; let request_mints = &payment_request.mints; let wallet_mints = multi_mint_wallet.get_wallets().await; // Wallets where unit, balance and mint match request let mut matching_wallets = vec![]; for wallet in wallet_mints.iter() { let balance = wallet.total_balance().await?; if let Some(request_mints) = request_mints { if !request_mints.contains(&wallet.mint_url) { continue; } } if let Some(unit) = unit { if wallet.unit != unit { continue; } } if balance >= amount { matching_wallets.push(wallet); } } let matching_wallet = matching_wallets.first().unwrap(); // We prefer nostr transport if it is available to hide ip. let transport = payment_request .transports .iter() .find(|t| t._type == TransportType::Nostr) .or_else(|| { payment_request .transports .iter() .find(|t| t._type == TransportType::HttpPost) }) .ok_or(anyhow!("No supported transport method found"))?; let proofs = matching_wallet .send( amount, None, None, &SplitTarget::default(), &SendKind::default(), true, ) .await? .proofs() .get(&matching_wallet.mint_url) .unwrap() .clone(); let payload = PaymentRequestPayload { id: payment_request.payment_id.clone(), memo: None, mint: matching_wallet.mint_url.clone(), unit: matching_wallet.unit, proofs, }; match transport._type { TransportType::Nostr => { let keys = Keys::generate(); let client = NostrClient::new(keys); let nprofile = Nip19Profile::from_bech32(&transport.target)?; println!("{:?}", nprofile.relays); let rumor = EventBuilder::new( nostr_sdk::Kind::from_u16(14), serde_json::to_string(&payload)?, [], ); let relays = nprofile.relays; for relay in relays.iter() { client.add_write_relay(relay).await?; } client.connect().await; let gift_wrap = client .gift_wrap_to(relays, &nprofile.public_key, rumor, None) .await?; println!( "Published event {} succufully to {}", gift_wrap.val, gift_wrap .success .iter() .map(|s| s.to_string()) .collect::>() .join(", ") ); if !gift_wrap.failed.is_empty() { println!( "Could not publish to {:?}", gift_wrap .failed .keys() .map(|relay| relay.to_string()) .collect::>() .join(", ") ); } } TransportType::HttpPost => { let client = Client::new(); let res = client .post(transport.target.clone()) .json(&payload) .send() .await?; let status = res.status(); if status.is_success() { println!("Successfully posted payment"); } else { println!("{:?}", res); println!("Error posting payment"); } } } Ok(()) }