mod.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. //! Cashu Wallet
  2. use std::collections::{HashMap, HashSet};
  3. use std::str::FromStr;
  4. use bip39::Mnemonic;
  5. use cashu::dhke::{construct_proofs, unblind_message};
  6. #[cfg(feature = "nut07")]
  7. use cashu::nuts::nut07::ProofState;
  8. use cashu::nuts::{
  9. BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, PreMintSecrets, PreSwap, Proof,
  10. Proofs, SwapRequest, Token,
  11. };
  12. #[cfg(feature = "nut07")]
  13. use cashu::secret::Secret;
  14. use cashu::types::{MeltQuote, Melted, MintQuote};
  15. use cashu::url::UncheckedUrl;
  16. use cashu::{Amount, Bolt11Invoice};
  17. use localstore::LocalStore;
  18. use thiserror::Error;
  19. use tracing::warn;
  20. use crate::client::Client;
  21. use crate::utils::unix_time;
  22. pub mod localstore;
  23. #[derive(Debug, Error)]
  24. pub enum Error {
  25. /// Insufficient Funds
  26. #[error("Insufficient Funds")]
  27. InsufficientFunds,
  28. #[error("`{0}`")]
  29. Cashu(#[from] cashu::error::wallet::Error),
  30. #[error("`{0}`")]
  31. Client(#[from] crate::client::Error),
  32. /// Cashu Url Error
  33. #[error("`{0}`")]
  34. CashuUrl(#[from] cashu::url::Error),
  35. #[error("Quote Expired")]
  36. QuoteExpired,
  37. #[error("Quote Unknown")]
  38. QuoteUnknown,
  39. #[error("`{0}`")]
  40. LocalStore(#[from] localstore::Error),
  41. #[error("`{0}`")]
  42. Custom(String),
  43. }
  44. #[derive(Clone, Debug)]
  45. pub struct BackupInfo {
  46. mnemonic: Mnemonic,
  47. counter: HashMap<Id, u64>,
  48. }
  49. #[derive(Clone, Debug)]
  50. pub struct Wallet<C: Client, L: LocalStore> {
  51. pub client: C,
  52. localstore: L,
  53. backup_info: Option<BackupInfo>,
  54. }
  55. impl<C: Client, L: LocalStore> Wallet<C, L> {
  56. pub async fn new(client: C, localstore: L, backup_info: Option<BackupInfo>) -> Self {
  57. Self {
  58. backup_info,
  59. client,
  60. localstore,
  61. }
  62. }
  63. /// Back up seed
  64. pub fn mnemonic(&self) -> Option<Mnemonic> {
  65. self.backup_info.clone().map(|b| b.mnemonic)
  66. }
  67. /// Back up keyset counters
  68. pub fn keyset_counters(&self) -> Option<HashMap<Id, u64>> {
  69. self.backup_info.clone().map(|b| b.counter)
  70. }
  71. pub async fn mint_balances(&self) -> Result<HashMap<UncheckedUrl, Amount>, Error> {
  72. let mints = self.localstore.get_mints().await?;
  73. let mut balances = HashMap::new();
  74. for (mint, _) in mints {
  75. if let Some(proofs) = self.localstore.get_proofs(mint.clone()).await? {
  76. let amount = proofs.iter().map(|p| p.amount).sum();
  77. balances.insert(mint, amount);
  78. } else {
  79. balances.insert(mint, Amount::ZERO);
  80. }
  81. }
  82. Ok(balances)
  83. }
  84. pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
  85. Ok(self.localstore.get_proofs(mint_url).await?)
  86. }
  87. pub async fn add_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Error> {
  88. let mint_info = match self
  89. .client
  90. .get_mint_info(mint_url.clone().try_into()?)
  91. .await
  92. {
  93. Ok(mint_info) => Some(mint_info),
  94. Err(err) => {
  95. warn!("Could not get mint info {}", err);
  96. None
  97. }
  98. };
  99. self.localstore
  100. .add_mint(mint_url, mint_info.clone())
  101. .await?;
  102. Ok(mint_info)
  103. }
  104. pub async fn get_mint_keys(
  105. &self,
  106. mint_url: &UncheckedUrl,
  107. keyset_id: Id,
  108. ) -> Result<Keys, Error> {
  109. let keys = if let Some(keys) = self.localstore.get_keys(&keyset_id).await? {
  110. keys
  111. } else {
  112. let keys = self
  113. .client
  114. .get_mint_keyset(mint_url.try_into()?, keyset_id)
  115. .await?;
  116. self.localstore.add_keys(keys.keys.clone()).await?;
  117. keys.keys
  118. };
  119. Ok(keys)
  120. }
  121. /// Check if a proof is spent
  122. #[cfg(feature = "nut07")]
  123. pub async fn check_proofs_spent(
  124. &self,
  125. mint_url: UncheckedUrl,
  126. proofs: Proofs,
  127. ) -> Result<Vec<ProofState>, Error> {
  128. let spendable = self
  129. .client
  130. .post_check_state(
  131. mint_url.try_into()?,
  132. proofs
  133. .clone()
  134. .into_iter()
  135. .map(|p| p.secret)
  136. .collect::<Vec<Secret>>()
  137. .clone(),
  138. )
  139. .await?;
  140. // Separate proofs in spent and unspent based on mint response
  141. Ok(spendable.states)
  142. }
  143. /*
  144. // TODO: This should be create token
  145. // the requited proofs for the token amount may already be in the wallet and mint is not needed
  146. // Mint a token
  147. pub async fn mint_token(
  148. &mut self,
  149. mint_url: UncheckedUrl,
  150. amount: Amount,
  151. memo: Option<String>,
  152. unit: Option<CurrencyUnit>,
  153. ) -> Result<Token, Error> {
  154. let quote = self
  155. .mint_quote(
  156. mint_url.clone(),
  157. amount,
  158. unit.clone()
  159. .ok_or(Error::Custom("Unit required".to_string()))?,
  160. )
  161. .await?;
  162. let proofs = self.mint(mint_url.clone(), &quote.id).await?;
  163. let token = Token::new(mint_url.clone(), proofs, memo, unit);
  164. Ok(token?)
  165. }
  166. */
  167. /// Mint Quote
  168. pub async fn mint_quote(
  169. &mut self,
  170. mint_url: UncheckedUrl,
  171. amount: Amount,
  172. unit: CurrencyUnit,
  173. ) -> Result<MintQuote, Error> {
  174. let quote_res = self
  175. .client
  176. .post_mint_quote(mint_url.try_into()?, amount, unit.clone())
  177. .await?;
  178. let quote = MintQuote {
  179. id: quote_res.quote.clone(),
  180. amount,
  181. unit: unit.clone(),
  182. request: quote_res.request,
  183. paid: quote_res.paid,
  184. expiry: quote_res.expiry,
  185. };
  186. self.localstore.add_mint_quote(quote.clone()).await?;
  187. Ok(quote)
  188. }
  189. async fn active_mint_keyset(
  190. &mut self,
  191. mint_url: &UncheckedUrl,
  192. unit: &CurrencyUnit,
  193. ) -> Result<Option<Id>, Error> {
  194. if let Some(keysets) = self.localstore.get_mint_keysets(mint_url.clone()).await? {
  195. for keyset in keysets {
  196. if keyset.unit.eq(unit) && keyset.active {
  197. return Ok(Some(keyset.id));
  198. }
  199. }
  200. }
  201. let keysets = self.client.get_mint_keysets(mint_url.try_into()?).await?;
  202. self.localstore
  203. .add_mint_keysets(
  204. mint_url.clone(),
  205. keysets.keysets.clone().into_iter().collect(),
  206. )
  207. .await?;
  208. for keyset in &keysets.keysets {
  209. if keyset.unit.eq(unit) && keyset.active {
  210. return Ok(Some(keyset.id));
  211. }
  212. }
  213. Ok(None)
  214. }
  215. async fn active_keys(
  216. &mut self,
  217. mint_url: &UncheckedUrl,
  218. unit: &CurrencyUnit,
  219. ) -> Result<Option<Keys>, Error> {
  220. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  221. let keys;
  222. if let Some(k) = self.localstore.get_keys(&active_keyset_id).await? {
  223. keys = Some(k.clone())
  224. } else {
  225. let keyset = self
  226. .client
  227. .get_mint_keyset(mint_url.try_into()?, active_keyset_id)
  228. .await?;
  229. self.localstore.add_keys(keyset.keys.clone()).await?;
  230. keys = Some(keyset.keys);
  231. }
  232. Ok(keys)
  233. }
  234. /// Mint
  235. pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Amount, Error> {
  236. // Check that mint is in store of mints
  237. if self.localstore.get_mint(mint_url.clone()).await?.is_none() {
  238. self.add_mint(mint_url.clone()).await?;
  239. }
  240. let quote_info = self.localstore.get_mint_quote(quote_id).await?;
  241. let quote_info = if let Some(quote) = quote_info {
  242. if quote.expiry.le(&unix_time()) && quote.expiry.ne(&0) {
  243. return Err(Error::QuoteExpired);
  244. }
  245. quote.clone()
  246. } else {
  247. return Err(Error::QuoteUnknown);
  248. };
  249. let active_keyset_id = self
  250. .active_mint_keyset(&mint_url, &quote_info.unit)
  251. .await?
  252. .unwrap();
  253. let premint_secrets = match &self.backup_info {
  254. Some(backup_info) => PreMintSecrets::from_seed(
  255. active_keyset_id,
  256. *backup_info.counter.get(&active_keyset_id).unwrap_or(&0),
  257. &backup_info.mnemonic,
  258. quote_info.amount,
  259. )?,
  260. None => PreMintSecrets::random(active_keyset_id, quote_info.amount)?,
  261. };
  262. let mint_res = self
  263. .client
  264. .post_mint(
  265. mint_url.clone().try_into()?,
  266. quote_id,
  267. premint_secrets.clone(),
  268. )
  269. .await?;
  270. let keys = self.get_mint_keys(&mint_url, active_keyset_id).await?;
  271. let proofs = construct_proofs(
  272. mint_res.signatures,
  273. premint_secrets.rs(),
  274. premint_secrets.secrets(),
  275. &keys,
  276. )?;
  277. let minted_amount = proofs.iter().map(|p| p.amount).sum();
  278. // Remove filled quote from store
  279. self.localstore.remove_mint_quote(&quote_info.id).await?;
  280. // Add new proofs to store
  281. self.localstore.add_proofs(mint_url, proofs).await?;
  282. Ok(minted_amount)
  283. }
  284. /// Receive
  285. pub async fn receive(&mut self, encoded_token: &str) -> Result<(), Error> {
  286. let token_data = Token::from_str(encoded_token)?;
  287. let unit = token_data.unit.unwrap_or_default();
  288. let mut proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
  289. for token in token_data.token {
  290. if token.proofs.is_empty() {
  291. continue;
  292. }
  293. let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
  294. // TODO: if none fetch keyset for mint
  295. let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
  296. // Sum amount of all proofs
  297. let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  298. let pre_swap = self
  299. .create_swap(&token.mint, &unit, Some(amount), token.proofs)
  300. .await?;
  301. let swap_response = self
  302. .client
  303. .post_swap(token.mint.clone().try_into()?, pre_swap.split_request)
  304. .await?;
  305. // Proof to keep
  306. let p = construct_proofs(
  307. swap_response.signatures,
  308. pre_swap.pre_mint_secrets.rs(),
  309. pre_swap.pre_mint_secrets.secrets(),
  310. &keys.unwrap(),
  311. )?;
  312. let mint_proofs = proofs.entry(token.mint).or_default();
  313. mint_proofs.extend(p);
  314. }
  315. for (mint, proofs) in proofs {
  316. self.localstore.add_proofs(mint, proofs).await?;
  317. }
  318. Ok(())
  319. }
  320. /// Create Split Payload
  321. async fn create_swap(
  322. &mut self,
  323. mint_url: &UncheckedUrl,
  324. unit: &CurrencyUnit,
  325. amount: Option<Amount>,
  326. proofs: Proofs,
  327. ) -> Result<PreSwap, Error> {
  328. // Since split is used to get the needed combination of tokens for a specific
  329. // amount first blinded messages are created for the amount
  330. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  331. let pre_mint_secrets = if let Some(amount) = amount {
  332. let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
  333. let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
  334. let change_messages = PreMintSecrets::random(active_keyset_id, change_amount)?;
  335. // Combine the BlindedMessages totoalling the desired amount with change
  336. desired_messages.combine(change_messages);
  337. // Sort the premint secrets to avoid finger printing
  338. desired_messages.sort_secrets();
  339. desired_messages
  340. } else {
  341. let value = proofs.iter().map(|p| p.amount).sum();
  342. PreMintSecrets::random(active_keyset_id, value)?
  343. };
  344. let split_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
  345. Ok(PreSwap {
  346. pre_mint_secrets,
  347. split_request,
  348. })
  349. }
  350. pub async fn process_swap_response(
  351. &self,
  352. blinded_messages: PreMintSecrets,
  353. promises: Vec<BlindedSignature>,
  354. ) -> Result<Proofs, Error> {
  355. let mut proofs = vec![];
  356. for (promise, premint) in promises.iter().zip(blinded_messages) {
  357. let a = self
  358. .localstore
  359. .get_keys(&promise.keyset_id)
  360. .await?
  361. .unwrap()
  362. .amount_key(promise.amount)
  363. .unwrap()
  364. .to_owned();
  365. let blinded_c = promise.c.clone();
  366. let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap();
  367. let proof = Proof {
  368. keyset_id: promise.keyset_id,
  369. amount: promise.amount,
  370. secret: premint.secret,
  371. c: unblinded_sig,
  372. };
  373. proofs.push(proof);
  374. }
  375. Ok(proofs)
  376. }
  377. /// Send
  378. pub async fn send(
  379. &mut self,
  380. mint_url: &UncheckedUrl,
  381. unit: &CurrencyUnit,
  382. amount: Amount,
  383. ) -> Result<Proofs, Error> {
  384. let proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
  385. let pre_swap = self
  386. .create_swap(mint_url, unit, Some(amount), proofs.clone())
  387. .await?;
  388. let swap_response = self
  389. .client
  390. .post_swap(mint_url.clone().try_into()?, pre_swap.split_request)
  391. .await?;
  392. let mut keep_proofs = Proofs::new();
  393. let mut send_proofs = Proofs::new();
  394. let mut post_swap_proofs = construct_proofs(
  395. swap_response.signatures,
  396. pre_swap.pre_mint_secrets.rs(),
  397. pre_swap.pre_mint_secrets.secrets(),
  398. &self.active_keys(mint_url, unit).await?.unwrap(),
  399. )?;
  400. post_swap_proofs.reverse();
  401. for proof in post_swap_proofs {
  402. if (proof.amount + send_proofs.iter().map(|p| p.amount).sum()).gt(&amount) {
  403. keep_proofs.push(proof);
  404. } else {
  405. send_proofs.push(proof);
  406. }
  407. }
  408. let send_amount: Amount = send_proofs.iter().map(|p| p.amount).sum();
  409. if send_amount.ne(&amount) {
  410. warn!(
  411. "Send amount proofs is {:?} expected {:?}",
  412. send_amount, amount
  413. );
  414. }
  415. self.localstore
  416. .remove_proofs(mint_url.clone(), &proofs)
  417. .await?;
  418. self.localstore
  419. .add_pending_proofs(mint_url.clone(), proofs)
  420. .await?;
  421. self.localstore
  422. .add_proofs(mint_url.clone(), keep_proofs)
  423. .await?;
  424. Ok(send_proofs)
  425. }
  426. /// Melt Quote
  427. pub async fn melt_quote(
  428. &mut self,
  429. mint_url: UncheckedUrl,
  430. unit: CurrencyUnit,
  431. request: String,
  432. ) -> Result<MeltQuote, Error> {
  433. let quote_res = self
  434. .client
  435. .post_melt_quote(
  436. mint_url.clone().try_into()?,
  437. unit.clone(),
  438. Bolt11Invoice::from_str(&request.clone()).unwrap(),
  439. )
  440. .await?;
  441. let quote = MeltQuote {
  442. id: quote_res.quote,
  443. amount: quote_res.amount.into(),
  444. request,
  445. unit,
  446. fee_reserve: quote_res.fee_reserve.into(),
  447. paid: quote_res.paid,
  448. expiry: quote_res.expiry,
  449. };
  450. self.localstore.add_melt_quote(quote.clone()).await?;
  451. Ok(quote)
  452. }
  453. // Select proofs
  454. async fn select_proofs(
  455. &self,
  456. mint_url: UncheckedUrl,
  457. unit: &CurrencyUnit,
  458. amount: Amount,
  459. ) -> Result<Proofs, Error> {
  460. let mint_proofs = self
  461. .localstore
  462. .get_proofs(mint_url.clone())
  463. .await?
  464. .ok_or(Error::InsufficientFunds)?;
  465. let mint_keysets = self.localstore.get_mint_keysets(mint_url).await?.unwrap();
  466. let (active, inactive): (HashSet<KeySetInfo>, HashSet<KeySetInfo>) = mint_keysets
  467. .into_iter()
  468. .filter(|p| p.unit.eq(unit))
  469. .partition(|x| x.active);
  470. let active: HashSet<Id> = active.iter().map(|k| k.id).collect();
  471. let inactive: HashSet<Id> = inactive.iter().map(|k| k.id).collect();
  472. let mut active_proofs: Proofs = Vec::new();
  473. let mut inactive_proofs: Proofs = Vec::new();
  474. for proof in mint_proofs {
  475. if active.contains(&proof.keyset_id) {
  476. active_proofs.push(proof);
  477. } else if inactive.contains(&proof.keyset_id) {
  478. inactive_proofs.push(proof);
  479. }
  480. }
  481. active_proofs.reverse();
  482. inactive_proofs.reverse();
  483. inactive_proofs.append(&mut active_proofs);
  484. let proofs = inactive_proofs;
  485. let mut selected_proofs: Proofs = Vec::new();
  486. for proof in proofs {
  487. if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
  488. selected_proofs.push(proof);
  489. }
  490. }
  491. if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
  492. return Err(Error::InsufficientFunds);
  493. }
  494. Ok(selected_proofs)
  495. }
  496. /// Melt
  497. pub async fn melt(&mut self, mint_url: &UncheckedUrl, quote_id: &str) -> Result<Melted, Error> {
  498. let quote_info = self.localstore.get_melt_quote(quote_id).await?;
  499. let quote_info = if let Some(quote) = quote_info {
  500. if quote.expiry.le(&unix_time()) {
  501. return Err(Error::QuoteExpired);
  502. }
  503. quote.clone()
  504. } else {
  505. return Err(Error::QuoteUnknown);
  506. };
  507. let proofs = self
  508. .select_proofs(mint_url.clone(), &quote_info.unit, quote_info.amount)
  509. .await?;
  510. let proofs_amount = proofs.iter().map(|p| p.amount).sum();
  511. let blinded = PreMintSecrets::blank(
  512. self.active_mint_keyset(mint_url, &quote_info.unit)
  513. .await?
  514. .unwrap(),
  515. proofs_amount,
  516. )?;
  517. let melt_response = self
  518. .client
  519. .post_melt(
  520. mint_url.clone().try_into()?,
  521. quote_id.to_string(),
  522. proofs.clone(),
  523. Some(blinded.blinded_messages()),
  524. )
  525. .await?;
  526. let change_proofs = match melt_response.change {
  527. Some(change) => Some(construct_proofs(
  528. change,
  529. blinded.rs(),
  530. blinded.secrets(),
  531. &self.active_keys(mint_url, &quote_info.unit).await?.unwrap(),
  532. )?),
  533. None => None,
  534. };
  535. let melted = Melted {
  536. paid: true,
  537. preimage: melt_response.payment_preimage,
  538. change: change_proofs.clone(),
  539. };
  540. if let Some(change_proofs) = change_proofs {
  541. self.localstore
  542. .add_proofs(mint_url.clone(), change_proofs)
  543. .await?;
  544. }
  545. self.localstore.remove_melt_quote(&quote_info.id).await?;
  546. self.localstore
  547. .remove_proofs(mint_url.clone(), &proofs)
  548. .await?;
  549. Ok(melted)
  550. }
  551. pub fn proofs_to_token(
  552. &self,
  553. mint_url: UncheckedUrl,
  554. proofs: Proofs,
  555. memo: Option<String>,
  556. unit: Option<CurrencyUnit>,
  557. ) -> Result<String, Error> {
  558. Ok(Token::new(mint_url, proofs, memo, unit)?.to_string())
  559. }
  560. }
  561. /*
  562. #[cfg(test)]
  563. mod tests {
  564. use std::collections::{HashMap, HashSet};
  565. use super::*;
  566. use crate::client::Client;
  567. use crate::mint::Mint;
  568. use cashu::nuts::nut04;
  569. #[test]
  570. fn test_wallet() {
  571. let mut mint = Mint::new(
  572. "supersecretsecret",
  573. "0/0/0/0",
  574. HashMap::new(),
  575. HashSet::new(),
  576. 32,
  577. );
  578. let keys = mint.active_keyset_pubkeys();
  579. let client = Client::new("https://cashu-rs.thesimplekid.space/").unwrap();
  580. let wallet = Wallet::new(client, keys.keys);
  581. let blinded_messages = BlindedMessages::random(Amount::from_sat(64)).unwrap();
  582. let mint_request = nut04::MintRequest {
  583. outputs: blinded_messages.blinded_messages.clone(),
  584. };
  585. let res = mint.process_mint_request(mint_request).unwrap();
  586. let proofs = wallet
  587. .process_split_response(blinded_messages, res.promises)
  588. .unwrap();
  589. for proof in &proofs {
  590. mint.verify_proof(proof).unwrap();
  591. }
  592. let split = wallet.create_split(proofs.clone()).unwrap();
  593. let split_request = split.split_payload;
  594. let split_response = mint.process_split_request(split_request).unwrap();
  595. let p = split_response.promises;
  596. let snd_proofs = wallet
  597. .process_split_response(split.blinded_messages, p.unwrap())
  598. .unwrap();
  599. let mut error = false;
  600. for proof in &snd_proofs {
  601. if let Err(err) = mint.verify_proof(proof) {
  602. println!("{err}{:?}", serde_json::to_string(proof));
  603. error = true;
  604. }
  605. }
  606. if error {
  607. panic!()
  608. }
  609. }
  610. }
  611. */