multi_mint_wallet.rs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. //! MultiMint Wallet
  2. //!
  3. //! Wrapper around core [`Wallet`] that enables the use of multiple mint unit
  4. //! pairs
  5. use std::collections::{BTreeMap, HashMap};
  6. use std::fmt;
  7. use std::str::FromStr;
  8. use std::sync::Arc;
  9. use serde::{Deserialize, Serialize};
  10. use tokio::sync::Mutex;
  11. use tracing::instrument;
  12. use super::types::SendKind;
  13. use super::Error;
  14. use crate::amount::SplitTarget;
  15. use crate::mint_url::MintUrl;
  16. use crate::nuts::{CurrencyUnit, Proof, SecretKey, SpendingConditions, Token};
  17. use crate::types::Melted;
  18. use crate::wallet::types::MintQuote;
  19. use crate::{Amount, Wallet};
  20. /// Multi Mint Wallet
  21. #[derive(Debug, Clone)]
  22. pub struct MultiMintWallet {
  23. /// Wallets
  24. pub wallets: Arc<Mutex<BTreeMap<WalletKey, Wallet>>>,
  25. }
  26. /// Wallet Key
  27. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
  28. pub struct WalletKey {
  29. mint_url: MintUrl,
  30. unit: CurrencyUnit,
  31. }
  32. impl fmt::Display for WalletKey {
  33. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  34. write!(f, "mint_url: {}, unit: {}", self.mint_url, self.unit,)
  35. }
  36. }
  37. impl WalletKey {
  38. /// Create new [`WalletKey`]
  39. pub fn new(mint_url: MintUrl, unit: CurrencyUnit) -> Self {
  40. Self { mint_url, unit }
  41. }
  42. }
  43. impl MultiMintWallet {
  44. /// New Multimint wallet
  45. pub fn new(wallets: Vec<Wallet>) -> Self {
  46. Self {
  47. wallets: Arc::new(Mutex::new(
  48. wallets
  49. .into_iter()
  50. .map(|w| (WalletKey::new(w.mint_url.clone(), w.unit.clone()), w))
  51. .collect(),
  52. )),
  53. }
  54. }
  55. /// Add wallet to MultiMintWallet
  56. #[instrument(skip(self, wallet))]
  57. pub async fn add_wallet(&self, wallet: Wallet) {
  58. let wallet_key = WalletKey::new(wallet.mint_url.clone(), wallet.unit.clone());
  59. let mut wallets = self.wallets.lock().await;
  60. wallets.insert(wallet_key, wallet);
  61. }
  62. /// Remove Wallet from MultiMintWallet
  63. #[instrument(skip(self))]
  64. pub async fn remove_wallet(&self, wallet_key: &WalletKey) {
  65. let mut wallets = self.wallets.lock().await;
  66. wallets.remove(wallet_key);
  67. }
  68. /// Get Wallets from MultiMintWallet
  69. #[instrument(skip(self))]
  70. pub async fn get_wallets(&self) -> Vec<Wallet> {
  71. self.wallets.lock().await.values().cloned().collect()
  72. }
  73. /// Get Wallet from MultiMintWallet
  74. #[instrument(skip(self))]
  75. pub async fn get_wallet(&self, wallet_key: &WalletKey) -> Option<Wallet> {
  76. let wallets = self.wallets.lock().await;
  77. wallets.get(wallet_key).cloned()
  78. }
  79. /// Check if mint unit pair is in wallet
  80. #[instrument(skip(self))]
  81. pub async fn has(&self, wallet_key: &WalletKey) -> bool {
  82. self.wallets.lock().await.contains_key(wallet_key)
  83. }
  84. /// Get wallet balances
  85. #[instrument(skip(self))]
  86. pub async fn get_balances(
  87. &self,
  88. unit: &CurrencyUnit,
  89. ) -> Result<BTreeMap<MintUrl, Amount>, Error> {
  90. let mut balances = BTreeMap::new();
  91. for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
  92. if unit == u {
  93. let wallet_balance = wallet.total_balance().await?;
  94. balances.insert(mint_url.clone(), wallet_balance);
  95. }
  96. }
  97. Ok(balances)
  98. }
  99. /// List proofs.
  100. #[instrument(skip(self))]
  101. pub async fn list_proofs(
  102. &self,
  103. ) -> Result<BTreeMap<MintUrl, (Vec<Proof>, CurrencyUnit)>, Error> {
  104. let mut mint_proofs = BTreeMap::new();
  105. for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
  106. let wallet_proofs = wallet.get_unspent_proofs().await?;
  107. mint_proofs.insert(mint_url.clone(), (wallet_proofs, u.clone()));
  108. }
  109. Ok(mint_proofs)
  110. }
  111. /// Create cashu token
  112. #[instrument(skip(self))]
  113. pub async fn send(
  114. &self,
  115. wallet_key: &WalletKey,
  116. amount: Amount,
  117. memo: Option<String>,
  118. conditions: Option<SpendingConditions>,
  119. send_kind: SendKind,
  120. include_fees: bool,
  121. ) -> Result<Token, Error> {
  122. let wallet = self
  123. .get_wallet(wallet_key)
  124. .await
  125. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  126. wallet
  127. .send(
  128. amount,
  129. memo,
  130. conditions,
  131. &SplitTarget::default(),
  132. &send_kind,
  133. include_fees,
  134. )
  135. .await
  136. }
  137. /// Mint quote for wallet
  138. #[instrument(skip(self))]
  139. pub async fn mint_quote(
  140. &self,
  141. wallet_key: &WalletKey,
  142. amount: Amount,
  143. description: Option<String>,
  144. ) -> Result<MintQuote, Error> {
  145. let wallet = self
  146. .get_wallet(wallet_key)
  147. .await
  148. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  149. wallet.mint_quote(amount, description).await
  150. }
  151. /// Check all mint quotes
  152. /// If quote is paid, wallet will mint
  153. #[instrument(skip(self))]
  154. pub async fn check_all_mint_quotes(
  155. &self,
  156. wallet_key: Option<WalletKey>,
  157. ) -> Result<HashMap<CurrencyUnit, Amount>, Error> {
  158. let mut amount_minted = HashMap::new();
  159. match wallet_key {
  160. Some(wallet_key) => {
  161. let wallet = self
  162. .get_wallet(&wallet_key)
  163. .await
  164. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  165. let amount = wallet.check_all_mint_quotes().await?;
  166. amount_minted.insert(wallet.unit, amount);
  167. }
  168. None => {
  169. for (_, wallet) in self.wallets.lock().await.iter() {
  170. let amount = wallet.check_all_mint_quotes().await?;
  171. amount_minted
  172. .entry(wallet.unit.clone())
  173. .and_modify(|b| *b += amount)
  174. .or_insert(amount);
  175. }
  176. }
  177. }
  178. Ok(amount_minted)
  179. }
  180. /// Mint a specific quote
  181. #[instrument(skip(self))]
  182. pub async fn mint(
  183. &self,
  184. wallet_key: &WalletKey,
  185. quote_id: &str,
  186. conditions: Option<SpendingConditions>,
  187. ) -> Result<Amount, Error> {
  188. let wallet = self
  189. .get_wallet(wallet_key)
  190. .await
  191. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  192. wallet
  193. .mint(quote_id, SplitTarget::default(), conditions)
  194. .await
  195. }
  196. /// Receive token
  197. /// Wallet must be already added to multimintwallet
  198. #[instrument(skip_all)]
  199. pub async fn receive(
  200. &self,
  201. encoded_token: &str,
  202. p2pk_signing_keys: &[SecretKey],
  203. preimages: &[String],
  204. ) -> Result<Amount, Error> {
  205. let token_data = Token::from_str(encoded_token)?;
  206. let unit = token_data.unit().unwrap_or_default();
  207. let proofs = token_data.proofs();
  208. let mut amount_received = Amount::ZERO;
  209. let mut mint_errors = None;
  210. let mint_url = token_data.mint_url()?;
  211. // Check that all mints in tokes have wallets
  212. let wallet_key = WalletKey::new(mint_url.clone(), unit.clone());
  213. if !self.has(&wallet_key).await {
  214. return Err(Error::UnknownWallet(wallet_key.clone()));
  215. }
  216. let wallet_key = WalletKey::new(mint_url.clone(), unit);
  217. let wallet = self
  218. .get_wallet(&wallet_key)
  219. .await
  220. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  221. match wallet
  222. .receive_proofs(proofs, SplitTarget::default(), p2pk_signing_keys, preimages)
  223. .await
  224. {
  225. Ok(amount) => {
  226. amount_received += amount;
  227. }
  228. Err(err) => {
  229. tracing::error!("Could no receive proofs for mint: {}", err);
  230. mint_errors = Some(err);
  231. }
  232. }
  233. match mint_errors {
  234. None => Ok(amount_received),
  235. Some(err) => Err(err),
  236. }
  237. }
  238. /// Pay an bolt11 invoice from specific wallet
  239. #[instrument(skip(self, bolt11))]
  240. pub async fn pay_invoice_for_wallet(
  241. &self,
  242. bolt11: &str,
  243. wallet_key: &WalletKey,
  244. max_fee: Option<Amount>,
  245. ) -> Result<Melted, Error> {
  246. let wallet = self
  247. .get_wallet(wallet_key)
  248. .await
  249. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  250. let quote = wallet.melt_quote(bolt11.to_string(), None).await?;
  251. if let Some(max_fee) = max_fee {
  252. if quote.fee_reserve > max_fee {
  253. return Err(Error::MaxFeeExceeded);
  254. }
  255. }
  256. wallet.melt(&quote.id).await
  257. }
  258. /// Restore
  259. #[instrument(skip(self))]
  260. pub async fn restore(&self, wallet_key: &WalletKey) -> Result<Amount, Error> {
  261. let wallet = self
  262. .get_wallet(wallet_key)
  263. .await
  264. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  265. wallet.restore().await
  266. }
  267. /// Verify token matches p2pk conditions
  268. #[instrument(skip(self, token))]
  269. pub async fn verify_token_p2pk(
  270. &self,
  271. wallet_key: &WalletKey,
  272. token: &Token,
  273. conditions: SpendingConditions,
  274. ) -> Result<(), Error> {
  275. let wallet = self
  276. .get_wallet(wallet_key)
  277. .await
  278. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  279. wallet.verify_token_p2pk(token, conditions)
  280. }
  281. /// Verifys all proofs in toke have valid dleq proof
  282. #[instrument(skip(self, token))]
  283. pub async fn verify_token_dleq(
  284. &self,
  285. wallet_key: &WalletKey,
  286. token: &Token,
  287. ) -> Result<(), Error> {
  288. let wallet = self
  289. .get_wallet(wallet_key)
  290. .await
  291. .ok_or(Error::UnknownWallet(wallet_key.clone()))?;
  292. wallet.verify_token_dleq(token).await
  293. }
  294. }