|
@@ -60,6 +60,50 @@ pub struct TransferResult {
|
|
|
pub target_balance_after: Amount,
|
|
pub target_balance_after: Amount,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/// Configuration for individual wallets within MultiMintWallet
|
|
|
|
|
+#[derive(Clone, Default, Debug)]
|
|
|
|
|
+pub struct WalletConfig {
|
|
|
|
|
+ /// Custom mint connector implementation
|
|
|
|
|
+ pub mint_connector: Option<Arc<dyn super::MintConnector + Send + Sync>>,
|
|
|
|
|
+ /// Custom auth connector implementation
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ pub auth_connector: Option<Arc<dyn super::auth::AuthMintConnector + Send + Sync>>,
|
|
|
|
|
+ /// Target number of proofs to maintain at each denomination
|
|
|
|
|
+ pub target_proof_count: Option<usize>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl WalletConfig {
|
|
|
|
|
+ /// Create a new empty WalletConfig
|
|
|
|
|
+ pub fn new() -> Self {
|
|
|
|
|
+ Self::default()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set custom mint connector
|
|
|
|
|
+ pub fn with_mint_connector(
|
|
|
|
|
+ mut self,
|
|
|
|
|
+ connector: Arc<dyn super::MintConnector + Send + Sync>,
|
|
|
|
|
+ ) -> Self {
|
|
|
|
|
+ self.mint_connector = Some(connector);
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set custom auth connector
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ pub fn with_auth_connector(
|
|
|
|
|
+ mut self,
|
|
|
|
|
+ connector: Arc<dyn super::auth::AuthMintConnector + Send + Sync>,
|
|
|
|
|
+ ) -> Self {
|
|
|
|
|
+ self.auth_connector = Some(connector);
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set target proof count
|
|
|
|
|
+ pub fn with_target_proof_count(mut self, count: usize) -> Self {
|
|
|
|
|
+ self.target_proof_count = Some(count);
|
|
|
|
|
+ self
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// Multi Mint Wallet
|
|
/// Multi Mint Wallet
|
|
|
///
|
|
///
|
|
|
/// A wallet that manages multiple mints but supports only one currency unit.
|
|
/// A wallet that manages multiple mints but supports only one currency unit.
|
|
@@ -89,8 +133,8 @@ pub struct TransferResult {
|
|
|
/// // Add mints to the wallet
|
|
/// // Add mints to the wallet
|
|
|
/// let mint_url1: MintUrl = "https://mint1.example.com".parse()?;
|
|
/// let mint_url1: MintUrl = "https://mint1.example.com".parse()?;
|
|
|
/// let mint_url2: MintUrl = "https://mint2.example.com".parse()?;
|
|
/// let mint_url2: MintUrl = "https://mint2.example.com".parse()?;
|
|
|
-/// wallet.add_mint(mint_url1.clone(), None).await?;
|
|
|
|
|
-/// wallet.add_mint(mint_url2, None).await?;
|
|
|
|
|
|
|
+/// wallet.add_mint(mint_url1.clone()).await?;
|
|
|
|
|
+/// wallet.add_mint(mint_url2).await?;
|
|
|
///
|
|
///
|
|
|
/// // Check total balance across all mints
|
|
/// // Check total balance across all mints
|
|
|
/// let balance = wallet.total_balance().await?;
|
|
/// let balance = wallet.total_balance().await?;
|
|
@@ -199,12 +243,149 @@ impl MultiMintWallet {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Adds a mint to this [MultiMintWallet]
|
|
/// Adds a mint to this [MultiMintWallet]
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// Creates a wallet for the specified mint using default or global settings.
|
|
|
|
|
+ /// For custom configuration, use `add_mint_with_config()`.
|
|
|
#[instrument(skip(self))]
|
|
#[instrument(skip(self))]
|
|
|
- pub async fn add_mint(
|
|
|
|
|
|
|
+ pub async fn add_mint(&self, mint_url: MintUrl) -> Result<(), Error> {
|
|
|
|
|
+ // Create wallet with default settings
|
|
|
|
|
+ let wallet = self
|
|
|
|
|
+ .create_wallet_with_config(mint_url.clone(), None)
|
|
|
|
|
+ .await?;
|
|
|
|
|
+
|
|
|
|
|
+ // Insert into wallets map
|
|
|
|
|
+ let mut wallets = self.wallets.write().await;
|
|
|
|
|
+ wallets.insert(mint_url, wallet);
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Adds a mint to this [MultiMintWallet] with custom configuration
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// The provided configuration is used to create the wallet with custom connectors
|
|
|
|
|
+ /// and settings. Configuration is stored within the Wallet instance itself.
|
|
|
|
|
+ #[instrument(skip(self))]
|
|
|
|
|
+ pub async fn add_mint_with_config(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: MintUrl,
|
|
|
|
|
+ config: WalletConfig,
|
|
|
|
|
+ ) -> Result<(), Error> {
|
|
|
|
|
+ // Create wallet with the provided config
|
|
|
|
|
+ let wallet = self
|
|
|
|
|
+ .create_wallet_with_config(mint_url.clone(), Some(&config))
|
|
|
|
|
+ .await?;
|
|
|
|
|
+
|
|
|
|
|
+ // Insert into wallets map
|
|
|
|
|
+ let mut wallets = self.wallets.write().await;
|
|
|
|
|
+ wallets.insert(mint_url, wallet);
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set or update configuration for a mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// If the wallet already exists, it will be updated with the new config.
|
|
|
|
|
+ /// If the wallet doesn't exist, it will be created with the specified config.
|
|
|
|
|
+ #[instrument(skip(self))]
|
|
|
|
|
+ pub async fn set_mint_config(
|
|
|
&self,
|
|
&self,
|
|
|
mint_url: MintUrl,
|
|
mint_url: MintUrl,
|
|
|
- target_proof_count: Option<usize>,
|
|
|
|
|
|
|
+ config: WalletConfig,
|
|
|
) -> Result<(), Error> {
|
|
) -> Result<(), Error> {
|
|
|
|
|
+ // Check if wallet already exists
|
|
|
|
|
+ if self.has_mint(&mint_url).await {
|
|
|
|
|
+ // Update existing wallet in place
|
|
|
|
|
+ let mut wallets = self.wallets.write().await;
|
|
|
|
|
+ if let Some(wallet) = wallets.get_mut(&mint_url) {
|
|
|
|
|
+ // Update target_proof_count if provided
|
|
|
|
|
+ if let Some(count) = config.target_proof_count {
|
|
|
|
|
+ wallet.set_target_proof_count(count);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Update connector if provided
|
|
|
|
|
+ if let Some(connector) = config.mint_connector {
|
|
|
|
|
+ wallet.set_client(connector);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: Handle auth_connector if provided
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ if let Some(_auth_connector) = config.auth_connector {
|
|
|
|
|
+ // For now, we can't easily inject auth_connector into the wallet
|
|
|
|
|
+ // This would require additional work on the Wallet API
|
|
|
|
|
+ // We'll note this as a future enhancement
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Wallet doesn't exist, create it with the provided config
|
|
|
|
|
+ self.add_mint_with_config(mint_url, config).await
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set the auth client (AuthWallet) for a specific mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This allows updating the auth wallet for an existing mint wallet without recreating it.
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ #[instrument(skip_all)]
|
|
|
|
|
+ pub async fn set_auth_client(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: &MintUrl,
|
|
|
|
|
+ auth_wallet: Option<super::auth::AuthWallet>,
|
|
|
|
|
+ ) -> Result<(), Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.set_auth_client(auth_wallet).await;
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Remove mint from MultiMintWallet
|
|
|
|
|
+ #[instrument(skip(self))]
|
|
|
|
|
+ pub async fn remove_mint(&self, mint_url: &MintUrl) {
|
|
|
|
|
+ let mut wallets = self.wallets.write().await;
|
|
|
|
|
+ wallets.remove(mint_url);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Internal: Create wallet with optional custom configuration
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// Priority order for configuration:
|
|
|
|
|
+ /// 1. Custom connector from config (if provided)
|
|
|
|
|
+ /// 2. Global settings (proxy/Tor)
|
|
|
|
|
+ /// 3. Default HttpClient
|
|
|
|
|
+ async fn create_wallet_with_config(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: MintUrl,
|
|
|
|
|
+ config: Option<&WalletConfig>,
|
|
|
|
|
+ ) -> Result<Wallet, Error> {
|
|
|
|
|
+ // Check if custom connector is provided in config
|
|
|
|
|
+ if let Some(cfg) = config {
|
|
|
|
|
+ if let Some(custom_connector) = &cfg.mint_connector {
|
|
|
|
|
+ // Use custom connector with WalletBuilder
|
|
|
|
|
+ let builder = WalletBuilder::new()
|
|
|
|
|
+ .mint_url(mint_url.clone())
|
|
|
|
|
+ .unit(self.unit.clone())
|
|
|
|
|
+ .localstore(self.localstore.clone())
|
|
|
|
|
+ .seed(self.seed)
|
|
|
|
|
+ .target_proof_count(cfg.target_proof_count.unwrap_or(3))
|
|
|
|
|
+ .shared_client(custom_connector.clone());
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: Handle auth_connector if provided
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ if let Some(_auth_connector) = &cfg.auth_connector {
|
|
|
|
|
+ // For now, we can't easily inject auth_connector into the wallet
|
|
|
|
|
+ // This would require additional work on the Wallet/WalletBuilder API
|
|
|
|
|
+ // We'll note this as a future enhancement
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return builder.build();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Fall back to existing logic: proxy/Tor/default
|
|
|
|
|
+ let target_proof_count = config.and_then(|c| c.target_proof_count).unwrap_or(3);
|
|
|
|
|
+
|
|
|
let wallet = if let Some(proxy_url) = &self.proxy_config {
|
|
let wallet = if let Some(proxy_url) = &self.proxy_config {
|
|
|
// Create wallet with proxy-configured client
|
|
// Create wallet with proxy-configured client
|
|
|
let client = crate::wallet::HttpClient::with_proxy(
|
|
let client = crate::wallet::HttpClient::with_proxy(
|
|
@@ -228,7 +409,7 @@ impl MultiMintWallet {
|
|
|
.unit(self.unit.clone())
|
|
.unit(self.unit.clone())
|
|
|
.localstore(self.localstore.clone())
|
|
.localstore(self.localstore.clone())
|
|
|
.seed(self.seed)
|
|
.seed(self.seed)
|
|
|
- .target_proof_count(target_proof_count.unwrap_or(3))
|
|
|
|
|
|
|
+ .target_proof_count(target_proof_count)
|
|
|
.client(client)
|
|
.client(client)
|
|
|
.build()?
|
|
.build()?
|
|
|
} else {
|
|
} else {
|
|
@@ -256,7 +437,7 @@ impl MultiMintWallet {
|
|
|
.unit(self.unit.clone())
|
|
.unit(self.unit.clone())
|
|
|
.localstore(self.localstore.clone())
|
|
.localstore(self.localstore.clone())
|
|
|
.seed(self.seed)
|
|
.seed(self.seed)
|
|
|
- .target_proof_count(target_proof_count.unwrap_or(3))
|
|
|
|
|
|
|
+ .target_proof_count(target_proof_count)
|
|
|
.client(client)
|
|
.client(client)
|
|
|
.build()?
|
|
.build()?
|
|
|
} else {
|
|
} else {
|
|
@@ -266,7 +447,7 @@ impl MultiMintWallet {
|
|
|
self.unit.clone(),
|
|
self.unit.clone(),
|
|
|
self.localstore.clone(),
|
|
self.localstore.clone(),
|
|
|
self.seed,
|
|
self.seed,
|
|
|
- target_proof_count,
|
|
|
|
|
|
|
+ Some(target_proof_count),
|
|
|
)?
|
|
)?
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -278,22 +459,12 @@ impl MultiMintWallet {
|
|
|
self.unit.clone(),
|
|
self.unit.clone(),
|
|
|
self.localstore.clone(),
|
|
self.localstore.clone(),
|
|
|
self.seed,
|
|
self.seed,
|
|
|
- target_proof_count,
|
|
|
|
|
|
|
+ Some(target_proof_count),
|
|
|
)?
|
|
)?
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- let mut wallets = self.wallets.write().await;
|
|
|
|
|
- wallets.insert(mint_url, wallet);
|
|
|
|
|
-
|
|
|
|
|
- Ok(())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// Remove mint from MultiMintWallet
|
|
|
|
|
- #[instrument(skip(self))]
|
|
|
|
|
- pub async fn remove_mint(&self, mint_url: &MintUrl) {
|
|
|
|
|
- let mut wallets = self.wallets.write().await;
|
|
|
|
|
- wallets.remove(mint_url);
|
|
|
|
|
|
|
+ Ok(wallet)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Load all wallets from database that have proofs for this currency unit
|
|
/// Load all wallets from database that have proofs for this currency unit
|
|
@@ -317,7 +488,7 @@ impl MultiMintWallet {
|
|
|
if mint_has_proofs_for_unit {
|
|
if mint_has_proofs_for_unit {
|
|
|
// Add mint to the MultiMintWallet if not already present
|
|
// Add mint to the MultiMintWallet if not already present
|
|
|
if !self.has_mint(&mint_url).await {
|
|
if !self.has_mint(&mint_url).await {
|
|
|
- self.add_mint(mint_url.clone(), None).await?
|
|
|
|
|
|
|
+ self.add_mint(mint_url.clone()).await?
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -980,7 +1151,7 @@ impl MultiMintWallet {
|
|
|
|
|
|
|
|
// Add the untrusted mint temporarily if needed
|
|
// Add the untrusted mint temporarily if needed
|
|
|
if !is_trusted {
|
|
if !is_trusted {
|
|
|
- self.add_mint(mint_url.clone(), None).await?;
|
|
|
|
|
|
|
+ self.add_mint(mint_url.clone()).await?;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let wallets = self.wallets.read().await;
|
|
let wallets = self.wallets.read().await;
|
|
@@ -1414,6 +1585,103 @@ impl MultiMintWallet {
|
|
|
|
|
|
|
|
Ok(total_consolidated)
|
|
Ok(total_consolidated)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /// Mint blind auth tokens for a specific mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This is a convenience method that calls the underlying wallet's mint_blind_auth.
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ #[instrument(skip_all)]
|
|
|
|
|
+ pub async fn mint_blind_auth(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: &MintUrl,
|
|
|
|
|
+ amount: Amount,
|
|
|
|
|
+ ) -> Result<Proofs, Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.mint_blind_auth(amount).await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Get unspent auth proofs for a specific mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This is a convenience method that calls the underlying wallet's get_unspent_auth_proofs.
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ #[instrument(skip_all)]
|
|
|
|
|
+ pub async fn get_unspent_auth_proofs(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: &MintUrl,
|
|
|
|
|
+ ) -> Result<Vec<cdk_common::AuthProof>, Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.get_unspent_auth_proofs().await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set Clear Auth Token (CAT) for authentication at a specific mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This is a convenience method that calls the underlying wallet's set_cat.
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ #[instrument(skip_all)]
|
|
|
|
|
+ pub async fn set_cat(&self, mint_url: &MintUrl, cat: String) -> Result<(), Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.set_cat(cat).await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Set refresh token for authentication at a specific mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This is a convenience method that calls the underlying wallet's set_refresh_token.
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ #[instrument(skip_all)]
|
|
|
|
|
+ pub async fn set_refresh_token(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: &MintUrl,
|
|
|
|
|
+ refresh_token: String,
|
|
|
|
|
+ ) -> Result<(), Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.set_refresh_token(refresh_token).await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Refresh CAT token for a specific mint
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This is a convenience method that calls the underlying wallet's refresh_access_token.
|
|
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
|
|
+ #[instrument(skip(self))]
|
|
|
|
|
+ pub async fn refresh_access_token(&self, mint_url: &MintUrl) -> Result<(), Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.refresh_access_token().await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Query mint for current mint information
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This is a convenience method that calls the underlying wallet's fetch_mint_info.
|
|
|
|
|
+ #[instrument(skip(self))]
|
|
|
|
|
+ pub async fn fetch_mint_info(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ mint_url: &MintUrl,
|
|
|
|
|
+ ) -> Result<Option<crate::nuts::MintInfo>, Error> {
|
|
|
|
|
+ let wallets = self.wallets.read().await;
|
|
|
|
|
+ let wallet = wallets.get(mint_url).ok_or(Error::UnknownMint {
|
|
|
|
|
+ mint_url: mint_url.to_string(),
|
|
|
|
|
+ })?;
|
|
|
|
|
+
|
|
|
|
|
+ wallet.fetch_mint_info().await
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl Drop for MultiMintWallet {
|
|
impl Drop for MultiMintWallet {
|