Merge pull request #901 from thesimplekid/refresh_keys

feat: refactor wallet keyset management for better clarity
This commit is contained in:
thesimplekid
2025-07-23 20:49:42 +01:00
committed by GitHub
parent 8ab545ae44
commit d2e9f1a626
17 changed files with 236 additions and 162 deletions

View File

@@ -497,6 +497,28 @@ pub struct KeySetInfo {
pub final_expiry: Option<u64>,
}
/// List of [KeySetInfo]
pub type KeySetInfos = Vec<KeySetInfo>;
/// Utility methods for [KeySetInfos]
pub trait KeySetInfosMethods {
/// Filter for active keysets
fn active(&self) -> impl Iterator<Item = &KeySetInfo> + '_;
/// Filter keysets for specific unit
fn unit(&self, unit: CurrencyUnit) -> impl Iterator<Item = &KeySetInfo> + '_;
}
impl KeySetInfosMethods for KeySetInfos {
fn active(&self) -> impl Iterator<Item = &KeySetInfo> + '_ {
self.iter().filter(|k| k.active)
}
fn unit(&self, unit: CurrencyUnit) -> impl Iterator<Item = &KeySetInfo> + '_ {
self.iter().filter(move |k| k.unit == unit)
}
}
fn deserialize_input_fee_ppk<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,

View File

