use std::str::FromStr; use anyhow::Result; use cdk::nuts::{Conditions, CurrencyUnit, PublicKey, SpendingConditions}; use cdk::wallet::types::SendKind; use cdk::wallet::{MultiMintWallet, SendMemo, SendOptions}; 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}; #[derive(Args)] pub struct SendSubCommand { /// Token Memo #[arg(short, long)] memo: Option, /// Preimage #[arg(long)] preimage: Option, /// Required number of signatures #[arg(long)] required_sigs: Option, /// Locktime before refund keys can be used #[arg(short, long)] locktime: Option, /// Pubkey to lock proofs to #[arg(short, long, action = clap::ArgAction::Append)] pubkey: Vec, /// Refund keys that can be used after locktime #[arg(long, action = clap::ArgAction::Append)] refund_keys: Vec, /// Token as V3 token #[arg(short, long)] v3: bool, /// Should the send be offline only #[arg(short, long)] offline: bool, /// Include fee to redeem in token #[arg(short, long)] include_fee: bool, /// Amount willing to overpay to avoid a swap #[arg(short, long)] tolerance: Option, /// Currency unit e.g. sat #[arg(default_value = "sat")] unit: String, } pub async fn send( multi_mint_wallet: &MultiMintWallet, sub_command_args: &SendSubCommand, ) -> Result<()> { 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?; let token_amount = Amount::from(get_number_input::("Enter value of token in sats")?); check_sufficient_funds(mints_amounts[mint_number].1, token_amount)?; let conditions = match &sub_command_args.preimage { Some(preimage) => { let pubkeys = match sub_command_args.pubkey.is_empty() { true => None, false => Some( sub_command_args .pubkey .iter() .map(|p| PublicKey::from_str(p).unwrap()) .collect(), ), }; let refund_keys = match sub_command_args.refund_keys.is_empty() { true => None, false => Some( sub_command_args .refund_keys .iter() .map(|p| PublicKey::from_str(p).unwrap()) .collect(), ), }; let conditions = Conditions::new( sub_command_args.locktime, pubkeys, refund_keys, sub_command_args.required_sigs, None, ) .unwrap(); Some(SpendingConditions::new_htlc( preimage.clone(), Some(conditions), )?) } None => match sub_command_args.pubkey.is_empty() { true => None, false => { let pubkeys: Vec = sub_command_args .pubkey .iter() .map(|p| PublicKey::from_str(p).unwrap()) .collect(); let refund_keys: Vec = sub_command_args .refund_keys .iter() .map(|p| PublicKey::from_str(p).unwrap()) .collect(); let refund_keys = (!refund_keys.is_empty()).then_some(refund_keys); let data_pubkey = pubkeys[0]; let pubkeys = pubkeys[1..].to_vec(); let pubkeys = (!pubkeys.is_empty()).then_some(pubkeys); let conditions = Conditions::new( sub_command_args.locktime, pubkeys, refund_keys, sub_command_args.required_sigs, None, ) .unwrap(); Some(SpendingConditions::P2PKConditions { data: data_pubkey, conditions: Some(conditions), }) } }, }; let send_kind = match (sub_command_args.offline, sub_command_args.tolerance) { (true, Some(amount)) => SendKind::OfflineTolerance(Amount::from(amount)), (true, None) => SendKind::OfflineExact, (false, Some(amount)) => SendKind::OnlineTolerance(Amount::from(amount)), (false, None) => SendKind::OnlineExact, }; let prepared_send = wallet .prepare_send( token_amount, SendOptions { memo: sub_command_args.memo.clone().map(|memo| SendMemo { memo, include_memo: true, }), send_kind, include_fee: sub_command_args.include_fee, conditions, ..Default::default() }, ) .await?; let token = wallet.send(prepared_send, None).await?; match sub_command_args.v3 { true => { let token = token; println!("{}", token.to_v3_string()); } false => { println!("{}", token); } } Ok(()) }