wallet.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. //! Cashu Wallet
  2. use std::error::Error as StdError;
  3. use std::fmt;
  4. use std::str::FromStr;
  5. use cashu::dhke::construct_proofs;
  6. use cashu::dhke::unblind_message;
  7. use cashu::nuts::nut00::{
  8. mint, wallet::BlindedMessages, wallet::Token, BlindedSignature, Proof, Proofs,
  9. };
  10. use cashu::nuts::nut01::Keys;
  11. use cashu::nuts::nut03::RequestMintResponse;
  12. use cashu::nuts::nut06::{SplitPayload, SplitRequest};
  13. use cashu::types::{Melted, ProofsStatus, SendProofs};
  14. use cashu::Amount;
  15. pub use cashu::Bolt11Invoice;
  16. use tracing::warn;
  17. #[cfg(feature = "blocking")]
  18. use crate::client::blocking::Client;
  19. #[cfg(not(feature = "blocking"))]
  20. use crate::client::Client;
  21. #[derive(Debug)]
  22. pub enum Error {
  23. /// Insufficaint Funds
  24. InsufficantFunds,
  25. Cashu(cashu::error::wallet::Error),
  26. Client(crate::client::Error),
  27. Custom(String),
  28. }
  29. impl StdError for Error {}
  30. impl fmt::Display for Error {
  31. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  32. match self {
  33. Error::InsufficantFunds => write!(f, "Insufficant Funds"),
  34. Error::Cashu(err) => write!(f, "{}", err),
  35. Error::Client(err) => write!(f, "{}", err),
  36. Error::Custom(err) => write!(f, "{}", err),
  37. }
  38. }
  39. }
  40. impl From<cashu::error::wallet::Error> for Error {
  41. fn from(err: cashu::error::wallet::Error) -> Self {
  42. Self::Cashu(err)
  43. }
  44. }
  45. impl From<crate::client::Error> for Error {
  46. fn from(err: crate::client::Error) -> Error {
  47. Error::Client(err)
  48. }
  49. }
  50. #[derive(Clone, Debug)]
  51. pub struct Wallet {
  52. pub client: Client,
  53. pub mint_keys: Keys,
  54. pub balance: Amount,
  55. }
  56. impl Wallet {
  57. pub fn new(client: Client, mint_keys: Keys) -> Self {
  58. Self {
  59. client,
  60. mint_keys,
  61. balance: Amount::ZERO,
  62. }
  63. }
  64. // TODO: getter method for keys that if it cant get them try again
  65. /// Check if a proof is spent
  66. #[cfg(not(feature = "blocking"))]
  67. pub async fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
  68. let spendable = self.client.check_spendable(proofs).await?;
  69. // Separate proofs in spent and unspent based on mint response
  70. let (spendable, spent): (Vec<_>, Vec<_>) = proofs
  71. .iter()
  72. .zip(spendable.spendable.iter())
  73. .partition(|(_, &b)| b);
  74. Ok(ProofsStatus {
  75. spendable: spendable.into_iter().map(|(s, _)| s).cloned().collect(),
  76. spent: spent.into_iter().map(|(s, _)| s).cloned().collect(),
  77. })
  78. }
  79. /// Check if a proof is spent
  80. #[cfg(feature = "blocking")]
  81. pub fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
  82. let spendable = self.client.check_spendable(proofs)?;
  83. // Separate proofs in spent and unspent based on mint response
  84. let (spendable, spent): (Vec<_>, Vec<_>) = proofs
  85. .iter()
  86. .zip(spendable.spendable.iter())
  87. .partition(|(_, &b)| b);
  88. Ok(ProofsStatus {
  89. spendable: spendable.into_iter().map(|(s, _)| s).cloned().collect(),
  90. spent: spent.into_iter().map(|(s, _)| s).cloned().collect(),
  91. })
  92. }
  93. /// Request Token Mint
  94. #[cfg(not(feature = "blocking"))]
  95. pub async fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
  96. Ok(self.client.request_mint(amount).await?)
  97. }
  98. /// Request Token Mint
  99. #[cfg(feature = "blocking")]
  100. pub fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
  101. Ok(self.client.request_mint(amount)?)
  102. }
  103. /// Mint Token
  104. #[cfg(not(feature = "blocking"))]
  105. pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result<Token, Error> {
  106. let proofs = self.mint(amount, hash).await?;
  107. let token = Token::new(self.client.mint_url.clone(), proofs, None);
  108. Ok(token)
  109. }
  110. /// Blocking Mint Token
  111. #[cfg(feature = "blocking")]
  112. pub fn mint_token(&self, amount: Amount, hash: &str) -> Result<Token, Error> {
  113. let proofs = self.mint(amount, hash)?;
  114. let token = Token::new(self.client.client.mint_url.clone(), proofs, None);
  115. Ok(token)
  116. }
  117. /// Mint Proofs
  118. #[cfg(not(feature = "blocking"))]
  119. pub async fn mint(&self, amount: Amount, hash: &str) -> Result<Proofs, Error> {
  120. let blinded_messages = BlindedMessages::random(amount)?;
  121. let mint_res = self.client.mint(blinded_messages.clone(), hash).await?;
  122. let proofs = construct_proofs(
  123. mint_res.promises,
  124. blinded_messages.rs,
  125. blinded_messages.secrets,
  126. &self.mint_keys,
  127. )?;
  128. Ok(proofs)
  129. }
  130. /// Blocking Mint Proofs
  131. #[cfg(feature = "blocking")]
  132. pub fn mint(&self, amount: Amount, hash: &str) -> Result<Proofs, Error> {
  133. let blinded_messages = BlindedMessages::random(amount)?;
  134. let mint_res = self.client.mint(blinded_messages.clone(), hash)?;
  135. let proofs = construct_proofs(
  136. mint_res.promises,
  137. blinded_messages.rs,
  138. blinded_messages.secrets,
  139. &self.mint_keys,
  140. )?;
  141. Ok(proofs)
  142. }
  143. /// Check fee
  144. #[cfg(not(feature = "blocking"))]
  145. pub async fn check_fee(&self, invoice: Bolt11Invoice) -> Result<Amount, Error> {
  146. Ok(self.client.check_fees(invoice).await?.fee)
  147. }
  148. /// Check fee
  149. #[cfg(feature = "blocking")]
  150. pub fn check_fee(&self, invoice: Bolt11Invoice) -> Result<Amount, Error> {
  151. Ok(self.client.check_fees(invoice)?.fee)
  152. }
  153. /// Receive
  154. #[cfg(not(feature = "blocking"))]
  155. pub async fn receive(&self, encoded_token: &str) -> Result<Proofs, Error> {
  156. let token_data = Token::from_str(encoded_token)?;
  157. let mut proofs: Vec<Proofs> = vec![vec![]];
  158. for token in token_data.token {
  159. if token.proofs.is_empty() {
  160. continue;
  161. }
  162. let keys = if token.mint.to_string().eq(&self.client.mint_url.to_string()) {
  163. self.mint_keys.clone()
  164. } else {
  165. Client::new(token.mint.as_str())?.get_keys().await?
  166. };
  167. // Sum amount of all proofs
  168. let _amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  169. let split_payload = self.create_split(token.proofs)?;
  170. let split_response = self.client.split(split_payload.split_payload).await?;
  171. if let Some(promises) = &split_response.promises {
  172. // Proof to keep
  173. let p = construct_proofs(
  174. promises.to_owned(),
  175. split_payload.blinded_messages.rs,
  176. split_payload.blinded_messages.secrets,
  177. &keys,
  178. )?;
  179. proofs.push(p);
  180. } else {
  181. warn!("Response missing promises");
  182. return Err(Error::Custom("Split response missing promises".to_string()));
  183. }
  184. }
  185. Ok(proofs.iter().flatten().cloned().collect())
  186. }
  187. /// Blocking Receive
  188. #[cfg(feature = "blocking")]
  189. pub fn receive(&self, encoded_token: &str) -> Result<Proofs, Error> {
  190. let token_data = Token::from_str(encoded_token)?;
  191. let mut proofs: Vec<Proofs> = vec![vec![]];
  192. for token in token_data.token {
  193. if token.proofs.is_empty() {
  194. continue;
  195. }
  196. let keys = if token
  197. .mint
  198. .to_string()
  199. .eq(&self.client.client.mint_url.to_string())
  200. {
  201. self.mint_keys.clone()
  202. } else {
  203. Client::new(token.mint.as_str())?.get_keys()?
  204. };
  205. // Sum amount of all proofs
  206. let _amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  207. let split_payload = self.create_split(token.proofs)?;
  208. let split_response = self.client.split(split_payload.split_payload)?;
  209. if let Some(promises) = &split_response.promises {
  210. // Proof to keep
  211. let p = construct_proofs(
  212. promises.to_owned(),
  213. split_payload.blinded_messages.rs,
  214. split_payload.blinded_messages.secrets,
  215. &keys,
  216. )?;
  217. proofs.push(p);
  218. } else {
  219. warn!("Response missing promises");
  220. return Err(Error::Custom("Split response missing promises".to_string()));
  221. }
  222. }
  223. Ok(proofs.iter().flatten().cloned().collect())
  224. }
  225. /// Create Split Payload
  226. fn create_split(&self, proofs: Proofs) -> Result<SplitPayload, Error> {
  227. let value = proofs.iter().map(|p| p.amount).sum();
  228. let blinded_messages = BlindedMessages::random(value)?;
  229. let split_payload = SplitRequest {
  230. amount: None,
  231. proofs,
  232. outputs: blinded_messages.blinded_messages.clone(),
  233. };
  234. Ok(SplitPayload {
  235. blinded_messages,
  236. split_payload,
  237. })
  238. }
  239. pub fn process_split_response(
  240. &self,
  241. blinded_messages: BlindedMessages,
  242. promises: Vec<BlindedSignature>,
  243. ) -> Result<Proofs, Error> {
  244. let BlindedMessages {
  245. blinded_messages: _,
  246. secrets,
  247. rs,
  248. amounts: _,
  249. } = blinded_messages;
  250. let secrets: Vec<_> = secrets.iter().collect();
  251. let mut proofs = vec![];
  252. for (i, promise) in promises.iter().enumerate() {
  253. let a = self
  254. .mint_keys
  255. .amount_key(promise.amount)
  256. .unwrap()
  257. .to_owned();
  258. let blinded_c = promise.c.clone();
  259. let unblinded_sig = unblind_message(blinded_c, rs[i].clone().into(), a).unwrap();
  260. let proof = Proof {
  261. id: Some(promise.id.clone()),
  262. amount: promise.amount,
  263. secret: secrets[i].clone(),
  264. c: unblinded_sig,
  265. };
  266. proofs.push(proof);
  267. }
  268. Ok(proofs)
  269. }
  270. /// Send
  271. #[cfg(not(feature = "blocking"))]
  272. pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result<SendProofs, Error> {
  273. let mut amount_available = Amount::ZERO;
  274. let mut send_proofs = SendProofs::default();
  275. for proof in proofs {
  276. let proof_value = proof.amount;
  277. if amount_available > amount {
  278. send_proofs.change_proofs.push(proof);
  279. } else {
  280. send_proofs.send_proofs.push(proof);
  281. }
  282. amount_available += proof_value;
  283. }
  284. if amount_available.lt(&amount) {
  285. println!("Not enough funds");
  286. return Err(Error::InsufficantFunds);
  287. }
  288. // If amount available is EQUAL to send amount no need to split
  289. if amount_available.eq(&amount) {
  290. return Ok(send_proofs);
  291. }
  292. let _amount_to_keep = amount_available - amount;
  293. let amount_to_send = amount;
  294. let split_payload = self.create_split(send_proofs.send_proofs)?;
  295. let split_response = self.client.split(split_payload.split_payload).await?;
  296. // If only promises assemble proofs needed for amount
  297. let keep_proofs;
  298. let send_proofs;
  299. if let Some(promises) = split_response.promises {
  300. let proofs = construct_proofs(
  301. promises,
  302. split_payload.blinded_messages.rs,
  303. split_payload.blinded_messages.secrets,
  304. &self.mint_keys,
  305. )?;
  306. let split = amount_to_send.split();
  307. keep_proofs = proofs[0..split.len()].to_vec();
  308. send_proofs = proofs[split.len()..].to_vec();
  309. } else {
  310. return Err(Error::Custom("Invalid split response".to_string()));
  311. }
  312. // println!("Send Proofs: {:#?}", send_proofs);
  313. // println!("Keep Proofs: {:#?}", keep_proofs);
  314. Ok(SendProofs {
  315. change_proofs: keep_proofs,
  316. send_proofs,
  317. })
  318. }
  319. /// Send
  320. #[cfg(feature = "blocking")]
  321. pub fn send(&self, amount: Amount, proofs: Proofs) -> Result<SendProofs, Error> {
  322. let mut amount_available = Amount::ZERO;
  323. let mut send_proofs = SendProofs::default();
  324. for proof in proofs {
  325. let proof_value = proof.amount;
  326. if amount_available > amount {
  327. send_proofs.change_proofs.push(proof);
  328. } else {
  329. send_proofs.send_proofs.push(proof);
  330. }
  331. amount_available += proof_value;
  332. }
  333. if amount_available.lt(&amount) {
  334. println!("Not enough funds");
  335. return Err(Error::InsufficantFunds);
  336. }
  337. // If amount available is EQUAL to send amount no need to split
  338. if amount_available.eq(&amount) {
  339. return Ok(send_proofs);
  340. }
  341. let _amount_to_keep = amount_available - amount;
  342. let amount_to_send = amount;
  343. let split_payload = self.create_split(send_proofs.send_proofs)?;
  344. let split_response = self.client.split(split_payload.split_payload)?;
  345. // If only promises assemble proofs needed for amount
  346. let keep_proofs;
  347. let send_proofs;
  348. if let Some(promises) = split_response.promises {
  349. let proofs = construct_proofs(
  350. promises,
  351. split_payload.blinded_messages.rs,
  352. split_payload.blinded_messages.secrets,
  353. &self.mint_keys,
  354. )?;
  355. let split = amount_to_send.split();
  356. keep_proofs = proofs[0..split.len()].to_vec();
  357. send_proofs = proofs[split.len()..].to_vec();
  358. } else {
  359. return Err(Error::Custom("Invalid split response".to_string()));
  360. }
  361. // println!("Send Proofs: {:#?}", send_proofs);
  362. // println!("Keep Proofs: {:#?}", keep_proofs);
  363. Ok(SendProofs {
  364. change_proofs: keep_proofs,
  365. send_proofs,
  366. })
  367. }
  368. #[cfg(not(feature = "blocking"))]
  369. pub async fn melt(
  370. &self,
  371. invoice: Bolt11Invoice,
  372. proofs: Proofs,
  373. fee_reserve: Amount,
  374. ) -> Result<Melted, Error> {
  375. let blinded = BlindedMessages::blank(fee_reserve)?;
  376. let melt_response = self
  377. .client
  378. .melt(proofs, invoice, Some(blinded.blinded_messages))
  379. .await?;
  380. let change_proofs = match melt_response.change {
  381. Some(change) => Some(construct_proofs(
  382. change,
  383. blinded.rs,
  384. blinded.secrets,
  385. &self.mint_keys,
  386. )?),
  387. None => None,
  388. };
  389. let melted = Melted {
  390. paid: true,
  391. preimage: melt_response.preimage,
  392. change: change_proofs,
  393. };
  394. Ok(melted)
  395. }
  396. #[cfg(feature = "blocking")]
  397. pub fn melt(
  398. &self,
  399. invoice: Bolt11Invoice,
  400. proofs: Proofs,
  401. fee_reserve: Amount,
  402. ) -> Result<Melted, Error> {
  403. let blinded = BlindedMessages::blank(fee_reserve)?;
  404. let melt_response = self
  405. .client
  406. .melt(proofs, invoice, Some(blinded.blinded_messages))?;
  407. let change_proofs = match melt_response.change {
  408. Some(change) => Some(construct_proofs(
  409. change,
  410. blinded.rs,
  411. blinded.secrets,
  412. &self.mint_keys,
  413. )?),
  414. None => None,
  415. };
  416. let melted = Melted {
  417. paid: true,
  418. preimage: melt_response.preimage,
  419. change: change_proofs,
  420. };
  421. Ok(melted)
  422. }
  423. #[cfg(not(feature = "blocking"))]
  424. pub fn proofs_to_token(&self, proofs: Proofs, memo: Option<String>) -> Result<String, Error> {
  425. Ok(Token::new(self.client.mint_url.clone(), proofs, memo).convert_to_string()?)
  426. }
  427. #[cfg(feature = "blocking")]
  428. pub fn proofs_to_token(&self, proofs: Proofs, memo: Option<String>) -> Result<String, Error> {
  429. Ok(Token::new(self.client.client.mint_url.clone(), proofs, memo).convert_to_string()?)
  430. }
  431. }
  432. /*
  433. #[cfg(test)]
  434. mod tests {
  435. use std::collections::{HashMap, HashSet};
  436. use super::*;
  437. use crate::client::Client;
  438. use crate::mint::Mint;
  439. use cashu::nuts::nut04;
  440. #[test]
  441. fn test_wallet() {
  442. let mut mint = Mint::new(
  443. "supersecretsecret",
  444. "0/0/0/0",
  445. HashMap::new(),
  446. HashSet::new(),
  447. 32,
  448. );
  449. let keys = mint.active_keyset_pubkeys();
  450. let client = Client::new("https://cashu-rs.thesimplekid.space/").unwrap();
  451. let wallet = Wallet::new(client, keys.keys);
  452. let blinded_messages = BlindedMessages::random(Amount::from_sat(64)).unwrap();
  453. let mint_request = nut04::MintRequest {
  454. outputs: blinded_messages.blinded_messages.clone(),
  455. };
  456. let res = mint.process_mint_request(mint_request).unwrap();
  457. let proofs = wallet
  458. .process_split_response(blinded_messages, res.promises)
  459. .unwrap();
  460. for proof in &proofs {
  461. mint.verify_proof(proof).unwrap();
  462. }
  463. let split = wallet.create_split(proofs.clone()).unwrap();
  464. let split_request = split.split_payload;
  465. let split_response = mint.process_split_request(split_request).unwrap();
  466. let p = split_response.promises;
  467. let snd_proofs = wallet
  468. .process_split_response(split.blinded_messages, p.unwrap())
  469. .unwrap();
  470. let mut error = false;
  471. for proof in &snd_proofs {
  472. if let Err(err) = mint.verify_proof(proof) {
  473. println!("{err}{:?}", serde_json::to_string(proof));
  474. error = true;
  475. }
  476. }
  477. if error {
  478. panic!()
  479. }
  480. }
  481. }
  482. */