@@ -101,7 +101,7 @@ pub async fn pay_request(
.await?
{
Some(keysets_info) => keysets_info,
None => matching_wallet.get_mint_keysets().await?, // Hit the keysets endpoint if we don't have the keysets for this Mint
None => matching_wallet.load_mint_keysets().await?, // Hit the keysets endpoint if we don't have the keysets for this Mint
};
let proofs = token.proofs(&keysets_info)?;

View File

@@ -277,7 +277,7 @@ async fn test_regtest_bolt12_mint_extra() -> Result<()> {
assert_eq!(state.amount_paid, Amount::ZERO);
assert_eq!(state.amount_issued, Amount::ZERO);
let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
let active_keyset_id = wallet.fetch_active_keyset().await?.id;
let pay_amount_msats = 10_000;

View File

@@ -381,7 +381,7 @@ async fn test_fake_melt_change_in_quote() {
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
let keyset = wallet.get_active_mint_keyset().await.unwrap();
let keyset = wallet.fetch_active_keyset().await.unwrap();
let premint_secrets =
PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default()).unwrap();
@@ -489,7 +489,7 @@ async fn test_fake_mint_without_witness() {
let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let premint_secrets =
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
@@ -529,7 +529,7 @@ async fn test_fake_mint_with_wrong_witness() {
let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let premint_secrets =
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
@@ -573,7 +573,7 @@ async fn test_fake_mint_inflated() {
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let pre_mint =
PreMintSecrets::random(active_keyset_id, 500.into(), &SplitTarget::None).unwrap();
@@ -631,7 +631,7 @@ async fn test_fake_mint_multiple_units() {
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None).unwrap();
@@ -644,7 +644,7 @@ async fn test_fake_mint_multiple_units() {
)
.expect("failed to create new wallet");
let active_keyset_id = wallet_usd.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
let usd_pre_mint =
PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None).unwrap();
@@ -733,7 +733,7 @@ async fn test_fake_mint_multiple_unit_swap() {
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
{
let inputs: Proofs = vec![
@@ -767,7 +767,7 @@ async fn test_fake_mint_multiple_unit_swap() {
}
{
let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await.unwrap().id;
let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
let inputs: Proofs = proofs.into_iter().take(2).collect();
let total_inputs = inputs.total_amount().unwrap();
@@ -883,8 +883,8 @@ async fn test_fake_mint_multiple_unit_melt() {
let input_amount: u64 = inputs.total_amount().unwrap().into();
let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
let usd_pre_mint = PreMintSecrets::random(
usd_active_keyset_id,
@@ -952,7 +952,7 @@ async fn test_fake_mint_input_output_mismatch() {
None,
)
.expect("failed to create new usd wallet");
let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await.unwrap().id;
let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
let inputs = proofs;
@@ -1001,7 +1001,7 @@ async fn test_fake_mint_swap_inflated() {
.mint(&mint_quote.id, SplitTarget::None, None)
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let pre_mint =
PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None).unwrap();
@@ -1045,7 +1045,7 @@ async fn test_fake_mint_swap_spend_after_fail() {
.mint(&mint_quote.id, SplitTarget::None, None)
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let pre_mint =
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None).unwrap();
@@ -1116,7 +1116,7 @@ async fn test_fake_mint_melt_spend_after_fail() {
.mint(&mint_quote.id, SplitTarget::None, None)
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let pre_mint =
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None).unwrap();
@@ -1189,7 +1189,7 @@ async fn test_fake_mint_duplicate_proofs_swap() {
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let inputs = vec![proofs[0].clone(), proofs[0].clone()];

View File

@@ -358,7 +358,7 @@ async fn test_fake_melt_change_in_quote() {
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
let keyset = wallet.get_active_mint_keyset().await.unwrap();
let keyset = wallet.fetch_active_keyset().await.unwrap();
let premint_secrets =
PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default()).unwrap();

View File

@@ -308,7 +308,7 @@ async fn test_cached_mint() {
.await
.unwrap();
let active_keyset_id = wallet.get_active_mint_keyset().await.unwrap().id;
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
let premint_secrets =
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();

View File

@@ -8,6 +8,7 @@ use cdk::nuts::nut00::ProofsMethods;
use cdk::nuts::{CurrencyUnit, MintQuoteState, NotificationPayload};
use cdk::wallet::{Wallet, WalletSubscription};
use cdk::Amount;
use cdk_common::nut02::KeySetInfosMethods;
use cdk_sqlite::wallet::memory;
use rand::random;
@@ -62,9 +63,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Select proofs to send
let amount = Amount::from(64);
let active_keyset_ids = wallet
.get_active_mint_keysets()
.refresh_keysets()
.await?
.into_iter()
.active()
.map(|keyset| keyset.id)
.collect();
let selected =

View File

@@ -3,6 +3,7 @@ use std::sync::Arc;
use cdk_common::database::{self, WalletDatabase};
use cdk_common::mint_url::MintUrl;
use cdk_common::nut02::KeySetInfosMethods;
use cdk_common::{AuthProof, Id, Keys, MintInfo};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
@@ -167,12 +168,12 @@ impl AuthWallet {
self.client.get_mint_info().await.map(Some).or(Ok(None))
}
/// Get keys for mint keyset
/// Fetch keys for mint keyset
///
/// Selected keys from localstore if they are already known
/// If they are not known queries mint for keyset id and stores the [`Keys`]
/// Returns keys from local database if they are already stored.
/// If keys are not found locally, goes online to query the mint for the keyset and stores the [`Keys`] in local database.
#[instrument(skip(self))]
pub async fn get_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Error> {
pub async fn load_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Error> {
let keys = if let Some(keys) = self.localstore.get_keys(&keyset_id).await? {
keys
} else {
@@ -188,62 +189,88 @@ impl AuthWallet {
Ok(keys)
}
/// Get active keyset for mint
/// Get blind auth keysets from local database or go online if missing
///
/// Queries mint for current keysets then gets [`Keys`] for any unknown
/// keysets
/// First checks the local database for cached blind auth keysets. If keysets are not found locally,
/// goes online to refresh keysets from the mint and updates the local database.
/// This is the main method for getting auth keysets in operations that can work offline
/// but will fall back to online if needed.
#[instrument(skip(self))]
pub async fn get_active_mint_blind_auth_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
let keysets = self.client.get_mint_blind_auth_keysets().await?;
let keysets = keysets.keysets;
self.localstore
.add_mint_keysets(self.mint_url.clone(), keysets.clone())
.await?;
let active_keysets = keysets
.clone()
.into_iter()
.filter(|k| k.unit == CurrencyUnit::Auth)
.collect::<Vec<KeySetInfo>>();
pub async fn load_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
match self
.localstore
.get_mint_keysets(self.mint_url.clone())
.await?
{
Some(known_keysets) => {
let unknown_keysets: Vec<&KeySetInfo> = keysets
.iter()
.filter(|k| known_keysets.contains(k))
.collect();
for keyset in unknown_keysets {
self.get_keyset_keys(keyset.id).await?;
Some(keysets_info) => {
let auth_keysets: Vec<KeySetInfo> =
keysets_info.unit(CurrencyUnit::Sat).cloned().collect();
if auth_keysets.is_empty() {
// If we don't have any auth keysets, fetch them from the mint
let keysets = self.refresh_keysets().await?;
Ok(keysets)
} else {
Ok(auth_keysets)
}
}
None => {
for keyset in keysets {
self.get_keyset_keys(keyset.id).await?;
// If we don't have any keysets, fetch them from the mint
let keysets = self.refresh_keysets().await?;
Ok(keysets)
}
}
}
Ok(active_keysets)
}
/// Get active keyset for mint
/// Refresh blind auth keysets by fetching the latest from mint - always goes online
///
/// Queries mint for current keysets then gets [`Keys`] for any unknown
/// keysets
/// This method always goes online to fetch the latest blind auth keyset information from the mint.
/// It updates the local database with the fetched keysets and ensures we have keys for all keysets.
/// Returns only the keysets with Auth currency unit. This is used when operations need the most
/// up-to-date keyset information and are willing to go online.
#[instrument(skip(self))]
pub async fn get_active_mint_blind_auth_keyset(&self) -> Result<KeySetInfo, Error> {
let active_keysets = self.get_active_mint_blind_auth_keysets().await?;
pub async fn refresh_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
let keysets_response = self.client.get_mint_blind_auth_keysets().await?;
let keysets = keysets_response.keysets;
let keyset = active_keysets.first().ok_or(Error::NoActiveKeyset)?;
// Update local store with keysets
self.localstore
.add_mint_keysets(self.mint_url.clone(), keysets.clone())
.await?;
// Filter for auth keysets
let auth_keysets = keysets
.clone()
.into_iter()
.filter(|k| k.unit == CurrencyUnit::Auth)
.collect::<Vec<KeySetInfo>>();
// Ensure we have keys for all auth keysets
for keyset in &auth_keysets {
if self.localstore.get_keys(&keyset.id).await?.is_none() {
tracing::debug!("Fetching missing keys for auth keyset {}", keyset.id);
self.load_keyset_keys(keyset.id).await?;
}
}
Ok(auth_keysets)
}
/// Get the first active blind auth keyset - always goes online
///
/// This method always goes online to refresh keysets from the mint and then returns
/// the first active keyset found. Use this when you need the most up-to-date
/// keyset information for blind auth operations.
#[instrument(skip(self))]
pub async fn fetch_active_keyset(&self) -> Result<KeySetInfo, Error> {
let auth_keysets = self.refresh_keysets().await?;
let keyset = auth_keysets.first().ok_or(Error::NoActiveKeyset)?;
Ok(keyset.clone())
}
/// Get unspent proofs for mint
/// Get unspent auth proofs from local database only - offline operation
///
/// Returns auth proofs from the local database that are in the Unspent state.
/// This is an offline operation that does not contact the mint.
#[instrument(skip(self))]
pub async fn get_unspent_auth_proofs(&self) -> Result<Vec<AuthProof>, Error> {
Ok(self
@@ -334,9 +361,6 @@ impl AuthWallet {
let auth_token = self.client.get_auth_token().await?;
let active_keyset_id = self.get_active_mint_blind_auth_keysets().await?;
tracing::debug!("Active ketset: {:?}", active_keyset_id);
match &auth_token {
AuthToken::ClearAuth(cat) => {
if cat.is_empty() {
@@ -369,7 +393,7 @@ impl AuthWallet {
}
}
let active_keyset_id = self.get_active_mint_blind_auth_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
let premint_secrets =
PreMintSecrets::random(active_keyset_id, amount, &SplitTarget::Value(1.into()))?;
@@ -380,13 +404,13 @@ impl AuthWallet {
let mint_res = self.client.post_mint_blind_auth(request).await?;
let keys = self.get_keyset_keys(active_keyset_id).await?;
let keys = self.load_keyset_keys(active_keyset_id).await?;
// Verify the signature DLEQ is valid
{
assert!(mint_res.signatures.len() == premint_secrets.secrets.len());
for (sig, premint) in mint_res.signatures.iter().zip(&premint_secrets.secrets) {
let keys = self.get_keyset_keys(sig.keyset_id).await?;
let keys = self.load_keyset_keys(sig.keyset_id).await?;
let key = keys.amount_key(sig.amount).ok_or(Error::AmountKey)?;
match sig.verify_dleq(key, premint.blinded_message.blinded_secret) {
Ok(_) => (),

View File

@@ -227,7 +227,7 @@ impl Wallet {
tracing::warn!("Attempting to mint with expired quote.");
}
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
let count = self
.localstore
@@ -264,12 +264,12 @@ impl Wallet {
let mint_res = self.client.post_mint(request).await?;
let keys = self.get_keyset_keys(active_keyset_id).await?;
let keys = self.fetch_keyset_keys(active_keyset_id).await?;
// Verify the signature DLEQ is valid
{
for (sig, premint) in mint_res.signatures.iter().zip(&premint_secrets.secrets) {
let keys = self.get_keyset_keys(sig.keyset_id).await?;
let keys = self.fetch_keyset_keys(sig.keyset_id).await?;
let key = keys.amount_key(sig.amount).ok_or(Error::AmountKey)?;
match sig.verify_dleq(key, premint.blinded_message.blinded_secret) {
Ok(_) | Err(nut12::Error::MissingDleqProof) => (),

View File

@@ -105,7 +105,7 @@ impl Wallet {
return Err(Error::UnknownQuote);
};
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
let count = self
.localstore
@@ -161,12 +161,12 @@ impl Wallet {
let mint_res = self.client.post_mint(request).await?;
let keys = self.get_keyset_keys(active_keyset_id).await?;
let keys = self.fetch_keyset_keys(active_keyset_id).await?;
// Verify the signature DLEQ is valid
{
for (sig, premint) in mint_res.signatures.iter().zip(&premint_secrets.secrets) {
let keys = self.get_keyset_keys(sig.keyset_id).await?;
let keys = self.fetch_keyset_keys(sig.keyset_id).await?;
let key = keys.amount_key(sig.amount).ok_or(Error::AmountKey)?;
match sig.verify_dleq(key, premint.blinded_message.blinded_secret) {
Ok(_) | Err(nut12::Error::MissingDleqProof) => (),

View File

@@ -1,17 +1,18 @@
use std::collections::HashMap;
use cdk_common::nut02::{KeySetInfos, KeySetInfosMethods};
use tracing::instrument;
use crate::nuts::{Id, KeySetInfo, Keys};
use crate::{Error, Wallet};
impl Wallet {
/// Get keys for mint keyset
/// Fetch keys for mint keyset
///
/// Selected keys from localstore if they are already known
/// If they are not known queries mint for keyset id and stores the [`Keys`]
/// Returns keys from local database if they are already stored.
/// If keys are not found locally, goes online to query the mint for the keyset and stores the [`Keys`] in local database.
#[instrument(skip(self))]
pub async fn get_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Error> {
pub async fn fetch_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Error> {
let keys = if let Some(keys) = self.localstore.get_keys(&keyset_id).await? {
keys
} else {
@@ -27,10 +28,12 @@ impl Wallet {
Ok(keys)
}
/// Get keysets from DB or fetch them
/// Get keysets from local database or go online if missing
///
/// Checks the database for keysets and queries the Mint if
/// it can't find any.
/// First checks the local database for cached keysets. If keysets are not found locally,
/// goes online to refresh keysets from the mint and updates the local database.
/// This is the main method for getting keysets in token operations that can work offline
/// but will fall back to online if needed.
#[instrument(skip(self))]
pub async fn load_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
match self
@@ -39,86 +42,105 @@ impl Wallet {
.await?
{
Some(keysets_info) => Ok(keysets_info),
None => self.get_mint_keysets().await, // Hit the keysets endpoint if we don't have the keysets for this Mint
None => {
// If we don't have any keysets, fetch them from the mint
let keysets = self.refresh_keysets().await?;
Ok(keysets)
}
}
}
/// Get keysets for wallet's mint
/// Get keysets from local database only - pure offline operation
///
/// Queries mint for all keysets
/// Only checks the local database for cached keysets. If keysets are not found locally,
/// returns an error without going online. This is used for operations that must remain
/// offline and rely on previously cached keyset data.
#[instrument(skip(self))]
pub async fn get_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
let keysets = self.client.get_mint_keysets().await?;
self.localstore
.add_mint_keysets(self.mint_url.clone(), keysets.keysets.clone())
.await?;
Ok(keysets.keysets)
}
/// Get active keyset for mint
///
/// Queries mint for current keysets then gets [`Keys`] for any unknown
/// keysets
#[instrument(skip(self))]
pub async fn get_active_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
let keysets = self.client.get_mint_keysets().await?;
let keysets = keysets.keysets;
self.localstore
.add_mint_keysets(self.mint_url.clone(), keysets.clone())
.await?;
let active_keysets = keysets
.clone()
.into_iter()
.filter(|k| k.active && k.unit == self.unit)
.collect::<Vec<KeySetInfo>>();
match self
.localstore
.get_mint_keysets(self.mint_url.clone())
.await?
{
Some(known_keysets) => {
let unknown_keysets: Vec<&KeySetInfo> = keysets
.iter()
.filter(|k| known_keysets.contains(k))
.collect();
for keyset in unknown_keysets {
self.get_keyset_keys(keyset.id).await?;
}
}
None => {
for keyset in keysets {
self.get_keyset_keys(keyset.id).await?;
}
Some(keysets_info) => Ok(keysets_info),
None => Err(Error::UnknownKeySet),
}
}
Ok(active_keysets)
}
/// Get active keyset for mint with the lowest fees
/// Refresh keysets by fetching the latest from mint - always goes online
///
/// Queries mint for current keysets then gets [`Keys`] for any unknown
/// keysets
/// This method always goes online to fetch the latest keyset information from the mint.
/// It updates the local database with the fetched keysets and ensures we have keys
/// for all active keysets. This is used when operations need the most up-to-date
/// keyset information and are willing to go online.
#[instrument(skip(self))]
pub async fn get_active_mint_keyset(&self) -> Result<KeySetInfo, Error> {
// Important
pub async fn refresh_keysets(&self) -> Result<KeySetInfos, Error> {
tracing::debug!("Refreshing keysets and ensuring we have keys");
let _ = self.get_mint_info().await?;
let active_keysets = self.get_active_mint_keysets().await?;
let keyset_with_lowest_fee = active_keysets
.into_iter()
.min_by_key(|key| key.input_fee_ppk)
.ok_or(Error::NoActiveKeyset)?;
Ok(keyset_with_lowest_fee)
// Fetch all current keysets from mint
let keysets_response = self.client.get_mint_keysets().await?;
let all_keysets = keysets_response.keysets;
// Update local storage with keyset info
self.localstore
.add_mint_keysets(self.mint_url.clone(), all_keysets.clone())
.await?;
// Filter for active keysets matching our unit
let keysets: KeySetInfos = all_keysets.unit(self.unit.clone()).cloned().collect();
// Ensure we have keys for all active keysets
for keyset in &keysets {
if self.localstore.get_keys(&keyset.id).await?.is_none() {
tracing::debug!("Fetching missing keys for keyset {}", keyset.id);
self.fetch_keyset_keys(keyset.id).await?;
}
}
/// Get keyset fees for mint
Ok(keysets)
}
/// Get the active keyset with the lowest fees - always goes online
///
/// This method always goes online to refresh keysets from the mint and then returns
/// the active keyset with the minimum input fees. Use this when you need the most
/// up-to-date keyset information for operations.
#[instrument(skip(self))]
pub async fn fetch_active_keyset(&self) -> Result<KeySetInfo, Error> {
self.refresh_keysets()
.await?
.active()
.min_by_key(|k| k.input_fee_ppk)
.cloned()
.ok_or(Error::NoActiveKeyset)
}
/// Get the active keyset with the lowest fees from local database only - offline operation
///
/// Returns the active keyset with minimum input fees from cached keysets in the local database.
/// This is an offline operation that does not contact the mint. If no keysets are found locally,
/// returns an error. Use this for offline operations or when you want to avoid network calls.
#[instrument(skip(self))]
pub async fn get_active_keyset(&self) -> Result<KeySetInfo, Error> {
match self
.localstore
.get_mint_keysets(self.mint_url.clone())
.await?
{
Some(keysets_info) => keysets_info
.into_iter()
.min_by_key(|k| k.input_fee_ppk)
.ok_or(Error::NoActiveKeyset),
None => Err(Error::UnknownKeySet),
}
}
/// Get keyset fees for mint from local database only - offline operation
///
/// Returns a HashMap of keyset IDs to their input fee rates (per-proof-per-thousand)
/// from cached keysets in the local database. This is an offline operation that does
/// not contact the mint. If no keysets are found locally, returns an error.
pub async fn get_keyset_fees(&self) -> Result<HashMap<Id, u64>, Error> {
let keysets = self
.localstore
@@ -134,7 +156,11 @@ impl Wallet {
Ok(fees)
}
/// Get keyset fees for mint by keyset id
/// Get keyset fees for mint by keyset id from local database only - offline operation
///
/// Returns the input fee rate (per-proof-per-thousand) for a specific keyset ID from
/// cached keysets in the local database. This is an offline operation that does not
/// contact the mint. If the keyset is not found locally, returns an error.
pub async fn get_keyset_fees_by_id(&self, keyset_id: Id) -> Result<u64, Error> {
self.get_keyset_fees()
.await?

View File

@@ -146,7 +146,7 @@ impl Wallet {
.update_proofs_state(ys, State::Pending)
.await?;
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
let count = self
.localstore
@@ -317,7 +317,7 @@ impl Wallet {
let available_proofs = self.get_unspent_proofs().await?;
let active_keyset_ids = self
.get_active_mint_keysets()
.refresh_keysets()
.await?
.into_iter()
.map(|k| k.id)

View File

@@ -378,12 +378,12 @@ impl Wallet {
self.get_mint_info().await?;
}
let keysets = self.get_mint_keysets().await?;
let keysets = self.load_mint_keysets().await?;
let mut restored_value = Amount::ZERO;
for keyset in keysets {
let keys = self.get_keyset_keys(keyset.id).await?;
let keys = self.fetch_keyset_keys(keyset.id).await?;
let mut empty_batch = 0;
let mut start_counter = 0;
@@ -632,7 +632,7 @@ impl Wallet {
let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
Some(keys) => keys.amount_key(proof.amount),
None => {
let keys = self.get_keyset_keys(proof.keyset_id).await?;
let keys = self.fetch_keyset_keys(proof.keyset_id).await?;
let key = keys.amount_key(proof.amount);
keys_cache.insert(proof.keyset_id, keys);

View File

@@ -293,7 +293,7 @@ impl MultiMintWallet {
{
Some(keysets_info) => keysets_info,
// Hit the keysets endpoint if we don't have the keysets for this Mint
None => wallet.get_mint_keysets().await?,
None => wallet.load_mint_keysets().await?,
};
let proofs = token_data.proofs(&keysets_info)?;

View File

@@ -38,11 +38,11 @@ impl Wallet {
self.get_mint_info().await?;
}
let _ = self.get_active_mint_keyset().await?;
let _ = self.fetch_active_keyset().await?;
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
let keys = self.get_keyset_keys(active_keyset_id).await?;
let keys = self.fetch_keyset_keys(active_keyset_id).await?;
let mut proofs = proofs;
@@ -70,7 +70,7 @@ impl Wallet {
for proof in &mut proofs {
// Verify that proof DLEQ is valid
if proof.dleq.is_some() {
let keys = self.get_keyset_keys(proof.keyset_id).await?;
let keys = self.fetch_keyset_keys(proof.keyset_id).await?;
let key = keys.amount_key(proof.amount).ok_or(Error::AmountKey)?;
proof.verify_dleq(key)?;
}

View File

@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::fmt::Debug;
use cdk_common::nut02::KeySetInfosMethods;
use cdk_common::util::unix_time;
use cdk_common::wallet::{Transaction, TransactionDirection};
use tracing::instrument;
@@ -32,11 +33,8 @@ impl Wallet {
// If online send check mint for current keysets fees
if opts.send_kind.is_online() {
if let Err(e) = self.get_active_mint_keyset().await {
tracing::error!(
"Error fetching active mint keyset: {:?}. Using stored keysets",
e
);
if let Err(e) = self.refresh_keysets().await {
tracing::error!("Error refreshing keysets: {:?}. Using stored keysets", e);
}
}
@@ -78,11 +76,12 @@ impl Wallet {
// Select proofs
let active_keyset_ids = self
.get_active_mint_keysets()
.get_mint_keysets()
.await?
.into_iter()
.active()
.map(|k| k.id)
.collect();
let selected_proofs = Wallet::select_proofs(
amount,
available_proofs,
@@ -131,7 +130,7 @@ impl Wallet {
) -> Result<PreparedSend, Error> {
// Split amount with fee if necessary
let (send_amounts, send_fee) = if opts.include_fee {
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.get_active_keyset().await?.id;
let keyset_fee_ppk = self.get_keyset_fees_by_id(active_keyset_id).await?;
tracing::debug!("Keyset fee per proof: {:?}", keyset_fee_ppk);
let send_split = amount.split_with_fee(keyset_fee_ppk)?;
@@ -209,7 +208,7 @@ impl Wallet {
let mut proofs_to_send = send.proofs_to_send;
// Get active keyset ID
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
tracing::debug!("Active keyset ID: {:?}", active_keyset_id);
// Get keyset fees

View File

@@ -1,3 +1,4 @@
use cdk_common::nut02::KeySetInfosMethods;
use tracing::instrument;
use crate::amount::SplitTarget;
@@ -167,11 +168,12 @@ impl Wallet {
ensure_cdk!(proofs_sum >= amount, Error::InsufficientFunds);
let active_keyset_ids = self
.get_active_mint_keysets()
.refresh_keysets()
.await?
.into_iter()
.active()
.map(|k| k.id)
.collect();
let keyset_fees = self.get_keyset_fees().await?;
let proofs = Wallet::select_proofs(
amount,
@@ -203,7 +205,7 @@ impl Wallet {
include_fees: bool,
) -> Result<PreSwap, Error> {
tracing::info!("Creating swap");
let active_keyset_id = self.get_active_mint_keyset().await?.id;
let active_keyset_id = self.fetch_active_keyset().await?.id;
// Desired amount is either amount passed or value of all proof
let proofs_total = proofs.total_amount()?;