send.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. use std::io;
  2. use std::io::Write;
  3. use std::str::FromStr;
  4. use anyhow::{bail, Result};
  5. use cdk::amount::SplitTarget;
  6. use cdk::nuts::{Conditions, CurrencyUnit, PublicKey, SpendingConditions};
  7. use cdk::wallet::multi_mint_wallet::WalletKey;
  8. use cdk::wallet::types::SendKind;
  9. use cdk::wallet::MultiMintWallet;
  10. use cdk::Amount;
  11. use clap::Args;
  12. use crate::sub_commands::balance::mint_balances;
  13. #[derive(Args)]
  14. pub struct SendSubCommand {
  15. /// Token Memo
  16. #[arg(short, long)]
  17. memo: Option<String>,
  18. /// Preimage
  19. #[arg(long)]
  20. preimage: Option<String>,
  21. /// Required number of signatures
  22. #[arg(long)]
  23. required_sigs: Option<u64>,
  24. /// Locktime before refund keys can be used
  25. #[arg(short, long)]
  26. locktime: Option<u64>,
  27. /// Pubkey to lock proofs to
  28. #[arg(short, long, action = clap::ArgAction::Append)]
  29. pubkey: Vec<String>,
  30. /// Refund keys that can be used after locktime
  31. #[arg(long, action = clap::ArgAction::Append)]
  32. refund_keys: Vec<String>,
  33. /// Token as V3 token
  34. #[arg(short, long)]
  35. v3: bool,
  36. /// Should the send be offline only
  37. #[arg(short, long)]
  38. offline: bool,
  39. /// Include fee to redeam in token
  40. #[arg(short, long)]
  41. include_fee: bool,
  42. /// Amount willing to overpay to avoid a swap
  43. #[arg(short, long)]
  44. tolerance: Option<u64>,
  45. /// Currency unit e.g. sat
  46. #[arg(default_value = "sat")]
  47. unit: String,
  48. }
  49. pub async fn send(
  50. multi_mint_wallet: &MultiMintWallet,
  51. sub_command_args: &SendSubCommand,
  52. ) -> Result<()> {
  53. let unit = CurrencyUnit::from_str(&sub_command_args.unit)?;
  54. let mints_amounts = mint_balances(multi_mint_wallet, &unit).await?;
  55. println!("Enter mint number to create token");
  56. let mut user_input = String::new();
  57. let stdin = io::stdin();
  58. io::stdout().flush().unwrap();
  59. stdin.read_line(&mut user_input)?;
  60. let mint_number: usize = user_input.trim().parse()?;
  61. if mint_number.gt(&(mints_amounts.len() - 1)) {
  62. bail!("Invalid mint number");
  63. }
  64. println!("Enter value of token in sats");
  65. let mut user_input = String::new();
  66. let stdin = io::stdin();
  67. io::stdout().flush().unwrap();
  68. stdin.read_line(&mut user_input)?;
  69. let token_amount = Amount::from(user_input.trim().parse::<u64>()?);
  70. if token_amount.gt(&mints_amounts[mint_number].1) {
  71. bail!("Not enough funds");
  72. }
  73. let conditions = match &sub_command_args.preimage {
  74. Some(preimage) => {
  75. let pubkeys = match sub_command_args.pubkey.is_empty() {
  76. true => None,
  77. false => Some(
  78. sub_command_args
  79. .pubkey
  80. .iter()
  81. .map(|p| PublicKey::from_str(p).unwrap())
  82. .collect(),
  83. ),
  84. };
  85. let refund_keys = match sub_command_args.refund_keys.is_empty() {
  86. true => None,
  87. false => Some(
  88. sub_command_args
  89. .refund_keys
  90. .iter()
  91. .map(|p| PublicKey::from_str(p).unwrap())
  92. .collect(),
  93. ),
  94. };
  95. let conditions = Conditions::new(
  96. sub_command_args.locktime,
  97. pubkeys,
  98. refund_keys,
  99. sub_command_args.required_sigs,
  100. None,
  101. )
  102. .unwrap();
  103. Some(SpendingConditions::new_htlc(
  104. preimage.clone(),
  105. Some(conditions),
  106. )?)
  107. }
  108. None => match sub_command_args.pubkey.is_empty() {
  109. true => None,
  110. false => {
  111. let pubkeys: Vec<PublicKey> = sub_command_args
  112. .pubkey
  113. .iter()
  114. .map(|p| PublicKey::from_str(p).unwrap())
  115. .collect();
  116. let refund_keys: Vec<PublicKey> = sub_command_args
  117. .refund_keys
  118. .iter()
  119. .map(|p| PublicKey::from_str(p).unwrap())
  120. .collect();
  121. let refund_keys = (!refund_keys.is_empty()).then_some(refund_keys);
  122. let data_pubkey = pubkeys[0];
  123. let pubkeys = pubkeys[1..].to_vec();
  124. let pubkeys = (!pubkeys.is_empty()).then_some(pubkeys);
  125. let conditions = Conditions::new(
  126. sub_command_args.locktime,
  127. pubkeys,
  128. refund_keys,
  129. sub_command_args.required_sigs,
  130. None,
  131. )
  132. .unwrap();
  133. Some(SpendingConditions::P2PKConditions {
  134. data: data_pubkey,
  135. conditions: Some(conditions),
  136. })
  137. }
  138. },
  139. };
  140. let wallet = mints_amounts[mint_number].0.clone();
  141. let wallet = multi_mint_wallet
  142. .get_wallet(&WalletKey::new(wallet, unit))
  143. .await
  144. .expect("Known wallet");
  145. let send_kind = match (sub_command_args.offline, sub_command_args.tolerance) {
  146. (true, Some(amount)) => SendKind::OfflineTolerance(Amount::from(amount)),
  147. (true, None) => SendKind::OfflineExact,
  148. (false, Some(amount)) => SendKind::OnlineTolerance(Amount::from(amount)),
  149. (false, None) => SendKind::OnlineExact,
  150. };
  151. let token = wallet
  152. .send(
  153. token_amount,
  154. sub_command_args.memo.clone(),
  155. conditions,
  156. &SplitTarget::default(),
  157. &send_kind,
  158. sub_command_args.include_fee,
  159. )
  160. .await?;
  161. match sub_command_args.v3 {
  162. true => {
  163. let token = token;
  164. println!("{}", token.to_v3_string());
  165. }
  166. false => {
  167. println!("{}", token);
  168. }
  169. }
  170. Ok(())
  171. }