mirror of
https://github.com/aljazceru/cdk.git
synced 2026-01-04 13:35:20 +01:00
Mpp cdk cli (#743)
* refactor: Extract user input logic into a helper function * feat: get multiple quotes (hacky) * refactor: cdk-cli * refactor: cdk-cli * feat: refactor balances
This commit is contained in:
@@ -20,6 +20,7 @@ use url::Url;
|
||||
mod nostr_storage;
|
||||
mod sub_commands;
|
||||
mod token_storage;
|
||||
mod utils;
|
||||
|
||||
const DEFAULT_WORK_DIR: &str = ".cdk-cli";
|
||||
|
||||
@@ -183,7 +184,17 @@ async fn main() -> Result<()> {
|
||||
|
||||
let wallet = builder.build()?;
|
||||
|
||||
wallet.get_mint_info().await?;
|
||||
let wallet_clone = wallet.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = wallet_clone.get_mint_info().await {
|
||||
tracing::error!(
|
||||
"Could not get mint quote for {}, {}",
|
||||
wallet_clone.mint_url,
|
||||
err
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
wallets.push(wallet);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ pub async fn mint_balances(
|
||||
|
||||
let mut wallets_vec = Vec::with_capacity(wallets.len());
|
||||
|
||||
for (i, (mint_url, amount)) in wallets.iter().enumerate() {
|
||||
for (i, (mint_url, amount)) in wallets
|
||||
.iter()
|
||||
.filter(|(_, a)| a > &&Amount::ZERO)
|
||||
.enumerate()
|
||||
{
|
||||
let mint_url = mint_url.clone();
|
||||
println!("{i}: {mint_url} {amount} {unit}");
|
||||
wallets_vec.push((mint_url, *amount))
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
@@ -9,8 +7,10 @@ use cdk::wallet::multi_mint_wallet::MultiMintWallet;
|
||||
use cdk::wallet::types::WalletKey;
|
||||
use cdk::Bolt11Invoice;
|
||||
use clap::Args;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::sub_commands::balance::mint_balances;
|
||||
use crate::utils::{get_number_input, get_user_input, get_wallet_by_index, validate_mint_number};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct MeltSubCommand {
|
||||
@@ -29,82 +29,148 @@ pub async fn pay(
|
||||
let unit = CurrencyUnit::from_str(&sub_command_args.unit)?;
|
||||
let mints_amounts = mint_balances(multi_mint_wallet, &unit).await?;
|
||||
|
||||
println!("Enter mint number to melt from");
|
||||
let mut mints = vec![];
|
||||
let mut mint_amounts = vec![];
|
||||
if sub_command_args.mpp {
|
||||
loop {
|
||||
let mint_number: String =
|
||||
get_user_input("Enter mint number to melt from and -1 when done.")?;
|
||||
|
||||
let mut user_input = String::new();
|
||||
let stdin = io::stdin();
|
||||
io::stdout().flush().unwrap();
|
||||
stdin.read_line(&mut user_input)?;
|
||||
|
||||
let mint_number: usize = user_input.trim().parse()?;
|
||||
|
||||
if mint_number.gt(&(mints_amounts.len() - 1)) {
|
||||
bail!("Invalid mint number");
|
||||
}
|
||||
|
||||
let wallet = mints_amounts[mint_number].0.clone();
|
||||
|
||||
let wallet = multi_mint_wallet
|
||||
.get_wallet(&WalletKey::new(wallet, unit))
|
||||
.await
|
||||
.expect("Known wallet");
|
||||
|
||||
println!("Enter bolt11 invoice request");
|
||||
|
||||
let mut user_input = String::new();
|
||||
let stdin = io::stdin();
|
||||
io::stdout().flush().unwrap();
|
||||
stdin.read_line(&mut user_input)?;
|
||||
let bolt11 = Bolt11Invoice::from_str(user_input.trim())?;
|
||||
|
||||
let available_funds =
|
||||
<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * MSAT_IN_SAT;
|
||||
|
||||
// Determine payment amount and options
|
||||
let options = if sub_command_args.mpp || bolt11.amount_milli_satoshis().is_none() {
|
||||
// Get user input for amount
|
||||
println!(
|
||||
"Enter the amount you would like to pay in sats for a {} payment.",
|
||||
if sub_command_args.mpp {
|
||||
"MPP"
|
||||
} else {
|
||||
"amountless invoice"
|
||||
if mint_number == "-1" || mint_number.is_empty() {
|
||||
break;
|
||||
}
|
||||
);
|
||||
|
||||
let mut user_input = String::new();
|
||||
io::stdout().flush()?;
|
||||
io::stdin().read_line(&mut user_input)?;
|
||||
let mint_number: usize = mint_number.parse()?;
|
||||
validate_mint_number(mint_number, mints_amounts.len())?;
|
||||
|
||||
let user_amount = user_input.trim_end().parse::<u64>()? * MSAT_IN_SAT;
|
||||
|
||||
if user_amount > available_funds {
|
||||
bail!("Not enough funds");
|
||||
mints.push(mint_number);
|
||||
let melt_amount: u64 =
|
||||
get_number_input("Enter amount to mint from this mint in sats.")?;
|
||||
mint_amounts.push(melt_amount);
|
||||
}
|
||||
|
||||
Some(if sub_command_args.mpp {
|
||||
MeltOptions::new_mpp(user_amount)
|
||||
} else {
|
||||
MeltOptions::new_amountless(user_amount)
|
||||
})
|
||||
let bolt11 = Bolt11Invoice::from_str(&get_user_input("Enter bolt11 invoice request")?)?;
|
||||
|
||||
let mut quotes = JoinSet::new();
|
||||
|
||||
for (mint, amount) in mints.iter().zip(mint_amounts) {
|
||||
let wallet = mints_amounts[*mint].0.clone();
|
||||
|
||||
let wallet = multi_mint_wallet
|
||||
.get_wallet(&WalletKey::new(wallet, unit.clone()))
|
||||
.await
|
||||
.expect("Known wallet");
|
||||
let options = MeltOptions::new_mpp(amount * 1000);
|
||||
|
||||
let bolt11_clone = bolt11.clone();
|
||||
|
||||
quotes.spawn(async move {
|
||||
let quote = wallet
|
||||
.melt_quote(bolt11_clone.to_string(), Some(options))
|
||||
.await;
|
||||
|
||||
(wallet, quote)
|
||||
});
|
||||
}
|
||||
|
||||
let quotes = quotes.join_all().await;
|
||||
|
||||
for (wallet, quote) in quotes.iter() {
|
||||
if let Err(quote) = quote {
|
||||
tracing::error!("Could not get quote for {}: {:?}", wallet.mint_url, quote);
|
||||
bail!("Could not get melt quote for {}", wallet.mint_url);
|
||||
} else {
|
||||
let quote = quote.as_ref().unwrap();
|
||||
println!(
|
||||
"Melt quote {} for mint {} of amount {} with fee {}.",
|
||||
quote.id, wallet.mint_url, quote.amount, quote.fee_reserve
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut melts = JoinSet::new();
|
||||
|
||||
for (wallet, quote) in quotes {
|
||||
let quote = quote.expect("Errors checked above");
|
||||
|
||||
melts.spawn(async move {
|
||||
let melt = wallet.melt("e.id).await;
|
||||
(wallet, melt)
|
||||
});
|
||||
}
|
||||
|
||||
let melts = melts.join_all().await;
|
||||
|
||||
let mut error = false;
|
||||
|
||||
for (wallet, melt) in melts {
|
||||
match melt {
|
||||
Ok(melt) => {
|
||||
println!(
|
||||
"Melt for {} paid {} with fee of {} ",
|
||||
wallet.mint_url, melt.amount, melt.fee_paid
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Melt for {} failed with {}", wallet.mint_url, err);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if error {
|
||||
bail!("Could not complete all melts");
|
||||
}
|
||||
} else {
|
||||
// Check if invoice amount exceeds available funds
|
||||
let invoice_amount = bolt11.amount_milli_satoshis().unwrap();
|
||||
if invoice_amount > available_funds {
|
||||
bail!("Not enough funds");
|
||||
let mint_number: usize = get_number_input("Enter mint number to melt from")?;
|
||||
|
||||
let wallet =
|
||||
get_wallet_by_index(multi_mint_wallet, &mints_amounts, mint_number, unit.clone())
|
||||
.await?;
|
||||
|
||||
let bolt11 = Bolt11Invoice::from_str(&get_user_input("Enter bolt11 invoice request")?)?;
|
||||
|
||||
let available_funds =
|
||||
<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * MSAT_IN_SAT;
|
||||
|
||||
// Determine payment amount and options
|
||||
let options = if bolt11.amount_milli_satoshis().is_none() {
|
||||
// Get user input for amount
|
||||
let prompt = format!(
|
||||
"Enter the amount you would like to pay in sats for a {} payment.",
|
||||
if sub_command_args.mpp {
|
||||
"MPP"
|
||||
} else {
|
||||
"amountless invoice"
|
||||
}
|
||||
);
|
||||
|
||||
let user_amount = get_number_input::<u64>(&prompt)? * MSAT_IN_SAT;
|
||||
|
||||
if user_amount > available_funds {
|
||||
bail!("Not enough funds");
|
||||
}
|
||||
|
||||
Some(MeltOptions::new_amountless(user_amount))
|
||||
} else {
|
||||
// Check if invoice amount exceeds available funds
|
||||
let invoice_amount = bolt11.amount_milli_satoshis().unwrap();
|
||||
if invoice_amount > available_funds {
|
||||
bail!("Not enough funds");
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
// Process payment
|
||||
let quote = wallet.melt_quote(bolt11.to_string(), options).await?;
|
||||
println!("{:?}", quote);
|
||||
|
||||
let melt = wallet.melt("e.id).await?;
|
||||
println!("Paid invoice: {}", melt.state);
|
||||
|
||||
if let Some(preimage) = melt.preimage {
|
||||
println!("Payment preimage: {}", preimage);
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
// Process payment
|
||||
let quote = wallet.melt_quote(bolt11.to_string(), options).await?;
|
||||
println!("{:?}", quote);
|
||||
|
||||
let melt = wallet.melt("e.id).await?;
|
||||
println!("Paid invoice: {}", melt.state);
|
||||
|
||||
if let Some(preimage) = melt.preimage {
|
||||
println!("Payment preimage: {}", preimage);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -5,12 +5,13 @@ use cdk::amount::SplitTarget;
|
||||
use cdk::mint_url::MintUrl;
|
||||
use cdk::nuts::nut00::ProofsMethods;
|
||||
use cdk::nuts::{CurrencyUnit, MintQuoteState, NotificationPayload};
|
||||
use cdk::wallet::types::WalletKey;
|
||||
use cdk::wallet::{MultiMintWallet, WalletSubscription};
|
||||
use cdk::Amount;
|
||||
use clap::Args;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::get_or_create_wallet;
|
||||
|
||||
#[derive(Args, Serialize, Deserialize)]
|
||||
pub struct MintSubCommand {
|
||||
/// Mint url
|
||||
@@ -36,18 +37,7 @@ pub async fn mint(
|
||||
let unit = CurrencyUnit::from_str(&sub_command_args.unit)?;
|
||||
let description: Option<String> = sub_command_args.description.clone();
|
||||
|
||||
let wallet = match multi_mint_wallet
|
||||
.get_wallet(&WalletKey::new(mint_url.clone(), unit.clone()))
|
||||
.await
|
||||
{
|
||||
Some(wallet) => wallet.clone(),
|
||||
None => {
|
||||
tracing::debug!("Wallet does not exist creating..");
|
||||
multi_mint_wallet
|
||||
.create_and_add_wallet(&mint_url.to_string(), unit, None)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let wallet = get_or_create_wallet(multi_mint_wallet, &mint_url, unit).await?;
|
||||
|
||||
let quote_id = match &sub_command_args.quote_id {
|
||||
None => {
|
||||
|
||||
@@ -14,6 +14,7 @@ use nostr_sdk::nips::nip04;
|
||||
use nostr_sdk::{Filter, Keys, Kind, Timestamp};
|
||||
|
||||
use crate::nostr_storage;
|
||||
use crate::utils::get_or_create_wallet;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ReceiveSubCommand {
|
||||
@@ -137,17 +138,14 @@ async fn receive_token(
|
||||
let token: Token = Token::from_str(token_str)?;
|
||||
|
||||
let mint_url = token.mint_url()?;
|
||||
let unit = token.unit().unwrap_or_default();
|
||||
|
||||
let wallet_key = WalletKey::new(mint_url.clone(), token.unit().unwrap_or_default());
|
||||
|
||||
if multi_mint_wallet.get_wallet(&wallet_key).await.is_none() {
|
||||
multi_mint_wallet
|
||||
.create_and_add_wallet(
|
||||
&mint_url.to_string(),
|
||||
token.unit().unwrap_or_default(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
if multi_mint_wallet
|
||||
.get_wallet(&WalletKey::new(mint_url.clone(), unit.clone()))
|
||||
.await
|
||||
.is_none()
|
||||
{
|
||||
get_or_create_wallet(multi_mint_wallet, &mint_url, unit).await?;
|
||||
}
|
||||
|
||||
let amount = multi_mint_wallet
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use cdk::nuts::{Conditions, CurrencyUnit, PublicKey, SpendingConditions};
|
||||
use cdk::wallet::types::{SendKind, WalletKey};
|
||||
use cdk::wallet::types::SendKind;
|
||||
use cdk::wallet::{MultiMintWallet, SendMemo, SendOptions};
|
||||
use cdk::Amount;
|
||||
use clap::Args;
|
||||
|
||||
use crate::sub_commands::balance::mint_balances;
|
||||
use crate::utils::{check_sufficient_funds, get_number_input, get_wallet_by_index};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct SendSubCommand {
|
||||
@@ -55,30 +54,13 @@ pub async fn send(
|
||||
let unit = CurrencyUnit::from_str(&sub_command_args.unit)?;
|
||||
let mints_amounts = mint_balances(multi_mint_wallet, &unit).await?;
|
||||
|
||||
println!("Enter mint number to create token");
|
||||
let mint_number: usize = get_number_input("Enter mint number to create token")?;
|
||||
|
||||
let mut user_input = String::new();
|
||||
let stdin = io::stdin();
|
||||
io::stdout().flush().unwrap();
|
||||
stdin.read_line(&mut user_input)?;
|
||||
let wallet = get_wallet_by_index(multi_mint_wallet, &mints_amounts, mint_number, unit).await?;
|
||||
|
||||
let mint_number: usize = user_input.trim().parse()?;
|
||||
let token_amount = Amount::from(get_number_input::<u64>("Enter value of token in sats")?);
|
||||
|
||||
if mint_number.gt(&(mints_amounts.len() - 1)) {
|
||||
bail!("Invalid mint number");
|
||||
}
|
||||
|
||||
println!("Enter value of token in sats");
|
||||
|
||||
let mut user_input = String::new();
|
||||
let stdin = io::stdin();
|
||||
io::stdout().flush().unwrap();
|
||||
stdin.read_line(&mut user_input)?;
|
||||
let token_amount = Amount::from(user_input.trim().parse::<u64>()?);
|
||||
|
||||
if token_amount.gt(&mints_amounts[mint_number].1) {
|
||||
bail!("Not enough funds");
|
||||
}
|
||||
check_sufficient_funds(mints_amounts[mint_number].1, token_amount)?;
|
||||
|
||||
let conditions = match &sub_command_args.preimage {
|
||||
Some(preimage) => {
|
||||
@@ -156,12 +138,6 @@ pub async fn send(
|
||||
},
|
||||
};
|
||||
|
||||
let wallet = mints_amounts[mint_number].0.clone();
|
||||
let wallet = multi_mint_wallet
|
||||
.get_wallet(&WalletKey::new(wallet, unit))
|
||||
.await
|
||||
.expect("Known wallet");
|
||||
|
||||
let send_kind = match (sub_command_args.offline, sub_command_args.tolerance) {
|
||||
(true, Some(amount)) => SendKind::OfflineTolerance(Amount::from(amount)),
|
||||
(true, None) => SendKind::OfflineExact,
|
||||
|
||||
83
crates/cdk-cli/src/utils.rs
Normal file
83
crates/cdk-cli/src/utils.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::io::{self, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use cdk::mint_url::MintUrl;
|
||||
use cdk::nuts::CurrencyUnit;
|
||||
use cdk::wallet::multi_mint_wallet::MultiMintWallet;
|
||||
use cdk::wallet::types::WalletKey;
|
||||
use cdk::Amount;
|
||||
|
||||
/// Helper function to get user input with a prompt
|
||||
pub fn get_user_input(prompt: &str) -> Result<String> {
|
||||
println!("{}", prompt);
|
||||
let mut user_input = String::new();
|
||||
io::stdout().flush()?;
|
||||
io::stdin().read_line(&mut user_input)?;
|
||||
Ok(user_input.trim().to_string())
|
||||
}
|
||||
|
||||
/// Helper function to get a number from user input with a prompt
|
||||
pub fn get_number_input<T>(prompt: &str) -> Result<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let input = get_user_input(prompt)?;
|
||||
let number = input.parse::<T>()?;
|
||||
Ok(number)
|
||||
}
|
||||
|
||||
/// Helper function to validate a mint number against available mints
|
||||
pub fn validate_mint_number(mint_number: usize, mint_count: usize) -> Result<()> {
|
||||
if mint_number >= mint_count {
|
||||
bail!("Invalid mint number");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to check if there are enough funds for an operation
|
||||
pub fn check_sufficient_funds(available: Amount, required: Amount) -> Result<()> {
|
||||
if required.gt(&available) {
|
||||
bail!("Not enough funds");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to get a wallet from the multi-mint wallet
|
||||
pub async fn get_wallet_by_index(
|
||||
multi_mint_wallet: &MultiMintWallet,
|
||||
mint_amounts: &[(MintUrl, Amount)],
|
||||
mint_number: usize,
|
||||
unit: CurrencyUnit,
|
||||
) -> Result<cdk::wallet::Wallet> {
|
||||
validate_mint_number(mint_number, mint_amounts.len())?;
|
||||
|
||||
let wallet_key = WalletKey::new(mint_amounts[mint_number].0.clone(), unit);
|
||||
let wallet = multi_mint_wallet
|
||||
.get_wallet(&wallet_key)
|
||||
.await
|
||||
.ok_or_else(|| anyhow::anyhow!("Wallet not found"))?;
|
||||
|
||||
Ok(wallet.clone())
|
||||
}
|
||||
|
||||
/// Helper function to create or get a wallet
|
||||
pub async fn get_or_create_wallet(
|
||||
multi_mint_wallet: &MultiMintWallet,
|
||||
mint_url: &MintUrl,
|
||||
unit: CurrencyUnit,
|
||||
) -> Result<cdk::wallet::Wallet> {
|
||||
match multi_mint_wallet
|
||||
.get_wallet(&WalletKey::new(mint_url.clone(), unit.clone()))
|
||||
.await
|
||||
{
|
||||
Some(wallet) => Ok(wallet.clone()),
|
||||
None => {
|
||||
tracing::debug!("Wallet does not exist creating..");
|
||||
multi_mint_wallet
|
||||
.create_and_add_wallet(&mint_url.to_string(), unit, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user