Skip to content

Commit 8bcf922

Browse files
committed
Perform a swap before melting by default.
Fixes cashubtc#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.
1 parent 9f4d5ba commit 8bcf922

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

crates/cdk/src/wallet/melt.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::HashMap;
22
use std::str::FromStr;
33

4+
use cdk_common::amount::SplitTarget;
45
use cdk_common::wallet::{Transaction, TransactionDirection};
56
use lightning_invoice::Bolt11Invoice;
67
use tracing::instrument;
@@ -313,14 +314,32 @@ impl Wallet {
313314
.map(|k| k.id)
314315
.collect();
315316
let keyset_fees = self.get_keyset_fees().await?;
316-
let input_proofs = Wallet::select_proofs(
317+
let (mut input_proofs, mut exchange) = Wallet::select_exact_proofs(
317318
inputs_needed_amount,
318319
available_proofs,
319320
&active_keyset_ids,
320321
&keyset_fees,
321322
true,
322323
)?;
323324

325+
if let Some((proof, exact_amount)) = exchange.take() {
326+
if let Ok(Some(new_proofs)) = self
327+
.swap(
328+
Some(exact_amount),
329+
SplitTarget::None,
330+
vec![proof.clone()],
331+
None,
332+
false,
333+
)
334+
.await
335+
{
336+
input_proofs.extend_from_slice(&new_proofs);
337+
} else {
338+
// swap failed, add it back to the original set of profos
339+
input_proofs.push(proof);
340+
}
341+
}
342+
324343
self.melt_proofs(quote_id, input_proofs).await
325344
}
326345
}

crates/cdk/src/wallet/proofs.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,63 @@ impl Wallet {
176176
Ok(balance)
177177
}
178178

179+
/// Select exact proofs
180+
///
181+
/// This function is similar to `select_proofs` but it the selected proofs will not exceed the
182+
/// requested Amount, it will include a Proof and the exacto amount needed form that Proof to
183+
/// perform a swap.
184+
///
185+
/// The intent is to perform a swap with info, or include the Proof as part of the return if the
186+
/// swap is not needed or if the swap failed.
187+
pub fn select_exact_proofs(
188+
amount: Amount,
189+
proofs: Proofs,
190+
active_keyset_ids: &Vec<Id>,
191+
keyset_fees: &HashMap<Id, u64>,
192+
include_fees: bool,
193+
) -> Result<(Proofs, Option<(Proof, Amount)>), Error> {
194+
let mut input_proofs =
195+
Self::select_proofs(amount, proofs, active_keyset_ids, keyset_fees, include_fees)?;
196+
let mut exchange = None;
197+
198+
// How much amounts do we have selected in our proof sets?
199+
let total_for_proofs = input_proofs.total_amount().unwrap_or_default();
200+
201+
if total_for_proofs > amount {
202+
// If the selected proofs' total amount is more than the needed amount with fees,
203+
// consider swapping if it makes sense to avoid locking large tokens. Instead, make the
204+
// exact amount of tokens for the melting, even if that means paying more fees.
205+
//
206+
// If the fees would make it more expensive than it is already, it makes no sense, so
207+
// skip it.
208+
//
209+
// The first step is to sort the proofs, select the one with the biggest amount, and
210+
// perform a swap requesting the exact amount (covering the swap fees).
211+
input_proofs.sort_by(|a, b| a.amount.cmp(&b.amount));
212+
213+
if let Some(proof_to_exchange) = input_proofs.pop() {
214+
let fee_ppk = keyset_fees
215+
.get(&proof_to_exchange.keyset_id)
216+
.cloned()
217+
.unwrap_or_default()
218+
.into();
219+
220+
if let Some(exact_amount_to_melt) = total_for_proofs
221+
.checked_sub(proof_to_exchange.amount)
222+
.and_then(|a| a.checked_add(fee_ppk))
223+
.and_then(|b| amount.checked_sub(b))
224+
{
225+
exchange = Some((proof_to_exchange, exact_amount_to_melt));
226+
} else {
227+
// failed for some reason
228+
input_proofs.push(proof_to_exchange);
229+
}
230+
}
231+
}
232+
233+
Ok((input_proofs, exchange))
234+
}
235+
179236
/// Select proofs
180237
#[instrument(skip_all)]
181238
pub fn select_proofs(

0 commit comments

Comments
 (0)