mod.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. //! Client to connet to mint
  2. use async_trait::async_trait;
  3. use cashu::nuts::nut00::wallet::BlindedMessages;
  4. use cashu::nuts::nut00::{BlindedMessage, Proof};
  5. use cashu::nuts::nut01::Keys;
  6. use cashu::nuts::nut03::RequestMintResponse;
  7. use cashu::nuts::nut04::PostMintResponse;
  8. use cashu::nuts::nut05::CheckFeesResponse;
  9. use cashu::nuts::nut06::{SplitRequest, SplitResponse};
  10. #[cfg(feature = "nut07")]
  11. use cashu::nuts::nut07::CheckSpendableResponse;
  12. use cashu::nuts::nut08::MeltResponse;
  13. #[cfg(feature = "nut09")]
  14. use cashu::nuts::MintInfo;
  15. use cashu::nuts::*;
  16. use cashu::{utils, Amount};
  17. use serde::{Deserialize, Serialize};
  18. use thiserror::Error;
  19. use url::Url;
  20. #[cfg(feature = "gloo")]
  21. pub mod gloo_client;
  22. #[cfg(not(target_arch = "wasm32"))]
  23. pub mod minreq_client;
  24. pub use cashu::Bolt11Invoice;
  25. #[derive(Debug, Error)]
  26. pub enum Error {
  27. #[error("Invoice not paid")]
  28. InvoiceNotPaid,
  29. #[error("Wallet not responding")]
  30. LightingWalletNotResponding(Option<String>),
  31. /// Parse Url Error
  32. #[error("`{0}`")]
  33. UrlParse(#[from] url::ParseError),
  34. /// Serde Json error
  35. #[error("`{0}`")]
  36. SerdeJson(#[from] serde_json::Error),
  37. /// Cashu Url Error
  38. #[error("`{0}`")]
  39. CashuUrl(#[from] cashu::url::Error),
  40. /// Min req error
  41. #[cfg(not(target_arch = "wasm32"))]
  42. #[error("`{0}`")]
  43. MinReq(#[from] minreq::Error),
  44. #[cfg(feature = "gloo")]
  45. #[error("`{0}`")]
  46. Gloo(String),
  47. /// Custom Error
  48. #[error("`{0}`")]
  49. Custom(String),
  50. }
  51. impl Error {
  52. pub fn from_json(json: &str) -> Result<Self, Error> {
  53. let mint_res: MintErrorResponse = serde_json::from_str(json)?;
  54. let err = mint_res
  55. .error
  56. .as_deref()
  57. .or(mint_res.detail.as_deref())
  58. .unwrap_or_default();
  59. let mint_error = match err {
  60. error if error.starts_with("Lightning invoice not paid yet.") => Error::InvoiceNotPaid,
  61. error if error.starts_with("Lightning wallet not responding") => {
  62. let mint = utils::extract_url_from_error(error);
  63. Error::LightingWalletNotResponding(mint)
  64. }
  65. error => Error::Custom(error.to_owned()),
  66. };
  67. Ok(mint_error)
  68. }
  69. }
  70. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  71. pub struct MintErrorResponse {
  72. code: u32,
  73. error: Option<String>,
  74. detail: Option<String>,
  75. }
  76. #[async_trait(?Send)]
  77. pub trait Client {
  78. async fn get_mint_keys(&self, mint_url: Url) -> Result<Keys, Error>;
  79. async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error>;
  80. async fn get_request_mint(
  81. &self,
  82. mint_url: Url,
  83. amount: Amount,
  84. ) -> Result<RequestMintResponse, Error>;
  85. // TODO: Hash should have a type
  86. async fn post_mint(
  87. &self,
  88. mint_url: Url,
  89. blinded_messages: BlindedMessages,
  90. hash: &str,
  91. ) -> Result<PostMintResponse, Error>;
  92. async fn post_check_fees(
  93. &self,
  94. mint_url: Url,
  95. invoice: Bolt11Invoice,
  96. ) -> Result<CheckFeesResponse, Error>;
  97. async fn post_melt(
  98. &self,
  99. mint_url: Url,
  100. proofs: Vec<Proof>,
  101. invoice: Bolt11Invoice,
  102. outputs: Option<Vec<BlindedMessage>>,
  103. ) -> Result<MeltResponse, Error>;
  104. // REVIEW: Should be consistent aboue passing in the Request struct or the
  105. // compnatants and making it within the function. Here the struct is passed
  106. // in but in check spendable and melt the compants are passed in
  107. async fn post_split(
  108. &self,
  109. mint_url: Url,
  110. split_request: SplitRequest,
  111. ) -> Result<SplitResponse, Error>;
  112. #[cfg(feature = "nut07")]
  113. async fn post_check_spendable(
  114. &self,
  115. mint_url: Url,
  116. proofs: Vec<nut00::mint::Proof>,
  117. ) -> Result<CheckSpendableResponse, Error>;
  118. #[cfg(feature = "nut09")]
  119. async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error>;
  120. }
  121. #[cfg(any(not(target_arch = "wasm32"), feature = "gloo"))]
  122. fn join_url(url: Url, path: &str) -> Result<Url, Error> {
  123. let mut url = url;
  124. if !url.path().ends_with('/') {
  125. url.path_segments_mut()
  126. .map_err(|_| Error::Custom("Url Path Segmants".to_string()))?
  127. .push(path);
  128. } else {
  129. url.path_segments_mut()
  130. .map_err(|_| Error::Custom("Url Path Segmants".to_string()))?
  131. .pop()
  132. .push(path);
  133. }
  134. Ok(url)
  135. }
  136. #[cfg(test)]
  137. mod tests {
  138. use super::*;
  139. #[test]
  140. fn test_decode_error() {
  141. let err = r#"{"code":0,"error":"Lightning invoice not paid yet."}"#;
  142. let error = Error::from_json(err).unwrap();
  143. match error {
  144. Error::InvoiceNotPaid => {}
  145. _ => panic!("Wrong error"),
  146. }
  147. let err = r#"{"code": 0, "error": "Lightning wallet not responding: Failed to connect to https://legend.lnbits.com due to: All connection attempts failed"}"#;
  148. let error = Error::from_json(err).unwrap();
  149. match error {
  150. Error::LightingWalletNotResponding(mint) => {
  151. assert_eq!(mint, Some("https://legend.lnbits.com".to_string()));
  152. }
  153. _ => panic!("Wrong error"),
  154. }
  155. }
  156. }