|
@@ -1,17 +1,18 @@
|
|
|
use std::collections::HashMap;
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
+use cdk_common::nut02::{KeySetInfos, KeySetInfosMethods};
|
|
|
use tracing::instrument;
|
|
use tracing::instrument;
|
|
|
|
|
|
|
|
use crate::nuts::{Id, KeySetInfo, Keys};
|
|
use crate::nuts::{Id, KeySetInfo, Keys};
|
|
|
use crate::{Error, Wallet};
|
|
use crate::{Error, Wallet};
|
|
|
|
|
|
|
|
impl 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))]
|
|
#[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? {
|
|
let keys = if let Some(keys) = self.localstore.get_keys(&keyset_id).await? {
|
|
|
keys
|
|
keys
|
|
|
} else {
|
|
} else {
|
|
@@ -27,10 +28,12 @@ impl Wallet {
|
|
|
Ok(keys)
|
|
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))]
|
|
#[instrument(skip(self))]
|
|
|
pub async fn load_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
|
|
pub async fn load_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
|
|
|
match self
|
|
match self
|
|
@@ -39,86 +42,105 @@ impl Wallet {
|
|
|
.await?
|
|
.await?
|
|
|
{
|
|
{
|
|
|
Some(keysets_info) => Ok(keysets_info),
|
|
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))]
|
|
#[instrument(skip(self))]
|
|
|
pub async fn get_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
|
|
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)
|
|
|
|
|
|
|
+ match self
|
|
|
|
|
+ .localstore
|
|
|
|
|
+ .get_mint_keysets(self.mint_url.clone())
|
|
|
|
|
+ .await?
|
|
|
|
|
+ {
|
|
|
|
|
+ Some(keysets_info) => Ok(keysets_info),
|
|
|
|
|
+ None => Err(Error::UnknownKeySet),
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// Get active keyset for mint
|
|
|
|
|
|
|
+ /// 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))]
|
|
#[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;
|
|
|
|
|
|
|
+ pub async fn refresh_keysets(&self) -> Result<KeySetInfos, Error> {
|
|
|
|
|
+ tracing::debug!("Refreshing keysets and ensuring we have keys");
|
|
|
|
|
+ let _ = self.get_mint_info().await?;
|
|
|
|
|
|
|
|
|
|
+ // 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
|
|
self.localstore
|
|
|
- .add_mint_keysets(self.mint_url.clone(), keysets.clone())
|
|
|
|
|
|
|
+ .add_mint_keysets(self.mint_url.clone(), all_keysets.clone())
|
|
|
.await?;
|
|
.await?;
|
|
|
|
|
|
|
|
- let active_keysets = keysets
|
|
|
|
|
- .clone()
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .filter(|k| k.active && k.unit == self.unit)
|
|
|
|
|
- .collect::<Vec<KeySetInfo>>();
|
|
|
|
|
|
|
+ // Filter for active keysets matching our unit
|
|
|
|
|
+ let keysets: KeySetInfos = all_keysets.unit(self.unit.clone()).cloned().collect();
|
|
|
|
|
|
|
|
- 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?;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 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?;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Ok(active_keysets)
|
|
|
|
|
|
|
+ Ok(keysets)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// Get active keyset for mint with the lowest fees
|
|
|
|
|
|
|
+ /// Get the active keyset with the lowest fees - always goes online
|
|
|
///
|
|
///
|
|
|
- /// Queries mint for current keysets then gets [`Keys`] for any unknown
|
|
|
|
|
- /// keysets
|
|
|
|
|
|
|
+ /// 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))]
|
|
#[instrument(skip(self))]
|
|
|
- pub async fn get_active_mint_keyset(&self) -> Result<KeySetInfo, Error> {
|
|
|
|
|
- // Important
|
|
|
|
|
- let _ = self.get_mint_info().await?;
|
|
|
|
|
- let active_keysets = self.get_active_mint_keysets().await?;
|
|
|
|
|
|
|
+ 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)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- 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)
|
|
|
|
|
|
|
+ /// 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
|
|
|
|
|
|
|
+ /// 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> {
|
|
pub async fn get_keyset_fees(&self) -> Result<HashMap<Id, u64>, Error> {
|
|
|
let keysets = self
|
|
let keysets = self
|
|
|
.localstore
|
|
.localstore
|
|
@@ -134,7 +156,11 @@ impl Wallet {
|
|
|
Ok(fees)
|
|
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> {
|
|
pub async fn get_keyset_fees_by_id(&self, keyset_id: Id) -> Result<u64, Error> {
|
|
|
self.get_keyset_fees()
|
|
self.get_keyset_fees()
|
|
|
.await?
|
|
.await?
|