transfer.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. use std::str::FromStr;
  2. use anyhow::{bail, Result};
  3. use cdk::mint_url::MintUrl;
  4. use cdk::wallet::WalletRepository;
  5. use cdk::Amount;
  6. use cdk_common::wallet::WalletKey;
  7. use clap::Args;
  8. use crate::utils::get_number_input;
  9. #[derive(Args)]
  10. pub struct TransferSubCommand {
  11. /// Source mint URL to transfer from (optional - will prompt if not provided)
  12. #[arg(long)]
  13. source_mint: Option<String>,
  14. /// Target mint URL to transfer to (optional - will prompt if not provided)
  15. #[arg(long)]
  16. target_mint: Option<String>,
  17. /// Amount to transfer (optional - will prompt if not provided)
  18. #[arg(short, long, conflicts_with = "full_balance")]
  19. amount: Option<u64>,
  20. /// Transfer all available balance from source mint
  21. #[arg(long, conflicts_with = "amount")]
  22. full_balance: bool,
  23. }
  24. /// Helper function to select a mint from available mints
  25. async fn select_mint(
  26. wallet_repository: &WalletRepository,
  27. prompt: &str,
  28. exclude_mint: Option<&MintUrl>,
  29. unit: &cdk::nuts::CurrencyUnit,
  30. ) -> Result<MintUrl> {
  31. let balances = wallet_repository.get_balances().await?;
  32. // Filter out excluded mint if provided
  33. let available_mints: Vec<_> = balances
  34. .iter()
  35. .filter(|(key, _)| exclude_mint.is_none_or(|excluded| &key.mint_url != excluded))
  36. .collect();
  37. if available_mints.is_empty() {
  38. bail!("No available mints found");
  39. }
  40. println!("\nAvailable mints:");
  41. for (i, (key, balance)) in available_mints.iter().enumerate() {
  42. println!(
  43. " {}: {} ({}) - {} {}",
  44. i, key.mint_url, key.unit, balance, unit
  45. );
  46. }
  47. let mint_number: usize = get_number_input(prompt)?;
  48. available_mints
  49. .get(mint_number)
  50. .map(|(key, _)| key.mint_url.clone())
  51. .ok_or_else(|| anyhow::anyhow!("Invalid mint number"))
  52. }
  53. pub async fn transfer(
  54. wallet_repository: &WalletRepository,
  55. sub_command_args: &TransferSubCommand,
  56. unit: &cdk::nuts::CurrencyUnit,
  57. ) -> Result<()> {
  58. // Check total balance for the requested unit
  59. let balances_by_unit = wallet_repository.total_balance().await?;
  60. let total_balance = balances_by_unit.get(unit).copied().unwrap_or(Amount::ZERO);
  61. if total_balance == Amount::ZERO {
  62. bail!("No funds available for unit {}", unit);
  63. }
  64. // Get source mint URL either from args or by prompting user
  65. let source_mint_url = if let Some(source_mint) = &sub_command_args.source_mint {
  66. let url = MintUrl::from_str(source_mint)?;
  67. // Verify the mint is in the wallet
  68. if !wallet_repository.has_mint(&url).await {
  69. bail!(
  70. "Source mint {} is not in the wallet. Please add it first.",
  71. url
  72. );
  73. }
  74. url
  75. } else {
  76. // Show available mints and let user select source
  77. select_mint(
  78. wallet_repository,
  79. "Enter source mint number to transfer from",
  80. None,
  81. unit,
  82. )
  83. .await?
  84. };
  85. // Get target mint URL either from args or by prompting user
  86. let target_mint_url = if let Some(target_mint) = &sub_command_args.target_mint {
  87. let url = MintUrl::from_str(target_mint)?;
  88. // Verify the mint is in the wallet
  89. if !wallet_repository.has_mint(&url).await {
  90. bail!(
  91. "Target mint {} is not in the wallet. Please add it first.",
  92. url
  93. );
  94. }
  95. url
  96. } else {
  97. // Show available mints (excluding source) and let user select target
  98. select_mint(
  99. wallet_repository,
  100. "Enter target mint number to transfer to",
  101. Some(&source_mint_url),
  102. unit,
  103. )
  104. .await?
  105. };
  106. // Ensure source and target are different
  107. if source_mint_url == target_mint_url {
  108. bail!("Source and target mints must be different");
  109. }
  110. // Check source mint balance
  111. let balances = wallet_repository.get_balances().await?;
  112. let source_key = WalletKey::new(source_mint_url.clone(), unit.clone());
  113. let source_balance = balances.get(&source_key).copied().unwrap_or(Amount::ZERO);
  114. if source_balance == Amount::ZERO {
  115. bail!("Source mint has no balance to transfer");
  116. }
  117. // Get source and target wallets
  118. let source_wallet = wallet_repository.get_wallet(&source_mint_url, unit).await?;
  119. let target_wallet = wallet_repository.get_wallet(&target_mint_url, unit).await?;
  120. // Determine transfer mode and execute
  121. if sub_command_args.full_balance {
  122. println!(
  123. "\nTransferring full balance ({} {}) from {} to {}...",
  124. source_balance, unit, source_mint_url, target_mint_url
  125. );
  126. // Send all from source
  127. let prepared = source_wallet
  128. .prepare_send(source_balance, Default::default())
  129. .await?;
  130. let token = prepared.confirm(None).await?;
  131. // Receive at target
  132. let received = target_wallet
  133. .receive(&token.to_string(), Default::default())
  134. .await?;
  135. let source_balance_after = source_wallet.total_balance().await?;
  136. let target_balance_after = target_wallet.total_balance().await?;
  137. println!("\nTransfer completed successfully!");
  138. println!("Amount sent: {} {}", source_balance, unit);
  139. println!("Amount received: {} {}", received, unit);
  140. let fees_paid = source_balance - received;
  141. if fees_paid > Amount::ZERO {
  142. println!("Fees paid: {} {}", fees_paid, unit);
  143. }
  144. println!("\nUpdated balances:");
  145. println!(
  146. " Source mint ({}): {} {}",
  147. source_mint_url, source_balance_after, unit
  148. );
  149. println!(
  150. " Target mint ({}): {} {}",
  151. target_mint_url, target_balance_after, unit
  152. );
  153. } else {
  154. let amount = match sub_command_args.amount {
  155. Some(amt) => Amount::from(amt),
  156. None => Amount::from(get_number_input::<u64>(&format!(
  157. "Enter amount to transfer in {}",
  158. unit
  159. ))?),
  160. };
  161. if source_balance < amount {
  162. bail!(
  163. "Insufficient funds in source mint. Available: {} {}, Required: {} {}",
  164. source_balance,
  165. unit,
  166. amount,
  167. unit
  168. );
  169. }
  170. println!(
  171. "\nTransferring {} {} from {} to {}...",
  172. amount, unit, source_mint_url, target_mint_url
  173. );
  174. // Send from source
  175. let prepared = source_wallet
  176. .prepare_send(amount, Default::default())
  177. .await?;
  178. let token = prepared.confirm(None).await?;
  179. // Receive at target
  180. let received = target_wallet
  181. .receive(&token.to_string(), Default::default())
  182. .await?;
  183. let source_balance_after = source_wallet.total_balance().await?;
  184. let target_balance_after = target_wallet.total_balance().await?;
  185. println!("\nTransfer completed successfully!");
  186. println!("Amount sent: {} {}", amount, unit);
  187. println!("Amount received: {} {}", received, unit);
  188. let fees_paid = amount - received;
  189. if fees_paid > Amount::ZERO {
  190. println!("Fees paid: {} {}", fees_paid, unit);
  191. }
  192. println!("\nUpdated balances:");
  193. println!(
  194. " Source mint ({}): {} {}",
  195. source_mint_url, source_balance_after, unit
  196. );
  197. println!(
  198. " Target mint ({}): {} {}",
  199. target_mint_url, target_balance_after, unit
  200. );
  201. }
  202. Ok(())
  203. }