mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-06 21:56:13 +01:00
Merge pull request #793 from crodas/feature/wallet-swap-before-melt
Perform a swap before melting by default.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use cdk_common::amount::SplitTarget;
|
||||
use cdk_common::wallet::{Transaction, TransactionDirection};
|
||||
use lightning_invoice::Bolt11Invoice;
|
||||
use tracing::instrument;
|
||||
@@ -313,7 +314,7 @@ impl Wallet {
|
||||
.map(|k| k.id)
|
||||
.collect();
|
||||
let keyset_fees = self.get_keyset_fees().await?;
|
||||
let input_proofs = Wallet::select_proofs(
|
||||
let (mut input_proofs, mut exchange) = Wallet::select_exact_proofs(
|
||||
inputs_needed_amount,
|
||||
available_proofs,
|
||||
&active_keyset_ids,
|
||||
@@ -321,6 +322,24 @@ impl Wallet {
|
||||
true,
|
||||
)?;
|
||||
|
||||
if let Some((proof, exact_amount)) = exchange.take() {
|
||||
let new_proofs = self
|
||||
.swap(
|
||||
Some(exact_amount),
|
||||
SplitTarget::None,
|
||||
vec![proof.clone()],
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("Received empty proofs");
|
||||
Error::Internal
|
||||
})?;
|
||||
|
||||
input_proofs.extend_from_slice(&new_proofs);
|
||||
}
|
||||
|
||||
self.melt_proofs(quote_id, input_proofs).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,63 @@ impl Wallet {
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
/// Select exact proofs
|
||||
///
|
||||
/// This function is similar to `select_proofs` but it the selected proofs will not exceed the
|
||||
/// requested Amount, it will include a Proof and the exacto amount needed form that Proof to
|
||||
/// perform a swap.
|
||||
///
|
||||
/// The intent is to perform a swap with info, or include the Proof as part of the return if the
|
||||
/// swap is not needed or if the swap failed.
|
||||
pub fn select_exact_proofs(
|
||||
amount: Amount,
|
||||
proofs: Proofs,
|
||||
active_keyset_ids: &Vec<Id>,
|
||||
keyset_fees: &HashMap<Id, u64>,
|
||||
include_fees: bool,
|
||||
) -> Result<(Proofs, Option<(Proof, Amount)>), Error> {
|
||||
let mut input_proofs =
|
||||
Self::select_proofs(amount, proofs, active_keyset_ids, keyset_fees, include_fees)?;
|
||||
let mut exchange = None;
|
||||
|
||||
// How much amounts do we have selected in our proof sets?
|
||||
let total_for_proofs = input_proofs.total_amount().unwrap_or_default();
|
||||
|
||||
if total_for_proofs > amount {
|
||||
// If the selected proofs' total amount is more than the needed amount with fees,
|
||||
// consider swapping if it makes sense to avoid locking large tokens. Instead, make the
|
||||
// exact amount of tokens for the melting, even if that means paying more fees.
|
||||
//
|
||||
// If the fees would make it more expensive than it is already, it makes no sense, so
|
||||
// skip it.
|
||||
//
|
||||
// The first step is to sort the proofs, select the one with the biggest amount, and
|
||||
// perform a swap requesting the exact amount (covering the swap fees).
|
||||
input_proofs.sort_by(|a, b| a.amount.cmp(&b.amount));
|
||||
|
||||
if let Some(proof_to_exchange) = input_proofs.pop() {
|
||||
let fee_ppk = keyset_fees
|
||||
.get(&proof_to_exchange.keyset_id)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
if let Some(exact_amount_to_melt) = total_for_proofs
|
||||
.checked_sub(proof_to_exchange.amount)
|
||||
.and_then(|a| a.checked_add(fee_ppk))
|
||||
.and_then(|b| amount.checked_sub(b))
|
||||
{
|
||||
exchange = Some((proof_to_exchange, exact_amount_to_melt));
|
||||
} else {
|
||||
// failed for some reason
|
||||
input_proofs.push(proof_to_exchange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((input_proofs, exchange))
|
||||
}
|
||||
|
||||
/// Select proofs
|
||||
#[instrument(skip_all)]
|
||||
pub fn select_proofs(
|
||||
@@ -512,6 +569,20 @@ mod tests {
|
||||
.for_each(|proof| assert_eq!(proof.amount, Amount::ONE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_proof_change() {
|
||||
let proofs = vec![proof(64), proof(4), proof(32)];
|
||||
let (selected_proofs, exchange) =
|
||||
Wallet::select_exact_proofs(97.into(), proofs, &vec![id()], &HashMap::new(), false)
|
||||
.unwrap();
|
||||
assert!(exchange.is_some());
|
||||
let (proof_to_exchange, amount) = exchange.unwrap();
|
||||
|
||||
assert_eq!(selected_proofs.len(), 2);
|
||||
assert_eq!(proof_to_exchange.amount, 64.into());
|
||||
assert_eq!(amount, 61.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_proofs_huge_proofs() {
|
||||
let proofs = (0..32)
|
||||
|
||||
Reference in New Issue
Block a user