mod.rs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. //! Client to connet to mint
  2. use async_trait::async_trait;
  3. use cashu::error::ErrorResponse;
  4. #[cfg(feature = "nut07")]
  5. use cashu::nuts::CheckStateResponse;
  6. use cashu::nuts::{
  7. BlindedMessage, CurrencyUnit, Id, KeySet, KeysetResponse, MeltBolt11Response,
  8. MeltQuoteBolt11Response, MintBolt11Response, MintInfo, MintQuoteBolt11Response, PreMintSecrets,
  9. Proof, SwapRequest, SwapResponse,
  10. };
  11. #[cfg(feature = "nut07")]
  12. use cashu::secret::Secret;
  13. use cashu::Amount;
  14. use thiserror::Error;
  15. use url::Url;
  16. #[cfg(feature = "gloo")]
  17. pub mod gloo_client;
  18. #[cfg(not(target_arch = "wasm32"))]
  19. pub mod minreq_client;
  20. pub use cashu::Bolt11Invoice;
  21. #[derive(Debug, Error)]
  22. pub enum Error {
  23. #[error("Invoice not paid")]
  24. InvoiceNotPaid,
  25. #[error("Wallet not responding")]
  26. LightingWalletNotResponding(Option<String>),
  27. /// Parse Url Error
  28. #[error("`{0}`")]
  29. UrlParse(#[from] url::ParseError),
  30. /// Serde Json error
  31. #[error("`{0}`")]
  32. SerdeJson(#[from] serde_json::Error),
  33. /// Cashu Url Error
  34. #[error("`{0}`")]
  35. CashuUrl(#[from] cashu::url::Error),
  36. /// Min req error
  37. #[cfg(not(target_arch = "wasm32"))]
  38. #[error("`{0}`")]
  39. MinReq(#[from] minreq::Error),
  40. #[cfg(feature = "gloo")]
  41. #[error("`{0}`")]
  42. Gloo(String),
  43. #[error("Unknown Error response")]
  44. UnknownErrorResponse(cashu::error::ErrorResponse),
  45. /// Custom Error
  46. #[error("`{0}`")]
  47. Custom(String),
  48. }
  49. impl From<ErrorResponse> for Error {
  50. fn from(err: ErrorResponse) -> Error {
  51. Self::UnknownErrorResponse(err)
  52. }
  53. }
  54. #[async_trait(?Send)]
  55. pub trait Client {
  56. async fn get_mint_keys(&self, mint_url: Url) -> Result<Vec<KeySet>, Error>;
  57. async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error>;
  58. async fn get_mint_keyset(&self, mint_url: Url, keyset_id: Id) -> Result<KeySet, Error>;
  59. async fn post_mint_quote(
  60. &self,
  61. mint_url: Url,
  62. amount: Amount,
  63. unit: CurrencyUnit,
  64. ) -> Result<MintQuoteBolt11Response, Error>;
  65. async fn post_mint(
  66. &self,
  67. mint_url: Url,
  68. quote: &str,
  69. premint_secrets: PreMintSecrets,
  70. ) -> Result<MintBolt11Response, Error>;
  71. async fn post_melt_quote(
  72. &self,
  73. mint_url: Url,
  74. unit: CurrencyUnit,
  75. request: Bolt11Invoice,
  76. ) -> Result<MeltQuoteBolt11Response, Error>;
  77. async fn post_melt(
  78. &self,
  79. mint_url: Url,
  80. quote: String,
  81. inputs: Vec<Proof>,
  82. outputs: Option<Vec<BlindedMessage>>,
  83. ) -> Result<MeltBolt11Response, Error>;
  84. // REVIEW: Should be consistent aboue passing in the Request struct or the
  85. // compnatants and making it within the function. Here the struct is passed
  86. // in but in check spendable and melt the compants are passed in
  87. async fn post_swap(
  88. &self,
  89. mint_url: Url,
  90. split_request: SwapRequest,
  91. ) -> Result<SwapResponse, Error>;
  92. #[cfg(feature = "nut07")]
  93. async fn post_check_state(
  94. &self,
  95. mint_url: Url,
  96. secrets: Vec<Secret>,
  97. ) -> Result<CheckStateResponse, Error>;
  98. async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error>;
  99. }
  100. #[cfg(any(not(target_arch = "wasm32"), feature = "gloo"))]
  101. fn join_url(url: Url, paths: &[&str]) -> Result<Url, Error> {
  102. let mut url = url;
  103. for path in paths {
  104. if !url.path().ends_with('/') {
  105. url.path_segments_mut()
  106. .map_err(|_| Error::Custom("Url Path Segmants".to_string()))?
  107. .push(path);
  108. } else {
  109. url.path_segments_mut()
  110. .map_err(|_| Error::Custom("Url Path Segmants".to_string()))?
  111. .pop()
  112. .push(path);
  113. }
  114. }
  115. Ok(url)
  116. }
  117. #[cfg(test)]
  118. mod tests {
  119. /*
  120. use super::*;
  121. #[test]
  122. fn test_decode_error() {
  123. let err = r#"{"code":0,"error":"Lightning invoice not paid yet."}"#;
  124. let error = Error::from_json(err).unwrap();
  125. match error {
  126. Error::InvoiceNotPaid => {}
  127. _ => panic!("Wrong error"),
  128. }
  129. let err = r#"{"code": 0, "error": "Lightning wallet not responding: Failed to connect to https://legend.lnbits.com due to: All connection attempts failed"}"#;
  130. let error = Error::from_json(err).unwrap();
  131. match error {
  132. Error::LightingWalletNotResponding(mint) => {
  133. assert_eq!(mint, Some("https://legend.lnbits.com".to_string()));
  134. }
  135. _ => panic!("Wrong error"),
  136. }
  137. }
  138. */
  139. }