mod.rs 4.7 KB

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