| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- use std::io::{self, Write};
- use anyhow::{anyhow, Result};
- use cdk::nuts::nut18::TransportType;
- use cdk::nuts::{PaymentRequest, PaymentRequestPayload, Token};
- use cdk::wallet::{MultiMintWallet, SendOptions};
- use clap::Args;
- use nostr_sdk::nips::nip19::Nip19Profile;
- use nostr_sdk::{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();
- let transports = payment_request
- .transports
- .clone()
- .ok_or(anyhow!("Cannot pay request without transport"))?;
- // We prefer nostr transport if it is available to hide ip.
- let transport = transports
- .iter()
- .find(|t| t._type == TransportType::Nostr)
- .or_else(|| {
- transports
- .iter()
- .find(|t| t._type == TransportType::HttpPost)
- });
- let prepared_send = matching_wallet
- .prepare_send(
- amount,
- SendOptions {
- include_fee: true,
- ..Default::default()
- },
- )
- .await?;
- let token = matching_wallet.send(prepared_send, None).await?;
- // We need the keysets information to properly convert from token proof to proof
- let keysets_info = match matching_wallet
- .localstore
- .get_mint_keysets(token.mint_url()?)
- .await?
- {
- Some(keysets_info) => keysets_info,
- None => matching_wallet.load_mint_keysets().await?, // Hit the keysets endpoint if we don't have the keysets for this Mint
- };
- let proofs = token.proofs(&keysets_info)?;
- if let Some(transport) = transport {
- let payload = PaymentRequestPayload {
- id: payment_request.payment_id.clone(),
- memo: None,
- mint: matching_wallet.mint_url.clone(),
- unit: matching_wallet.unit.clone(),
- 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)?,
- )
- .build(nprofile.public_key);
- 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::<Vec<_>>()
- .join(", ")
- );
- if !gift_wrap.failed.is_empty() {
- println!(
- "Could not publish to {:?}",
- gift_wrap
- .failed
- .keys()
- .map(|relay| relay.to_string())
- .collect::<Vec<_>>()
- .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");
- }
- }
- }
- } else {
- // If no transport is available, print the token
- let token = Token::new(
- matching_wallet.mint_url.clone(),
- proofs,
- None,
- matching_wallet.unit.clone(),
- );
- println!("Token: {token}");
- }
- Ok(())
- }
|