use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use cdk_common::parking_lot::RwLock;
use cdk_common::{database, AuthToken};
use tokio::sync::RwLock as TokioRwLock;
use crate::cdk_database::WalletDatabase;
use crate::error::Error;
use crate::mint_url::MintUrl;
use crate::nuts::CurrencyUnit;
use crate::wallet::auth::AuthWallet;
use crate::wallet::mint_metadata_cache::MintMetadataCache;
use crate::wallet::{HttpClient, MintConnector, SubscriptionManager, Wallet};
pub struct WalletBuilder {
mint_url: Option<MintUrl>,
unit: Option<CurrencyUnit>,
localstore: Option<Arc<dyn WalletDatabase<database::Error> + Send + Sync>>,
target_proof_count: Option<usize>,
auth_wallet: Option<AuthWallet>,
seed: Option<[u8; 64]>,
use_http_subscription: bool,
client: Option<Arc<dyn MintConnector + Send + Sync>>,
metadata_cache_ttl: Option<Duration>,
metadata_cache: Option<Arc<MintMetadataCache>>,
metadata_caches: HashMap<MintUrl, Arc<MintMetadataCache>>,
}
impl std::fmt::Debug for WalletBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WalletBuilder")
.field("mint_url", &self.mint_url)
.field("unit", &self.unit)
.field("target_proof_count", &self.target_proof_count)
.finish_non_exhaustive()
}
}
impl Default for WalletBuilder {
fn default() -> Self {
Self {
mint_url: None,
unit: None,
localstore: None,
target_proof_count: Some(3),
auth_wallet: None,
seed: None,
client: None,
metadata_cache_ttl: Some(Duration::from_secs(3600)),
use_http_subscription: false,
metadata_cache: None,
metadata_caches: HashMap::new(),
}
}
}
impl WalletBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn use_http_subscription(mut self) -> Self {
self.use_http_subscription = true;
self
}
pub fn set_metadata_cache_ttl(mut self, metadata_cache_ttl: Option<Duration>) -> Self {
self.metadata_cache_ttl = metadata_cache_ttl;
self
}
pub fn prefer_ws_subscription(mut self) -> Self {
self.use_http_subscription = false;
self
}
pub fn mint_url(mut self, mint_url: MintUrl) -> Self {
self.mint_url = Some(mint_url);
self
}
pub fn unit(mut self, unit: CurrencyUnit) -> Self {
self.unit = Some(unit);
self
}
pub fn localstore(
mut self,
localstore: Arc<dyn WalletDatabase<database::Error> + Send + Sync>,
) -> Self {
self.localstore = Some(localstore);
self
}
pub fn target_proof_count(mut self, count: usize) -> Self {
self.target_proof_count = Some(count);
self
}
pub fn auth_wallet(mut self, auth_wallet: AuthWallet) -> Self {
self.auth_wallet = Some(auth_wallet);
self
}
pub fn seed(mut self, seed: [u8; 64]) -> Self {
self.seed = Some(seed);
self
}
pub fn client<C: MintConnector + 'static + Send + Sync>(mut self, client: C) -> Self {
self.client = Some(Arc::new(client));
self
}
pub fn shared_client(mut self, client: Arc<dyn MintConnector + Send + Sync>) -> Self {
self.client = Some(client);
self
}
pub fn metadata_cache(mut self, metadata_cache: Arc<MintMetadataCache>) -> Self {
self.metadata_cache = Some(metadata_cache);
self
}
pub fn metadata_caches(
mut self,
metadata_caches: HashMap<MintUrl, Arc<MintMetadataCache>>,
) -> Self {
self.metadata_caches = metadata_caches;
self
}
pub fn set_auth_cat(mut self, cat: String) -> Result<Self, Error> {
let mint_url = self
.mint_url
.clone()
.ok_or_else(|| Error::Custom("Mint URL required".to_string()))?;
let localstore = self
.localstore
.clone()
.ok_or_else(|| Error::Custom("Localstore required".to_string()))?;
let metadata_cache = self.metadata_cache.clone().unwrap_or_else(|| {
if let Some(cache) = self.metadata_caches.get(&mint_url) {
cache.clone()
} else {
Arc::new(MintMetadataCache::new(mint_url.clone()))
}
});
self.auth_wallet = Some(AuthWallet::new(
mint_url,
Some(AuthToken::ClearAuth(cat)),
localstore,
metadata_cache,
HashMap::new(),
None,
));
Ok(self)
}
pub fn build(self) -> Result<Wallet, Error> {
let mint_url = self
.mint_url
.ok_or(Error::Custom("Mint url required".to_string()))?;
let unit = self
.unit
.ok_or(Error::Custom("Unit required".to_string()))?;
let localstore = self
.localstore
.ok_or(Error::Custom("Localstore required".to_string()))?;
let seed: [u8; 64] = self
.seed
.ok_or(Error::Custom("Seed required".to_string()))?;
let client = match self.client {
Some(client) => client,
None => Arc::new(HttpClient::new(mint_url.clone(), self.auth_wallet.clone()))
as Arc<dyn MintConnector + Send + Sync>,
};
let metadata_cache_ttl = self.metadata_cache_ttl;
let metadata_cache = self.metadata_cache.unwrap_or_else(|| {
if let Some(cache) = self.metadata_caches.get(&mint_url) {
cache.clone()
} else {
Arc::new(MintMetadataCache::new(mint_url.clone()))
}
});
Ok(Wallet {
mint_url,
unit,
localstore,
metadata_cache,
metadata_cache_ttl: Arc::new(RwLock::new(metadata_cache_ttl)),
target_proof_count: self.target_proof_count.unwrap_or(3),
auth_wallet: Arc::new(TokioRwLock::new(self.auth_wallet)),
#[cfg(feature = "npubcash")]
npubcash_client: Arc::new(TokioRwLock::new(None)),
seed,
client: client.clone(),
subscription: SubscriptionManager::new(client, self.use_http_subscription),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_ttl() {
let builder = WalletBuilder::default();
assert_eq!(builder.metadata_cache_ttl, Some(Duration::from_secs(3600)));
}
}