use std::collections::{BTreeMap, HashMap};
use std::str::FromStr;
use std::sync::Arc;
use cdk_common::wallet::WalletKey;
use tokio::sync::Mutex;
use tracing::instrument;
use super::send::{PreparedSend, SendMemo, SendOptions};
use super::Error;
use crate::amount::SplitTarget;
use crate::mint_url::MintUrl;
use crate::nuts::{CurrencyUnit, MeltOptions, Proof, Proofs, SecretKey, SpendingConditions, Token};
use crate::types::Melted;
use crate::wallet::types::MintQuote;
use crate::{ensure_cdk, Amount, Wallet};
#[derive(Debug, Clone)]
pub struct MultiMintWallet {
pub wallets: Arc<Mutex<BTreeMap<WalletKey, Wallet>>>,
}
impl MultiMintWallet {
pub fn new(wallets: Vec<Wallet>) -> Self {
Self {
wallets: Arc::new(Mutex::new(
wallets
.into_iter()
.map(|w| (WalletKey::new(w.mint_url.clone(), w.unit.clone()), w))
.collect(),
)),
}
}
#[instrument(skip(self, wallet))]
pub async fn add_wallet(&self, wallet: Wallet) {
let wallet_key = WalletKey::new(wallet.mint_url.clone(), wallet.unit.clone());
let mut wallets = self.wallets.lock().await;
wallets.insert(wallet_key, wallet);
}
#[instrument(skip(self))]
pub async fn remove_wallet(&self, wallet_key: &WalletKey) {
let mut wallets = self.wallets.lock().await;
wallets.remove(wallet_key);
}
#[instrument(skip(self))]
pub async fn get_wallets(&self) -> Vec<Wallet> {
self.wallets.lock().await.values().cloned().collect()
}
#[instrument(skip(self))]
pub async fn get_wallet(&self, wallet_key: &WalletKey) -> Option<Wallet> {
let wallets = self.wallets.lock().await;
wallets.get(wallet_key).cloned()
}
#[instrument(skip(self))]
pub async fn has(&self, wallet_key: &WalletKey) -> bool {
self.wallets.lock().await.contains_key(wallet_key)
}
#[instrument(skip(self))]
pub async fn get_balances(
&self,
unit: &CurrencyUnit,
) -> Result<BTreeMap<MintUrl, Amount>, Error> {
let mut balances = BTreeMap::new();
for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
if unit == u {
let wallet_balance = wallet.total_balance().await?;
balances.insert(mint_url.clone(), wallet_balance);
}
}
Ok(balances)
}
#[instrument(skip(self))]
pub async fn list_proofs(
&self,
) -> Result<BTreeMap<MintUrl, (Vec<Proof>, CurrencyUnit)>, Error> {
let mut mint_proofs = BTreeMap::new();
for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
let wallet_proofs = wallet.get_unspent_proofs().await?;
mint_proofs.insert(mint_url.clone(), (wallet_proofs, u.clone()));
}
Ok(mint_proofs)
}
#[instrument(skip(self))]
pub async fn prepare_send(
&self,
wallet_key: &WalletKey,
amount: Amount,
opts: SendOptions,
) -> Result<PreparedSend, Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet.prepare_send(amount, opts).await
}
#[instrument(skip(self))]
pub async fn send(
&self,
wallet_key: &WalletKey,
send: PreparedSend,
memo: Option<SendMemo>,
) -> Result<Token, Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet.send(send, memo).await
}
#[instrument(skip(self))]
pub async fn mint_quote(
&self,
wallet_key: &WalletKey,
amount: Amount,
description: Option<String>,
) -> Result<MintQuote, Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet.mint_quote(amount, description).await
}
#[instrument(skip(self))]
pub async fn check_all_mint_quotes(
&self,
wallet_key: Option<WalletKey>,
) -> Result<HashMap<CurrencyUnit, Amount>, Error> {
let mut amount_minted = HashMap::new();
match wallet_key {
Some(wallet_key) => {
let wallet = self
.get_wallet(&wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
let amount = wallet.check_all_mint_quotes().await?;
amount_minted.insert(wallet.unit, amount);
}
None => {
for (_, wallet) in self.wallets.lock().await.iter() {
let amount = wallet.check_all_mint_quotes().await?;
amount_minted
.entry(wallet.unit.clone())
.and_modify(|b| *b += amount)
.or_insert(amount);
}
}
}
Ok(amount_minted)
}
#[instrument(skip(self))]
pub async fn mint(
&self,
wallet_key: &WalletKey,
quote_id: &str,
conditions: Option<SpendingConditions>,
) -> Result<Proofs, Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet
.mint(quote_id, SplitTarget::default(), conditions)
.await
}
#[instrument(skip_all)]
pub async fn receive(
&self,
encoded_token: &str,
p2pk_signing_keys: &[SecretKey],
preimages: &[String],
) -> Result<Amount, Error> {
let token_data = Token::from_str(encoded_token)?;
let unit = token_data.unit().unwrap_or_default();
let proofs = token_data.proofs();
let mut amount_received = Amount::ZERO;
let mut mint_errors = None;
let mint_url = token_data.mint_url()?;
let wallet_key = WalletKey::new(mint_url.clone(), unit.clone());
if !self.has(&wallet_key).await {
return Err(Error::UnknownWallet(wallet_key.clone()));
}
let wallet_key = WalletKey::new(mint_url.clone(), unit);
let wallet = self
.get_wallet(&wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
match wallet
.receive_proofs(proofs, SplitTarget::default(), p2pk_signing_keys, preimages)
.await
{
Ok(amount) => {
amount_received += amount;
}
Err(err) => {
tracing::error!("Could no receive proofs for mint: {}", err);
mint_errors = Some(err);
}
}
match mint_errors {
None => Ok(amount_received),
Some(err) => Err(err),
}
}
#[instrument(skip(self, bolt11))]
pub async fn pay_invoice_for_wallet(
&self,
bolt11: &str,
options: Option<MeltOptions>,
wallet_key: &WalletKey,
max_fee: Option<Amount>,
) -> Result<Melted, Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
let quote = wallet.melt_quote(bolt11.to_string(), options).await?;
if let Some(max_fee) = max_fee {
ensure_cdk!(quote.fee_reserve <= max_fee, Error::MaxFeeExceeded);
}
wallet.melt("e.id).await
}
#[instrument(skip(self))]
pub async fn restore(&self, wallet_key: &WalletKey) -> Result<Amount, Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet.restore().await
}
#[instrument(skip(self, token))]
pub async fn verify_token_p2pk(
&self,
wallet_key: &WalletKey,
token: &Token,
conditions: SpendingConditions,
) -> Result<(), Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet.verify_token_p2pk(token, conditions)
}
#[instrument(skip(self, token))]
pub async fn verify_token_dleq(
&self,
wallet_key: &WalletKey,
token: &Token,
) -> Result<(), Error> {
let wallet = self
.get_wallet(wallet_key)
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
wallet.verify_token_dleq(token).await
}
}