send.rs 5.5 KB

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