From de5f9111e1d24efd462bd0b4c30b7eb38cdbdf33 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Sat, 24 May 2025 07:17:06 -0400 Subject: [PATCH] feat: mint url flag (#765) --- crates/cdk-cli/src/sub_commands/melt.rs | 61 +++++++++++++++++++++---- crates/cdk-cli/src/sub_commands/send.rs | 31 ++++++++++--- crates/cdk-cli/src/utils.rs | 17 +++++++ 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/crates/cdk-cli/src/sub_commands/melt.rs b/crates/cdk-cli/src/sub_commands/melt.rs index c6e4a362..bd92dde9 100644 --- a/crates/cdk-cli/src/sub_commands/melt.rs +++ b/crates/cdk-cli/src/sub_commands/melt.rs @@ -10,7 +10,10 @@ use clap::Args; use tokio::task::JoinSet; use crate::sub_commands::balance::mint_balances; -use crate::utils::{get_number_input, get_user_input, get_wallet_by_index, validate_mint_number}; +use crate::utils::{ + get_number_input, get_user_input, get_wallet_by_index, get_wallet_by_mint_url, + validate_mint_number, +}; #[derive(Args)] pub struct MeltSubCommand { @@ -18,8 +21,11 @@ pub struct MeltSubCommand { #[arg(default_value = "sat")] unit: String, /// Mpp - #[arg(short, long)] + #[arg(short, long, conflicts_with = "mint_url")] mpp: bool, + /// Mint URL to use for melting + #[arg(long, conflicts_with = "mpp")] + mint_url: Option, } pub async fn pay( @@ -32,6 +38,31 @@ pub async fn pay( let mut mints = vec![]; let mut mint_amounts = vec![]; if sub_command_args.mpp { + // MPP functionality expects multiple mints, so mint_url flag doesn't fully apply here, + // but we can offer to use the specified mint as the first one if provided + if let Some(mint_url) = &sub_command_args.mint_url { + println!("Using mint URL {mint_url} as the first mint for MPP payment."); + + // Check if the mint exists + if let Ok(_wallet) = + get_wallet_by_mint_url(multi_mint_wallet, mint_url, unit.clone()).await + { + // Find the index of this mint in the mints_amounts list + if let Some(mint_index) = mints_amounts + .iter() + .position(|(url, _)| url.to_string() == *mint_url) + { + mints.push(mint_index); + let melt_amount: u64 = + get_number_input("Enter amount to mint from this mint in sats.")?; + mint_amounts.push(melt_amount); + } else { + println!("Warning: Mint URL exists but no balance found. Continuing with manual selection."); + } + } else { + println!("Warning: Could not find wallet for the specified mint URL. Continuing with manual selection."); + } + } loop { let mint_number: String = get_user_input("Enter mint number to melt from and -1 when done.")?; @@ -122,17 +153,29 @@ pub async fn pay( bail!("Could not complete all melts"); } } else { - let mint_number: usize = get_number_input("Enter mint number to melt from")?; - - let wallet = + // Get wallet either by mint URL or by index + let wallet = if let Some(mint_url) = &sub_command_args.mint_url { + // Use the provided mint URL + get_wallet_by_mint_url(multi_mint_wallet, mint_url, unit.clone()).await? + } else { + // Fallback to the index-based selection + let mint_number: usize = get_number_input("Enter mint number to melt from")?; get_wallet_by_index(multi_mint_wallet, &mints_amounts, mint_number, unit.clone()) - .await?; + .await? + }; + + // Find the mint amount for the selected wallet to check available funds + let mint_url = &wallet.mint_url; + let mint_amount = mints_amounts + .iter() + .find(|(url, _)| url == mint_url) + .map(|(_, amount)| *amount) + .ok_or_else(|| anyhow::anyhow!("Could not find balance for mint: {}", mint_url))?; + + let available_funds = >::into(mint_amount) * MSAT_IN_SAT; let bolt11 = Bolt11Invoice::from_str(&get_user_input("Enter bolt11 invoice request")?)?; - let available_funds = - >::into(mints_amounts[mint_number].1) * MSAT_IN_SAT; - // Determine payment amount and options let options = if bolt11.amount_milli_satoshis().is_none() { // Get user input for amount diff --git a/crates/cdk-cli/src/sub_commands/send.rs b/crates/cdk-cli/src/sub_commands/send.rs index ec8b33ad..59cd1078 100644 --- a/crates/cdk-cli/src/sub_commands/send.rs +++ b/crates/cdk-cli/src/sub_commands/send.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use anyhow::Result; +use anyhow::{anyhow, Result}; use cdk::nuts::{Conditions, CurrencyUnit, PublicKey, SpendingConditions}; use cdk::wallet::types::SendKind; use cdk::wallet::{MultiMintWallet, SendMemo, SendOptions}; @@ -8,7 +8,9 @@ use cdk::Amount; use clap::Args; use crate::sub_commands::balance::mint_balances; -use crate::utils::{check_sufficient_funds, get_number_input, get_wallet_by_index}; +use crate::utils::{ + check_sufficient_funds, get_number_input, get_wallet_by_index, get_wallet_by_mint_url, +}; #[derive(Args)] pub struct SendSubCommand { @@ -45,6 +47,9 @@ pub struct SendSubCommand { /// Amount willing to overpay to avoid a swap #[arg(short, long)] tolerance: Option, + /// Mint URL to use for sending + #[arg(long)] + mint_url: Option, /// Currency unit e.g. sat #[arg(default_value = "sat")] unit: String, @@ -57,13 +62,27 @@ pub async fn send( let unit = CurrencyUnit::from_str(&sub_command_args.unit)?; let mints_amounts = mint_balances(multi_mint_wallet, &unit).await?; - let mint_number: usize = get_number_input("Enter mint number to create token")?; - - let wallet = get_wallet_by_index(multi_mint_wallet, &mints_amounts, mint_number, unit).await?; + // Get wallet either by mint URL or by index + let wallet = if let Some(mint_url) = &sub_command_args.mint_url { + // Use the provided mint URL + get_wallet_by_mint_url(multi_mint_wallet, mint_url, unit).await? + } else { + // Fallback to the index-based selection + let mint_number: usize = get_number_input("Enter mint number to create token")?; + get_wallet_by_index(multi_mint_wallet, &mints_amounts, mint_number, unit).await? + }; let token_amount = Amount::from(get_number_input::("Enter value of token in sats")?); - check_sufficient_funds(mints_amounts[mint_number].1, token_amount)?; + // Find the mint amount for the selected wallet to check if we have sufficient funds + let mint_url = &wallet.mint_url; + let mint_amount = mints_amounts + .iter() + .find(|(url, _)| url == mint_url) + .map(|(_, amount)| *amount) + .ok_or_else(|| anyhow!("Could not find balance for mint: {}", mint_url))?; + + check_sufficient_funds(mint_amount, token_amount)?; let conditions = match (&sub_command_args.preimage, &sub_command_args.hash) { (Some(_), Some(_)) => { diff --git a/crates/cdk-cli/src/utils.rs b/crates/cdk-cli/src/utils.rs index 3be4eebb..c7100509 100644 --- a/crates/cdk-cli/src/utils.rs +++ b/crates/cdk-cli/src/utils.rs @@ -44,6 +44,23 @@ pub fn check_sufficient_funds(available: Amount, required: Amount) -> Result<()> Ok(()) } +/// Helper function to get a wallet from the multi-mint wallet by mint URL +pub async fn get_wallet_by_mint_url( + multi_mint_wallet: &MultiMintWallet, + mint_url_str: &str, + unit: CurrencyUnit, +) -> Result { + let mint_url = MintUrl::from_str(mint_url_str)?; + + let wallet_key = WalletKey::new(mint_url.clone(), unit); + let wallet = multi_mint_wallet + .get_wallet(&wallet_key) + .await + .ok_or_else(|| anyhow::anyhow!("Wallet not found for mint URL: {}", mint_url_str))?; + + Ok(wallet.clone()) +} + /// Helper function to get a wallet from the multi-mint wallet pub async fn get_wallet_by_index( multi_mint_wallet: &MultiMintWallet,