|  | @@ -1,8 +1,10 @@
 | 
											
												
													
														|  | -use std::collections::HashSet;
 |  | 
 | 
											
												
													
														|  | 
 |  | +use std::collections::{HashMap, HashSet};
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +use cdk_common::Id;
 | 
											
												
													
														|  |  use tracing::instrument;
 |  |  use tracing::instrument;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  use crate::amount::SplitTarget;
 |  |  use crate::amount::SplitTarget;
 | 
											
												
													
														|  | 
 |  | +use crate::fees::calculate_fee;
 | 
											
												
													
														|  |  use crate::nuts::nut00::ProofsMethods;
 |  |  use crate::nuts::nut00::ProofsMethods;
 | 
											
												
													
														|  |  use crate::nuts::{
 |  |  use crate::nuts::{
 | 
											
												
													
														|  |      CheckStateRequest, Proof, ProofState, Proofs, PublicKey, SpendingConditions, State,
 |  |      CheckStateRequest, Proof, ProofState, Proofs, PublicKey, SpendingConditions, State,
 | 
											
										
											
												
													
														|  | @@ -30,6 +32,13 @@ impl Wallet {
 | 
											
												
													
														|  |              .await
 |  |              .await
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +    /// Get pending spent [`Proofs`]
 | 
											
												
													
														|  | 
 |  | +    #[instrument(skip(self))]
 | 
											
												
													
														|  | 
 |  | +    pub async fn get_pending_spent_proofs(&self) -> Result<Proofs, Error> {
 | 
											
												
													
														|  | 
 |  | +        self.get_proofs_with(Some(vec![State::PendingSpent]), None)
 | 
											
												
													
														|  | 
 |  | +            .await
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |      /// Get this wallet's [Proofs] that match the args
 |  |      /// Get this wallet's [Proofs] that match the args
 | 
											
												
													
														|  |      pub async fn get_proofs_with(
 |  |      pub async fn get_proofs_with(
 | 
											
												
													
														|  |          &self,
 |  |          &self,
 | 
											
										
											
												
													
														|  | @@ -53,7 +62,10 @@ impl Wallet {
 | 
											
												
													
														|  |      /// Return proofs to unspent allowing them to be selected and spent
 |  |      /// Return proofs to unspent allowing them to be selected and spent
 | 
											
												
													
														|  |      #[instrument(skip(self))]
 |  |      #[instrument(skip(self))]
 | 
											
												
													
														|  |      pub async fn unreserve_proofs(&self, ys: Vec<PublicKey>) -> Result<(), Error> {
 |  |      pub async fn unreserve_proofs(&self, ys: Vec<PublicKey>) -> Result<(), Error> {
 | 
											
												
													
														|  | -        Ok(self.localstore.set_unspent_proofs(ys).await?)
 |  | 
 | 
											
												
													
														|  | 
 |  | +        Ok(self
 | 
											
												
													
														|  | 
 |  | +            .localstore
 | 
											
												
													
														|  | 
 |  | +            .update_proofs_state(ys, State::Unspent)
 | 
											
												
													
														|  | 
 |  | +            .await?)
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      /// Reclaim unspent proofs
 |  |      /// Reclaim unspent proofs
 | 
											
										
											
												
													
														|  | @@ -112,7 +124,7 @@ impl Wallet {
 | 
											
												
													
														|  |              .get_proofs(
 |  |              .get_proofs(
 | 
											
												
													
														|  |                  Some(self.mint_url.clone()),
 |  |                  Some(self.mint_url.clone()),
 | 
											
												
													
														|  |                  Some(self.unit.clone()),
 |  |                  Some(self.unit.clone()),
 | 
											
												
													
														|  | -                Some(vec![State::Pending, State::Reserved]),
 |  | 
 | 
											
												
													
														|  | 
 |  | +                Some(vec![State::Pending, State::Reserved, State::PendingSpent]),
 | 
											
												
													
														|  |                  None,
 |  |                  None,
 | 
											
												
													
														|  |              )
 |  |              )
 | 
											
												
													
														|  |              .await?;
 |  |              .await?;
 | 
											
										
											
												
													
														|  | @@ -153,123 +165,375 @@ impl Wallet {
 | 
											
												
													
														|  |          Ok(balance)
 |  |          Ok(balance)
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    /// Select proofs to send
 |  | 
 | 
											
												
													
														|  | 
 |  | +    /// Select proofs
 | 
											
												
													
														|  |      #[instrument(skip_all)]
 |  |      #[instrument(skip_all)]
 | 
											
												
													
														|  | -    pub async fn select_proofs_to_send(
 |  | 
 | 
											
												
													
														|  | -        &self,
 |  | 
 | 
											
												
													
														|  | 
 |  | +    pub fn select_proofs(
 | 
											
												
													
														|  |          amount: Amount,
 |  |          amount: Amount,
 | 
											
												
													
														|  |          proofs: Proofs,
 |  |          proofs: Proofs,
 | 
											
												
													
														|  | 
 |  | +        active_keyset_ids: &Vec<Id>,
 | 
											
												
													
														|  | 
 |  | +        keyset_fees: &HashMap<Id, u64>,
 | 
											
												
													
														|  |          include_fees: bool,
 |  |          include_fees: bool,
 | 
											
												
													
														|  |      ) -> Result<Proofs, Error> {
 |  |      ) -> Result<Proofs, Error> {
 | 
											
												
													
														|  |          tracing::debug!(
 |  |          tracing::debug!(
 | 
											
												
													
														|  | -            "Selecting proofs to send {} from {}",
 |  | 
 | 
											
												
													
														|  | 
 |  | +            "amount={}, proofs={:?}",
 | 
											
												
													
														|  |              amount,
 |  |              amount,
 | 
											
												
													
														|  | -            proofs.total_amount()?
 |  | 
 | 
											
												
													
														|  | 
 |  | +            proofs.iter().map(|p| p.amount.into()).collect::<Vec<u64>>()
 | 
											
												
													
														|  |          );
 |  |          );
 | 
											
												
													
														|  | 
 |  | +        if amount == Amount::ZERO {
 | 
											
												
													
														|  | 
 |  | +            return Ok(vec![]);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  |          ensure_cdk!(proofs.total_amount()? >= amount, Error::InsufficientFunds);
 |  |          ensure_cdk!(proofs.total_amount()? >= amount, Error::InsufficientFunds);
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        let (mut proofs_larger, mut proofs_smaller): (Proofs, Proofs) =
 |  | 
 | 
											
												
													
														|  | -            proofs.into_iter().partition(|p| p.amount > amount);
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        let next_bigger_proof = proofs_larger.first().cloned();
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // Sort proofs in descending order
 | 
											
												
													
														|  | 
 |  | +        let mut proofs = proofs;
 | 
											
												
													
														|  | 
 |  | +        proofs.sort_by(|a, b| a.cmp(b).reverse());
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        let mut selected_proofs: Proofs = Vec::new();
 |  | 
 | 
											
												
													
														|  | -        let mut remaining_amount = amount;
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // Split the amount into optimal amounts
 | 
											
												
													
														|  | 
 |  | +        let optimal_amounts = amount.split();
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        while remaining_amount > Amount::ZERO {
 |  | 
 | 
											
												
													
														|  | -            proofs_larger.sort();
 |  | 
 | 
											
												
													
														|  | -            // Sort smaller proofs in descending order
 |  | 
 | 
											
												
													
														|  | -            proofs_smaller.sort_by(|a: &Proof, b: &Proof| b.cmp(a));
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -            let selected_proof = if let Some(next_small) = proofs_smaller.clone().first() {
 |  | 
 | 
											
												
													
														|  | -                next_small.clone()
 |  | 
 | 
											
												
													
														|  | -            } else if let Some(next_bigger) = proofs_larger.first() {
 |  | 
 | 
											
												
													
														|  | -                next_bigger.clone()
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // Track selected proofs and remaining amounts (include all inactive proofs first)
 | 
											
												
													
														|  | 
 |  | +        let mut selected_proofs: HashSet<Proof> = proofs
 | 
											
												
													
														|  | 
 |  | +            .iter()
 | 
											
												
													
														|  | 
 |  | +            .filter(|p| !p.is_active(active_keyset_ids))
 | 
											
												
													
														|  | 
 |  | +            .cloned()
 | 
											
												
													
														|  | 
 |  | +            .collect();
 | 
											
												
													
														|  | 
 |  | +        if selected_proofs.total_amount()? >= amount {
 | 
											
												
													
														|  | 
 |  | +            tracing::debug!("All inactive proofs are sufficient");
 | 
											
												
													
														|  | 
 |  | +            return Ok(selected_proofs.into_iter().collect());
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        let mut remaining_amounts: Vec<Amount> = Vec::new();
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // Select proof with the exact amount and not already selected
 | 
											
												
													
														|  | 
 |  | +        let mut select_proof = |proofs: &Proofs, amount: Amount, exact: bool| -> bool {
 | 
											
												
													
														|  | 
 |  | +            let mut last_proof = None;
 | 
											
												
													
														|  | 
 |  | +            for proof in proofs.iter() {
 | 
											
												
													
														|  | 
 |  | +                if !selected_proofs.contains(proof) {
 | 
											
												
													
														|  | 
 |  | +                    if proof.amount == amount {
 | 
											
												
													
														|  | 
 |  | +                        selected_proofs.insert(proof.clone());
 | 
											
												
													
														|  | 
 |  | +                        return true;
 | 
											
												
													
														|  | 
 |  | +                    } else if !exact && proof.amount > amount {
 | 
											
												
													
														|  | 
 |  | +                        last_proof = Some(proof.clone());
 | 
											
												
													
														|  | 
 |  | +                    } else if proof.amount < amount {
 | 
											
												
													
														|  | 
 |  | +                        break;
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            if let Some(proof) = last_proof {
 | 
											
												
													
														|  | 
 |  | +                selected_proofs.insert(proof);
 | 
											
												
													
														|  | 
 |  | +                true
 | 
											
												
													
														|  |              } else {
 |  |              } else {
 | 
											
												
													
														|  | -                break;
 |  | 
 | 
											
												
													
														|  | -            };
 |  | 
 | 
											
												
													
														|  | 
 |  | +                false
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        };
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            let proof_amount = selected_proof.amount;
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // Select proofs with the optimal amounts
 | 
											
												
													
														|  | 
 |  | +        for optimal_amount in optimal_amounts {
 | 
											
												
													
														|  | 
 |  | +            if !select_proof(&proofs, optimal_amount, true) {
 | 
											
												
													
														|  | 
 |  | +                // Add the remaining amount to the remaining amounts because proof with the optimal amount was not found
 | 
											
												
													
														|  | 
 |  | +                remaining_amounts.push(optimal_amount);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            selected_proofs.push(selected_proof);
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // If all the optimal amounts are selected, return the selected proofs
 | 
											
												
													
														|  | 
 |  | +        if remaining_amounts.is_empty() {
 | 
											
												
													
														|  | 
 |  | +            tracing::debug!("All optimal amounts are selected");
 | 
											
												
													
														|  | 
 |  | +            if include_fees {
 | 
											
												
													
														|  | 
 |  | +                return Self::include_fees(
 | 
											
												
													
														|  | 
 |  | +                    amount,
 | 
											
												
													
														|  | 
 |  | +                    proofs,
 | 
											
												
													
														|  | 
 |  | +                    selected_proofs.into_iter().collect(),
 | 
											
												
													
														|  | 
 |  | +                    active_keyset_ids,
 | 
											
												
													
														|  | 
 |  | +                    keyset_fees,
 | 
											
												
													
														|  | 
 |  | +                );
 | 
											
												
													
														|  | 
 |  | +            } else {
 | 
											
												
													
														|  | 
 |  | +                return Ok(selected_proofs.into_iter().collect());
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            let fees = match include_fees {
 |  | 
 | 
											
												
													
														|  | -                true => self.get_proofs_fee(&selected_proofs).await?,
 |  | 
 | 
											
												
													
														|  | -                false => Amount::ZERO,
 |  | 
 | 
											
												
													
														|  | -            };
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // Select proofs with the remaining amounts by checking for 2 of the half amount, 4 of the quarter amount, etc.
 | 
											
												
													
														|  | 
 |  | +        tracing::debug!("Selecting proofs with the remaining amounts");
 | 
											
												
													
														|  | 
 |  | +        for remaining_amount in remaining_amounts {
 | 
											
												
													
														|  | 
 |  | +            // Number of proofs to select
 | 
											
												
													
														|  | 
 |  | +            let mut n = 2;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            let mut target_amount = remaining_amount;
 | 
											
												
													
														|  | 
 |  | +            let mut found = false;
 | 
											
												
													
														|  | 
 |  | +            while let Some(curr_amount) = target_amount.checked_div(Amount::from(2)) {
 | 
											
												
													
														|  | 
 |  | +                if curr_amount == Amount::ZERO {
 | 
											
												
													
														|  | 
 |  | +                    break;
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +                // Select proofs with the current amount
 | 
											
												
													
														|  | 
 |  | +                let mut count = 0;
 | 
											
												
													
														|  | 
 |  | +                for _ in 0..n {
 | 
											
												
													
														|  | 
 |  | +                    if select_proof(&proofs, curr_amount, true) {
 | 
											
												
													
														|  | 
 |  | +                        count += 1;
 | 
											
												
													
														|  | 
 |  | +                    } else {
 | 
											
												
													
														|  | 
 |  | +                        break;
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +                n -= count;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +                // All proofs with the current amount are selected
 | 
											
												
													
														|  | 
 |  | +                if n == 0 {
 | 
											
												
													
														|  | 
 |  | +                    found = true;
 | 
											
												
													
														|  | 
 |  | +                    break;
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +                // Try to find double the number of the next amount
 | 
											
												
													
														|  | 
 |  | +                n *= 2;
 | 
											
												
													
														|  | 
 |  | +                target_amount = curr_amount;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            if proof_amount >= remaining_amount + fees {
 |  | 
 | 
											
												
													
														|  | -                remaining_amount = Amount::ZERO;
 |  | 
 | 
											
												
													
														|  | -                break;
 |  | 
 | 
											
												
													
														|  | 
 |  | +            // Find closest amount over the remaining amount
 | 
											
												
													
														|  | 
 |  | +            if !found {
 | 
											
												
													
														|  | 
 |  | +                select_proof(&proofs, remaining_amount, false);
 | 
											
												
													
														|  |              }
 |  |              }
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            remaining_amount = amount.checked_add(fees).ok_or(Error::AmountOverflow)?
 |  | 
 | 
											
												
													
														|  | -                - selected_proofs.total_amount()?;
 |  | 
 | 
											
												
													
														|  | -            (proofs_larger, proofs_smaller) = proofs_smaller
 |  | 
 | 
											
												
													
														|  | -                .into_iter()
 |  | 
 | 
											
												
													
														|  | -                .skip(1)
 |  | 
 | 
											
												
													
														|  | -                .partition(|p| p.amount > remaining_amount);
 |  | 
 | 
											
												
													
														|  | 
 |  | +        // Check if the selected proofs total amount is equal to the amount else filter out unnecessary proofs
 | 
											
												
													
														|  | 
 |  | +        let mut selected_proofs = selected_proofs.into_iter().collect::<Vec<_>>();
 | 
											
												
													
														|  | 
 |  | +        let total_amount = selected_proofs.total_amount()?;
 | 
											
												
													
														|  | 
 |  | +        if total_amount != amount && selected_proofs.len() > 1 {
 | 
											
												
													
														|  | 
 |  | +            selected_proofs.sort_by(|a, b| a.cmp(b).reverse());
 | 
											
												
													
														|  | 
 |  | +            selected_proofs = Self::select_least_amount_over(selected_proofs, amount)?;
 | 
											
												
													
														|  |          }
 |  |          }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        if remaining_amount > Amount::ZERO {
 |  | 
 | 
											
												
													
														|  | -            if let Some(next_bigger) = next_bigger_proof {
 |  | 
 | 
											
												
													
														|  | -                return Ok(vec![next_bigger.clone()]);
 |  | 
 | 
											
												
													
														|  | -            }
 |  | 
 | 
											
												
													
														|  | 
 |  | +        if include_fees {
 | 
											
												
													
														|  | 
 |  | +            return Self::include_fees(
 | 
											
												
													
														|  | 
 |  | +                amount,
 | 
											
												
													
														|  | 
 |  | +                proofs,
 | 
											
												
													
														|  | 
 |  | +                selected_proofs,
 | 
											
												
													
														|  | 
 |  | +                active_keyset_ids,
 | 
											
												
													
														|  | 
 |  | +                keyset_fees,
 | 
											
												
													
														|  | 
 |  | +            );
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Ok(selected_proofs)
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +    fn select_least_amount_over(proofs: Proofs, amount: Amount) -> Result<Vec<Proof>, Error> {
 | 
											
												
													
														|  | 
 |  | +        let total_amount = proofs.total_amount()?;
 | 
											
												
													
														|  | 
 |  | +        if total_amount < amount {
 | 
											
												
													
														|  |              return Err(Error::InsufficientFunds);
 |  |              return Err(Error::InsufficientFunds);
 | 
											
												
													
														|  |          }
 |  |          }
 | 
											
												
													
														|  | 
 |  | +        if proofs.len() == 1 {
 | 
											
												
													
														|  | 
 |  | +            return Ok(proofs);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        Ok(selected_proofs)
 |  | 
 | 
											
												
													
														|  | 
 |  | +        for i in 1..proofs.len() {
 | 
											
												
													
														|  | 
 |  | +            let (left, right) = proofs.split_at(i);
 | 
											
												
													
														|  | 
 |  | +            let left = left.to_vec();
 | 
											
												
													
														|  | 
 |  | +            let right = right.to_vec();
 | 
											
												
													
														|  | 
 |  | +            let left_amount = left.total_amount()?;
 | 
											
												
													
														|  | 
 |  | +            let right_amount = right.total_amount()?;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            if left_amount >= amount && right_amount >= amount {
 | 
											
												
													
														|  | 
 |  | +                match (
 | 
											
												
													
														|  | 
 |  | +                    Self::select_least_amount_over(left, amount),
 | 
											
												
													
														|  | 
 |  | +                    Self::select_least_amount_over(right, amount),
 | 
											
												
													
														|  | 
 |  | +                ) {
 | 
											
												
													
														|  | 
 |  | +                    (Ok(left_proofs), Ok(right_proofs)) => {
 | 
											
												
													
														|  | 
 |  | +                        let left_total_amount = left_proofs.total_amount()?;
 | 
											
												
													
														|  | 
 |  | +                        let right_total_amount = right_proofs.total_amount()?;
 | 
											
												
													
														|  | 
 |  | +                        if left_total_amount < right_total_amount {
 | 
											
												
													
														|  | 
 |  | +                            return Ok(left_proofs);
 | 
											
												
													
														|  | 
 |  | +                        } else {
 | 
											
												
													
														|  | 
 |  | +                            return Ok(right_proofs);
 | 
											
												
													
														|  | 
 |  | +                        }
 | 
											
												
													
														|  | 
 |  | +                    }
 | 
											
												
													
														|  | 
 |  | +                    (Ok(left_proofs), Err(_)) => return Ok(left_proofs),
 | 
											
												
													
														|  | 
 |  | +                    (Err(_), Ok(right_proofs)) => return Ok(right_proofs),
 | 
											
												
													
														|  | 
 |  | +                    (Err(_), Err(_)) => return Err(Error::InsufficientFunds),
 | 
											
												
													
														|  | 
 |  | +                }
 | 
											
												
													
														|  | 
 |  | +            } else if left_amount >= amount {
 | 
											
												
													
														|  | 
 |  | +                return Self::select_least_amount_over(left, amount);
 | 
											
												
													
														|  | 
 |  | +            } else if right_amount >= amount {
 | 
											
												
													
														|  | 
 |  | +                return Self::select_least_amount_over(right, amount);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        Ok(proofs)
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    /// Select proofs to send
 |  | 
 | 
											
												
													
														|  | -    #[instrument(skip_all)]
 |  | 
 | 
											
												
													
														|  | -    pub async fn select_proofs_to_swap(
 |  | 
 | 
											
												
													
														|  | -        &self,
 |  | 
 | 
											
												
													
														|  | 
 |  | +    fn include_fees(
 | 
											
												
													
														|  |          amount: Amount,
 |  |          amount: Amount,
 | 
											
												
													
														|  |          proofs: Proofs,
 |  |          proofs: Proofs,
 | 
											
												
													
														|  | 
 |  | +        mut selected_proofs: Proofs,
 | 
											
												
													
														|  | 
 |  | +        active_keyset_ids: &Vec<Id>,
 | 
											
												
													
														|  | 
 |  | +        keyset_fees: &HashMap<Id, u64>,
 | 
											
												
													
														|  |      ) -> Result<Proofs, Error> {
 |  |      ) -> Result<Proofs, Error> {
 | 
											
												
													
														|  | 
 |  | +        tracing::debug!("Including fees");
 | 
											
												
													
														|  | 
 |  | +        let fee =
 | 
											
												
													
														|  | 
 |  | +            calculate_fee(&selected_proofs.count_by_keyset(), keyset_fees).unwrap_or_default();
 | 
											
												
													
														|  | 
 |  | +        let net_amount = selected_proofs.total_amount()? - fee;
 | 
											
												
													
														|  |          tracing::debug!(
 |  |          tracing::debug!(
 | 
											
												
													
														|  | -            "Selecting proofs to swap {} from {}",
 |  | 
 | 
											
												
													
														|  | -            amount,
 |  | 
 | 
											
												
													
														|  | -            proofs.total_amount()?
 |  | 
 | 
											
												
													
														|  | 
 |  | +            "Net amount={}, fee={}, total amount={}",
 | 
											
												
													
														|  | 
 |  | +            net_amount,
 | 
											
												
													
														|  | 
 |  | +            fee,
 | 
											
												
													
														|  | 
 |  | +            selected_proofs.total_amount()?
 | 
											
												
													
														|  |          );
 |  |          );
 | 
											
												
													
														|  | -        let active_keyset_id = self.get_active_mint_keyset().await?.id;
 |  | 
 | 
											
												
													
														|  | 
 |  | +        if net_amount >= amount {
 | 
											
												
													
														|  | 
 |  | +            tracing::debug!(
 | 
											
												
													
														|  | 
 |  | +                "Selected proofs: {:?}",
 | 
											
												
													
														|  | 
 |  | +                selected_proofs
 | 
											
												
													
														|  | 
 |  | +                    .iter()
 | 
											
												
													
														|  | 
 |  | +                    .map(|p| p.amount.into())
 | 
											
												
													
														|  | 
 |  | +                    .collect::<Vec<u64>>(),
 | 
											
												
													
														|  | 
 |  | +            );
 | 
											
												
													
														|  | 
 |  | +            return Ok(selected_proofs);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        let (mut active_proofs, mut inactive_proofs): (Proofs, Proofs) = proofs
 |  | 
 | 
											
												
													
														|  | 
 |  | +        tracing::debug!("Net amount is less than the required amount");
 | 
											
												
													
														|  | 
 |  | +        let remaining_amount = amount - net_amount;
 | 
											
												
													
														|  | 
 |  | +        let remaining_proofs = proofs
 | 
											
												
													
														|  |              .into_iter()
 |  |              .into_iter()
 | 
											
												
													
														|  | -            .partition(|p| p.keyset_id == active_keyset_id);
 |  | 
 | 
											
												
													
														|  | 
 |  | +            .filter(|p| !selected_proofs.contains(p))
 | 
											
												
													
														|  | 
 |  | +            .collect::<Proofs>();
 | 
											
												
													
														|  | 
 |  | +        selected_proofs.extend(Wallet::select_proofs(
 | 
											
												
													
														|  | 
 |  | +            remaining_amount,
 | 
											
												
													
														|  | 
 |  | +            remaining_proofs,
 | 
											
												
													
														|  | 
 |  | +            active_keyset_ids,
 | 
											
												
													
														|  | 
 |  | +            &HashMap::new(), // Fees are already calculated
 | 
											
												
													
														|  | 
 |  | +            false,
 | 
											
												
													
														|  | 
 |  | +        )?);
 | 
											
												
													
														|  | 
 |  | +        tracing::debug!(
 | 
											
												
													
														|  | 
 |  | +            "Selected proofs: {:?}",
 | 
											
												
													
														|  | 
 |  | +            selected_proofs
 | 
											
												
													
														|  | 
 |  | +                .iter()
 | 
											
												
													
														|  | 
 |  | +                .map(|p| p.amount.into())
 | 
											
												
													
														|  | 
 |  | +                .collect::<Vec<u64>>(),
 | 
											
												
													
														|  | 
 |  | +        );
 | 
											
												
													
														|  | 
 |  | +        Ok(selected_proofs)
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +}
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        let mut selected_proofs: Proofs = Vec::new();
 |  | 
 | 
											
												
													
														|  | -        inactive_proofs.sort_by(|a: &Proof, b: &Proof| b.cmp(a));
 |  | 
 | 
											
												
													
														|  | 
 |  | +#[cfg(test)]
 | 
											
												
													
														|  | 
 |  | +mod tests {
 | 
											
												
													
														|  | 
 |  | +    use std::collections::HashMap;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        for inactive_proof in inactive_proofs {
 |  | 
 | 
											
												
													
														|  | -            selected_proofs.push(inactive_proof);
 |  | 
 | 
											
												
													
														|  | -            let selected_total = selected_proofs.total_amount()?;
 |  | 
 | 
											
												
													
														|  | -            let fees = self.get_proofs_fee(&selected_proofs).await?;
 |  | 
 | 
											
												
													
														|  | 
 |  | +    use cdk_common::secret::Secret;
 | 
											
												
													
														|  | 
 |  | +    use cdk_common::{Amount, Id, Proof, PublicKey};
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            if selected_total >= amount + fees {
 |  | 
 | 
											
												
													
														|  | -                return Ok(selected_proofs);
 |  | 
 | 
											
												
													
														|  | -            }
 |  | 
 | 
											
												
													
														|  | -        }
 |  | 
 | 
											
												
													
														|  | 
 |  | +    use crate::Wallet;
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        active_proofs.sort_by(|a: &Proof, b: &Proof| b.cmp(a));
 |  | 
 | 
											
												
													
														|  | 
 |  | +    fn id() -> Id {
 | 
											
												
													
														|  | 
 |  | +        Id::from_bytes(&[0; 8]).unwrap()
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        for active_proof in active_proofs {
 |  | 
 | 
											
												
													
														|  | -            selected_proofs.push(active_proof);
 |  | 
 | 
											
												
													
														|  | -            let selected_total = selected_proofs.total_amount()?;
 |  | 
 | 
											
												
													
														|  | -            let fees = self.get_proofs_fee(&selected_proofs).await?;
 |  | 
 | 
											
												
													
														|  | 
 |  | +    fn proof(amount: u64) -> Proof {
 | 
											
												
													
														|  | 
 |  | +        Proof::new(
 | 
											
												
													
														|  | 
 |  | +            Amount::from(amount),
 | 
											
												
													
														|  | 
 |  | +            id(),
 | 
											
												
													
														|  | 
 |  | +            Secret::generate(),
 | 
											
												
													
														|  | 
 |  | +            PublicKey::from_hex(
 | 
											
												
													
														|  | 
 |  | +                "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
 | 
											
												
													
														|  | 
 |  | +            )
 | 
											
												
													
														|  | 
 |  | +            .unwrap(),
 | 
											
												
													
														|  | 
 |  | +        )
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -            if selected_total >= amount + fees {
 |  | 
 | 
											
												
													
														|  | -                return Ok(selected_proofs);
 |  | 
 | 
											
												
													
														|  | -            }
 |  | 
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_empty() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = vec![];
 | 
											
												
													
														|  | 
 |  | +        let selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(0.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 0);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_insufficient() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = vec![proof(1), proof(2), proof(4)];
 | 
											
												
													
														|  | 
 |  | +        let selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(8.into(), proofs, &vec![id()], &HashMap::new(), false);
 | 
											
												
													
														|  | 
 |  | +        assert!(selected_proofs.is_err());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_exact() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = vec![
 | 
											
												
													
														|  | 
 |  | +            proof(1),
 | 
											
												
													
														|  | 
 |  | +            proof(2),
 | 
											
												
													
														|  | 
 |  | +            proof(4),
 | 
											
												
													
														|  | 
 |  | +            proof(8),
 | 
											
												
													
														|  | 
 |  | +            proof(16),
 | 
											
												
													
														|  | 
 |  | +            proof(32),
 | 
											
												
													
														|  | 
 |  | +            proof(64),
 | 
											
												
													
														|  | 
 |  | +        ];
 | 
											
												
													
														|  | 
 |  | +        let mut selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(77.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
 | 
											
												
													
														|  | 
 |  | +        selected_proofs.sort();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 4);
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[0].amount, 1.into());
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[1].amount, 4.into());
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[2].amount, 8.into());
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[3].amount, 64.into());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_over() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = vec![proof(1), proof(2), proof(4), proof(8), proof(32), proof(64)];
 | 
											
												
													
														|  | 
 |  | +        let selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(31.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 1);
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[0].amount, 32.into());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_smaller_over() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = vec![proof(8), proof(16), proof(32)];
 | 
											
												
													
														|  | 
 |  | +        let selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(23.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 2);
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[0].amount, 16.into());
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[1].amount, 8.into());
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_many_ones() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = (0..1024).into_iter().map(|_| proof(1)).collect::<Vec<_>>();
 | 
											
												
													
														|  | 
 |  | +        let selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(1024.into(), proofs, &vec![id()], &HashMap::new(), false)
 | 
											
												
													
														|  | 
 |  | +                .unwrap();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 1024);
 | 
											
												
													
														|  | 
 |  | +        for i in 0..1024 {
 | 
											
												
													
														|  | 
 |  | +            assert_eq!(selected_proofs[i].amount, 1.into());
 | 
											
												
													
														|  |          }
 |  |          }
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        tracing::debug!(
 |  | 
 | 
											
												
													
														|  | -            "Could not select proofs to swap: total selected: {}",
 |  | 
 | 
											
												
													
														|  | -            selected_proofs.total_amount()?
 |  | 
 | 
											
												
													
														|  | -        );
 |  | 
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_huge_proofs() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = (0..32)
 | 
											
												
													
														|  | 
 |  | +            .flat_map(|i| {
 | 
											
												
													
														|  | 
 |  | +                (0..5)
 | 
											
												
													
														|  | 
 |  | +                    .into_iter()
 | 
											
												
													
														|  | 
 |  | +                    .map(|_| proof(1 << i))
 | 
											
												
													
														|  | 
 |  | +                    .collect::<Vec<_>>()
 | 
											
												
													
														|  | 
 |  | +            })
 | 
											
												
													
														|  | 
 |  | +            .collect::<Vec<_>>();
 | 
											
												
													
														|  | 
 |  | +        let mut selected_proofs = Wallet::select_proofs(
 | 
											
												
													
														|  | 
 |  | +            ((1u64 << 32) - 1).into(),
 | 
											
												
													
														|  | 
 |  | +            proofs,
 | 
											
												
													
														|  | 
 |  | +            &vec![id()],
 | 
											
												
													
														|  | 
 |  | +            &HashMap::new(),
 | 
											
												
													
														|  | 
 |  | +            false,
 | 
											
												
													
														|  | 
 |  | +        )
 | 
											
												
													
														|  | 
 |  | +        .unwrap();
 | 
											
												
													
														|  | 
 |  | +        selected_proofs.sort();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 32);
 | 
											
												
													
														|  | 
 |  | +        for i in 0..32 {
 | 
											
												
													
														|  | 
 |  | +            assert_eq!(selected_proofs[i].amount, (1 << i).into());
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -        Err(Error::InsufficientFunds)
 |  | 
 | 
											
												
													
														|  | 
 |  | +    #[test]
 | 
											
												
													
														|  | 
 |  | +    fn test_select_proofs_with_fees() {
 | 
											
												
													
														|  | 
 |  | +        let proofs = vec![proof(64), proof(4), proof(32)];
 | 
											
												
													
														|  | 
 |  | +        let mut keyset_fees = HashMap::new();
 | 
											
												
													
														|  | 
 |  | +        keyset_fees.insert(id(), 100);
 | 
											
												
													
														|  | 
 |  | +        let selected_proofs =
 | 
											
												
													
														|  | 
 |  | +            Wallet::select_proofs(10.into(), proofs, &vec![id()], &keyset_fees, false).unwrap();
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs.len(), 1);
 | 
											
												
													
														|  | 
 |  | +        assert_eq!(selected_proofs[0].amount, 32.into());
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  }
 |  |  }
 |