transfer.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. use std::str::FromStr;
  2. use anyhow::{bail, Result};
  3. use cdk::mint_url::MintUrl;
  4. use cdk::wallet::multi_mint_wallet::TransferMode;
  5. use cdk::wallet::MultiMintWallet;
  6. use cdk::Amount;
  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. multi_mint_wallet: &MultiMintWallet,
  27. prompt: &str,
  28. exclude_mint: Option<&MintUrl>,
  29. ) -> Result<MintUrl> {
  30. let balances = multi_mint_wallet.get_balances().await?;
  31. // Filter out excluded mint if provided
  32. let available_mints: Vec<_> = balances
  33. .iter()
  34. .filter(|(url, _)| exclude_mint.is_none_or(|excluded| url != &excluded))
  35. .collect();
  36. if available_mints.is_empty() {
  37. bail!("No available mints found");
  38. }
  39. println!("\nAvailable mints:");
  40. for (i, (mint_url, balance)) in available_mints.iter().enumerate() {
  41. println!(
  42. " {}: {} - {} {}",
  43. i,
  44. mint_url,
  45. balance,
  46. multi_mint_wallet.unit()
  47. );
  48. }
  49. let mint_number: usize = get_number_input(prompt)?;
  50. available_mints
  51. .get(mint_number)
  52. .map(|(url, _)| (*url).clone())
  53. .ok_or_else(|| anyhow::anyhow!("Invalid mint number"))
  54. }
  55. pub async fn transfer(
  56. multi_mint_wallet: &MultiMintWallet,
  57. sub_command_args: &TransferSubCommand,
  58. ) -> Result<()> {
  59. // Check total balance across all wallets
  60. let total_balance = multi_mint_wallet.total_balance().await?;
  61. if total_balance == Amount::ZERO {
  62. bail!("No funds available");
  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 !multi_mint_wallet.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. multi_mint_wallet,
  79. "Enter source mint number to transfer from",
  80. None,
  81. )
  82. .await?
  83. };
  84. // Get target mint URL either from args or by prompting user
  85. let target_mint_url = if let Some(target_mint) = &sub_command_args.target_mint {
  86. let url = MintUrl::from_str(target_mint)?;
  87. // Verify the mint is in the wallet
  88. if !multi_mint_wallet.has_mint(&url).await {
  89. bail!(
  90. "Target mint {} is not in the wallet. Please add it first.",
  91. url
  92. );
  93. }
  94. url
  95. } else {
  96. // Show available mints (excluding source) and let user select target
  97. select_mint(
  98. multi_mint_wallet,
  99. "Enter target mint number to transfer to",
  100. Some(&source_mint_url),
  101. )
  102. .await?
  103. };
  104. // Ensure source and target are different
  105. if source_mint_url == target_mint_url {
  106. bail!("Source and target mints must be different");
  107. }
  108. // Check source mint balance
  109. let balances = multi_mint_wallet.get_balances().await?;
  110. let source_balance = balances
  111. .get(&source_mint_url)
  112. .copied()
  113. .unwrap_or(Amount::ZERO);
  114. if source_balance == Amount::ZERO {
  115. bail!("Source mint has no balance to transfer");
  116. }
  117. // Determine transfer mode based on user input
  118. let transfer_mode = if sub_command_args.full_balance {
  119. println!(
  120. "\nTransferring full balance ({} {}) from {} to {}...",
  121. source_balance,
  122. multi_mint_wallet.unit(),
  123. source_mint_url,
  124. target_mint_url
  125. );
  126. TransferMode::FullBalance
  127. } else {
  128. let amount = match sub_command_args.amount {
  129. Some(amt) => Amount::from(amt),
  130. None => Amount::from(get_number_input::<u64>(&format!(
  131. "Enter amount to transfer in {}",
  132. multi_mint_wallet.unit()
  133. ))?),
  134. };
  135. if source_balance < amount {
  136. bail!(
  137. "Insufficient funds in source mint. Available: {} {}, Required: {} {}",
  138. source_balance,
  139. multi_mint_wallet.unit(),
  140. amount,
  141. multi_mint_wallet.unit()
  142. );
  143. }
  144. println!(
  145. "\nTransferring {} {} from {} to {}...",
  146. amount,
  147. multi_mint_wallet.unit(),
  148. source_mint_url,
  149. target_mint_url
  150. );
  151. TransferMode::ExactReceive(amount)
  152. };
  153. // Perform the transfer
  154. let transfer_result = multi_mint_wallet
  155. .transfer(&source_mint_url, &target_mint_url, transfer_mode)
  156. .await?;
  157. println!("\nTransfer completed successfully!");
  158. println!(
  159. "Amount sent: {} {}",
  160. transfer_result.amount_sent,
  161. multi_mint_wallet.unit()
  162. );
  163. println!(
  164. "Amount received: {} {}",
  165. transfer_result.amount_received,
  166. multi_mint_wallet.unit()
  167. );
  168. if transfer_result.fees_paid > Amount::ZERO {
  169. println!(
  170. "Fees paid: {} {}",
  171. transfer_result.fees_paid,
  172. multi_mint_wallet.unit()
  173. );
  174. }
  175. println!("\nUpdated balances:");
  176. println!(
  177. " Source mint ({}): {} {}",
  178. source_mint_url,
  179. transfer_result.source_balance_after,
  180. multi_mint_wallet.unit()
  181. );
  182. println!(
  183. " Target mint ({}): {} {}",
  184. target_mint_url,
  185. transfer_result.target_balance_after,
  186. multi_mint_wallet.unit()
  187. );
  188. Ok(())
  189. }