Files
cdk/crates/cdk-cli/src/sub_commands/pay_request.rs
thesimplekid d2e9f1a626 Merge pull request #901 from thesimplekid/refresh_keys
feat: refactor wallet keyset management for better clarity
2025-07-23 20:49:42 +01:00

197 lines
5.8 KiB
Rust

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(())
}