client.rs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. //! Client to connet to mint
  2. use std::fmt;
  3. use bitcoin::Amount;
  4. use serde_json::Value;
  5. use url::Url;
  6. pub use crate::Invoice;
  7. use crate::{
  8. keyset::{Keys, MintKeySets},
  9. types::{
  10. BlindedMessage, BlindedMessages, CheckFeesRequest, CheckFeesResponse,
  11. CheckSpendableRequest, CheckSpendableResponse, MeltRequest, MeltResponse, MintInfo,
  12. MintRequest, PostMintResponse, Proof, RequestMintResponse, SplitRequest, SplitResponse,
  13. },
  14. utils,
  15. };
  16. use serde::{Deserialize, Serialize};
  17. #[derive(Debug)]
  18. pub enum Error {
  19. InvoiceNotPaid,
  20. LightingWalletNotResponding(Option<String>),
  21. /// Parse Url Error
  22. UrlParseError(url::ParseError),
  23. /// Serde Json error
  24. SerdeJsonError(serde_json::Error),
  25. /// Min req error
  26. MinReqError(minreq::Error),
  27. /// Custom Error
  28. Custom(String),
  29. }
  30. impl From<url::ParseError> for Error {
  31. fn from(err: url::ParseError) -> Error {
  32. Error::UrlParseError(err)
  33. }
  34. }
  35. impl From<serde_json::Error> for Error {
  36. fn from(err: serde_json::Error) -> Error {
  37. Error::SerdeJsonError(err)
  38. }
  39. }
  40. impl From<minreq::Error> for Error {
  41. fn from(err: minreq::Error) -> Error {
  42. Error::MinReqError(err)
  43. }
  44. }
  45. impl std::error::Error for Error {}
  46. impl fmt::Display for Error {
  47. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  48. match self {
  49. Error::InvoiceNotPaid => write!(f, "Invoice not paid"),
  50. Error::LightingWalletNotResponding(mint) => {
  51. write!(
  52. f,
  53. "Lightning Wallet not responding: {}",
  54. mint.clone().unwrap_or("".to_string())
  55. )
  56. }
  57. Error::UrlParseError(err) => write!(f, "{}", err),
  58. Error::SerdeJsonError(err) => write!(f, "{}", err),
  59. Error::MinReqError(err) => write!(f, "{}", err),
  60. Error::Custom(message) => write!(f, "{}", message),
  61. }
  62. }
  63. }
  64. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  65. pub struct MintErrorResponse {
  66. code: u32,
  67. error: String,
  68. }
  69. impl Error {
  70. pub fn from_json(json: &str) -> Result<Self, Error> {
  71. let mint_res: MintErrorResponse = serde_json::from_str(json)?;
  72. let mint_error = match mint_res.error {
  73. error if error.starts_with("Lightning invoice not paid yet.") => Error::InvoiceNotPaid,
  74. error if error.starts_with("Lightning wallet not responding") => {
  75. let mint = utils::extract_url_from_error(&error);
  76. Error::LightingWalletNotResponding(mint)
  77. }
  78. error => Error::Custom(error),
  79. };
  80. Ok(mint_error)
  81. }
  82. }
  83. #[derive(Debug, Clone)]
  84. pub struct Client {
  85. pub mint_url: Url,
  86. }
  87. impl Client {
  88. pub fn new(mint_url: &str) -> Result<Self, Error> {
  89. // HACK
  90. let mut mint_url = String::from(mint_url);
  91. if !mint_url.ends_with('/') {
  92. mint_url.push('/');
  93. }
  94. let mint_url = Url::parse(&mint_url)?;
  95. Ok(Self { mint_url })
  96. }
  97. /// Get Mint Keys [NUT-01]
  98. pub async fn get_keys(&self) -> Result<Keys, Error> {
  99. let url = self.mint_url.join("keys")?;
  100. let keys = minreq::get(url).send()?.json::<Value>()?;
  101. let keys: Keys = serde_json::from_str(&keys.to_string())?;
  102. /*
  103. let keys: BTreeMap<u64, String> = match serde_json::from_value(keys.clone()) {
  104. Ok(keys) => keys,
  105. Err(_err) => {
  106. return Err(Error::CustomError(format!(
  107. "url: {}, {}",
  108. url,
  109. serde_json::to_string(&keys)?
  110. )))
  111. }
  112. };
  113. let mint_keys: BTreeMap<u64, PublicKey> = keys
  114. .into_iter()
  115. .filter_map(|(k, v)| {
  116. let key = hex::decode(v).ok()?;
  117. let public_key = PublicKey::from_sec1_bytes(&key).ok()?;
  118. Some((k, public_key))
  119. })
  120. .collect();
  121. */
  122. Ok(keys)
  123. }
  124. /// Get Keysets [NUT-02]
  125. pub async fn get_keysets(&self) -> Result<MintKeySets, Error> {
  126. let url = self.mint_url.join("keysets")?;
  127. let res = minreq::get(url).send()?.json::<Value>()?;
  128. let response: Result<MintKeySets, serde_json::Error> = serde_json::from_value(res.clone());
  129. match response {
  130. Ok(res) => Ok(res),
  131. Err(_) => Err(Error::from_json(&res.to_string())?),
  132. }
  133. }
  134. /// Request Mint [NUT-03]
  135. pub async fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
  136. let mut url = self.mint_url.join("mint")?;
  137. url.query_pairs_mut()
  138. .append_pair("amount", &amount.to_sat().to_string());
  139. let res = minreq::get(url).send()?.json::<Value>()?;
  140. let response: Result<RequestMintResponse, serde_json::Error> =
  141. serde_json::from_value(res.clone());
  142. match response {
  143. Ok(res) => Ok(res),
  144. Err(_) => Err(Error::from_json(&res.to_string())?),
  145. }
  146. }
  147. /// Mint Tokens [NUT-04]
  148. pub async fn mint(
  149. &self,
  150. blinded_messages: BlindedMessages,
  151. hash: &str,
  152. ) -> Result<PostMintResponse, Error> {
  153. let mut url = self.mint_url.join("mint")?;
  154. url.query_pairs_mut().append_pair("hash", hash);
  155. let request = MintRequest {
  156. outputs: blinded_messages.blinded_messages,
  157. };
  158. let res = minreq::post(url)
  159. .with_json(&request)?
  160. .send()?
  161. .json::<Value>()?;
  162. let response: Result<PostMintResponse, serde_json::Error> =
  163. serde_json::from_value(res.clone());
  164. match response {
  165. Ok(res) => Ok(res),
  166. Err(_) => Err(Error::from_json(&res.to_string())?),
  167. }
  168. }
  169. /// Check Max expected fee [NUT-05]
  170. pub async fn check_fees(&self, invoice: Invoice) -> Result<CheckFeesResponse, Error> {
  171. let url = self.mint_url.join("checkfees")?;
  172. let request = CheckFeesRequest { pr: invoice };
  173. let res = minreq::post(url)
  174. .with_json(&request)?
  175. .send()?
  176. .json::<Value>()?;
  177. let response: Result<CheckFeesResponse, serde_json::Error> =
  178. serde_json::from_value(res.clone());
  179. match response {
  180. Ok(res) => Ok(res),
  181. Err(_) => Err(Error::from_json(&res.to_string())?),
  182. }
  183. }
  184. /// Melt [NUT-05]
  185. /// [Nut-08] Lightning fee return if outputs defined
  186. pub async fn melt(
  187. &self,
  188. proofs: Vec<Proof>,
  189. invoice: Invoice,
  190. outputs: Option<Vec<BlindedMessage>>,
  191. ) -> Result<MeltResponse, Error> {
  192. let url = self.mint_url.join("melt")?;
  193. let request = MeltRequest {
  194. proofs,
  195. pr: invoice,
  196. outputs,
  197. };
  198. let value = minreq::post(url)
  199. .with_json(&request)?
  200. .send()?
  201. .json::<Value>()?;
  202. let response: Result<MeltResponse, serde_json::Error> =
  203. serde_json::from_value(value.clone());
  204. match response {
  205. Ok(res) => Ok(res),
  206. Err(_) => Err(Error::from_json(&value.to_string())?),
  207. }
  208. }
  209. /// Split Token [NUT-06]
  210. pub async fn split(&self, split_request: SplitRequest) -> Result<SplitResponse, Error> {
  211. let url = self.mint_url.join("split")?;
  212. let res = minreq::post(url)
  213. .with_json(&split_request)?
  214. .send()?
  215. .json::<Value>()?;
  216. let response: Result<SplitResponse, serde_json::Error> =
  217. serde_json::from_value(res.clone());
  218. match response {
  219. Ok(res) => Ok(res),
  220. Err(_) => Err(Error::from_json(&res.to_string())?),
  221. }
  222. }
  223. /// Spendable check [NUT-07]
  224. pub async fn check_spendable(
  225. &self,
  226. proofs: &Vec<Proof>,
  227. ) -> Result<CheckSpendableResponse, Error> {
  228. let url = self.mint_url.join("check")?;
  229. let request = CheckSpendableRequest {
  230. proofs: proofs.to_owned(),
  231. };
  232. let res = minreq::post(url)
  233. .with_json(&request)?
  234. .send()?
  235. .json::<Value>()?;
  236. let response: Result<CheckSpendableResponse, serde_json::Error> =
  237. serde_json::from_value(res.clone());
  238. match response {
  239. Ok(res) => Ok(res),
  240. Err(_) => Err(Error::from_json(&res.to_string())?),
  241. }
  242. }
  243. /// Get Mint Info [NUT-09]
  244. pub async fn get_info(&self) -> Result<MintInfo, Error> {
  245. let url = self.mint_url.join("info")?;
  246. let res = minreq::get(url).send()?.json::<Value>()?;
  247. let response: Result<MintInfo, serde_json::Error> = serde_json::from_value(res.clone());
  248. match response {
  249. Ok(res) => Ok(res),
  250. Err(_) => Err(Error::from_json(&res.to_string())?),
  251. }
  252. }
  253. }
  254. #[cfg(test)]
  255. mod tests {
  256. use super::*;
  257. #[test]
  258. fn test_decode_error() {
  259. let err = r#"{"code":0,"error":"Lightning invoice not paid yet."}"#;
  260. let error = Error::from_json(err).unwrap();
  261. match error {
  262. Error::InvoiceNotPaid => {}
  263. _ => panic!("Wrong error"),
  264. }
  265. let err = r#"{"code": 0, "error": "Lightning wallet not responding: Failed to connect to https://legend.lnbits.com due to: All connection attempts failed"}"#;
  266. let error = Error::from_json(err).unwrap();
  267. match error {
  268. Error::LightingWalletNotResponding(mint) => {
  269. assert_eq!(mint, Some("https://legend.lnbits.com".to_string()));
  270. }
  271. _ => panic!("Wrong error"),
  272. }
  273. }
  274. }