Przeglądaj źródła

Perform a swap before melting by default.

Fixes #465

This PR modifies the default `melt` operation, performing a swap to melt exact
amounts instead of overpaying and getting the exchange token.

Although this may end up costing more in fees, it is more efficient sometimes
since the overpaid amount is not unusable until the melt is finalized.
Cesar Rodas 2 tygodni temu
rodzic
commit
ffd5ad1b35
2 zmienionych plików z 77 dodań i 1 usunięć
  1. 20 1
      crates/cdk/src/wallet/melt.rs
  2. 57 0
      crates/cdk/src/wallet/proofs.rs

+ 20 - 1
crates/cdk/src/wallet/melt.rs

@@ -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() {
+            if let Ok(Some(new_proofs)) = self
+                .swap(
+                    Some(exact_amount),
+                    SplitTarget::None,
+                    vec![proof.clone()],
+                    None,
+                    false,
+                )
+                .await
+            {
+                input_proofs.extend_from_slice(&new_proofs);
+            } else {
+                // swap failed, add it back to the original set of profos
+                input_proofs.push(proof);
+            }
+        }
+
         self.melt_proofs(quote_id, input_proofs).await
     }
 }

+ 57 - 0
crates/cdk/src/wallet/proofs.rs

@@ -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(