Files
cdk/crates/cdk-cli/src/sub_commands/transfer.rs
2025-09-17 17:27:54 +01:00

210 lines
6.1 KiB
Rust

use std::str::FromStr;
use anyhow::{bail, Result};
use cdk::mint_url::MintUrl;
use cdk::wallet::multi_mint_wallet::TransferMode;
use cdk::wallet::MultiMintWallet;
use cdk::Amount;
use clap::Args;
use crate::utils::get_number_input;
#[derive(Args)]
pub struct TransferSubCommand {
/// Source mint URL to transfer from (optional - will prompt if not provided)
#[arg(long)]
source_mint: Option<String>,
/// Target mint URL to transfer to (optional - will prompt if not provided)
#[arg(long)]
target_mint: Option<String>,
/// Amount to transfer (optional - will prompt if not provided)
#[arg(short, long, conflicts_with = "full_balance")]
amount: Option<u64>,
/// Transfer all available balance from source mint
#[arg(long, conflicts_with = "amount")]
full_balance: bool,
}
/// Helper function to select a mint from available mints
async fn select_mint(
multi_mint_wallet: &MultiMintWallet,
prompt: &str,
exclude_mint: Option<&MintUrl>,
) -> Result<MintUrl> {
let balances = multi_mint_wallet.get_balances().await?;
// Filter out excluded mint if provided
let available_mints: Vec<_> = balances
.iter()
.filter(|(url, _)| exclude_mint.is_none_or(|excluded| url != &excluded))
.collect();
if available_mints.is_empty() {
bail!("No available mints found");
}
println!("\nAvailable mints:");
for (i, (mint_url, balance)) in available_mints.iter().enumerate() {
println!(
" {}: {} - {} {}",
i,
mint_url,
balance,
multi_mint_wallet.unit()
);
}
let mint_number: usize = get_number_input(prompt)?;
available_mints
.get(mint_number)
.map(|(url, _)| (*url).clone())
.ok_or_else(|| anyhow::anyhow!("Invalid mint number"))
}
pub async fn transfer(
multi_mint_wallet: &MultiMintWallet,
sub_command_args: &TransferSubCommand,
) -> Result<()> {
// Check total balance across all wallets
let total_balance = multi_mint_wallet.total_balance().await?;
if total_balance == Amount::ZERO {
bail!("No funds available");
}
// Get source mint URL either from args or by prompting user
let source_mint_url = if let Some(source_mint) = &sub_command_args.source_mint {
let url = MintUrl::from_str(source_mint)?;
// Verify the mint is in the wallet
if !multi_mint_wallet.has_mint(&url).await {
bail!(
"Source mint {} is not in the wallet. Please add it first.",
url
);
}
url
} else {
// Show available mints and let user select source
select_mint(
multi_mint_wallet,
"Enter source mint number to transfer from",
None,
)
.await?
};
// Get target mint URL either from args or by prompting user
let target_mint_url = if let Some(target_mint) = &sub_command_args.target_mint {
let url = MintUrl::from_str(target_mint)?;
// Verify the mint is in the wallet
if !multi_mint_wallet.has_mint(&url).await {
bail!(
"Target mint {} is not in the wallet. Please add it first.",
url
);
}
url
} else {
// Show available mints (excluding source) and let user select target
select_mint(
multi_mint_wallet,
"Enter target mint number to transfer to",
Some(&source_mint_url),
)
.await?
};
// Ensure source and target are different
if source_mint_url == target_mint_url {
bail!("Source and target mints must be different");
}
// Check source mint balance
let balances = multi_mint_wallet.get_balances().await?;
let source_balance = balances
.get(&source_mint_url)
.copied()
.unwrap_or(Amount::ZERO);
if source_balance == Amount::ZERO {
bail!("Source mint has no balance to transfer");
}
// Determine transfer mode based on user input
let transfer_mode = if sub_command_args.full_balance {
println!(
"\nTransferring full balance ({} {}) from {} to {}...",
source_balance,
multi_mint_wallet.unit(),
source_mint_url,
target_mint_url
);
TransferMode::FullBalance
} else {
let amount = match sub_command_args.amount {
Some(amt) => Amount::from(amt),
None => Amount::from(get_number_input::<u64>(&format!(
"Enter amount to transfer in {}",
multi_mint_wallet.unit()
))?),
};
if source_balance < amount {
bail!(
"Insufficient funds in source mint. Available: {} {}, Required: {} {}",
source_balance,
multi_mint_wallet.unit(),
amount,
multi_mint_wallet.unit()
);
}
println!(
"\nTransferring {} {} from {} to {}...",
amount,
multi_mint_wallet.unit(),
source_mint_url,
target_mint_url
);
TransferMode::ExactReceive(amount)
};
// Perform the transfer
let transfer_result = multi_mint_wallet
.transfer(&source_mint_url, &target_mint_url, transfer_mode)
.await?;
println!("\nTransfer completed successfully!");
println!(
"Amount sent: {} {}",
transfer_result.amount_sent,
multi_mint_wallet.unit()
);
println!(
"Amount received: {} {}",
transfer_result.amount_received,
multi_mint_wallet.unit()
);
if transfer_result.fees_paid > Amount::ZERO {
println!(
"Fees paid: {} {}",
transfer_result.fees_paid,
multi_mint_wallet.unit()
);
}
println!("\nUpdated balances:");
println!(
" Source mint ({}): {} {}",
source_mint_url,
transfer_result.source_balance_after,
multi_mint_wallet.unit()
);
println!(
" Target mint ({}): {} {}",
target_mint_url,
transfer_result.target_balance_after,
multi_mint_wallet.unit()
);
Ok(())
}