nut23.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. //! Bolt11
  2. use std::fmt;
  3. use std::str::FromStr;
  4. use lightning_invoice::Bolt11Invoice;
  5. use serde::de::DeserializeOwned;
  6. use serde::{Deserialize, Deserializer, Serialize};
  7. use serde_json::Value;
  8. use thiserror::Error;
  9. use super::{BlindSignature, CurrencyUnit, MeltQuoteState, Mpp, PublicKey};
  10. #[cfg(feature = "mint")]
  11. use crate::quote_id::QuoteId;
  12. use crate::Amount;
  13. /// NUT023 Error
  14. #[derive(Debug, Error)]
  15. pub enum Error {
  16. /// Unknown Quote State
  17. #[error("Unknown Quote State")]
  18. UnknownState,
  19. /// Amount overflow
  20. #[error("Amount overflow")]
  21. AmountOverflow,
  22. /// Invalid Amount
  23. #[error("Invalid Request")]
  24. InvalidAmountRequest,
  25. }
  26. /// Mint quote request [NUT-04]
  27. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  28. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  29. pub struct MintQuoteBolt11Request {
  30. /// Amount
  31. pub amount: Amount,
  32. /// Unit wallet would like to pay with
  33. pub unit: CurrencyUnit,
  34. /// Memo to create the invoice with
  35. #[serde(skip_serializing_if = "Option::is_none")]
  36. pub description: Option<String>,
  37. /// NUT-19 Pubkey
  38. #[serde(skip_serializing_if = "Option::is_none")]
  39. pub pubkey: Option<PublicKey>,
  40. }
  41. /// Possible states of a quote
  42. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
  43. #[serde(rename_all = "UPPERCASE")]
  44. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MintQuoteState))]
  45. pub enum QuoteState {
  46. /// Quote has not been paid
  47. #[default]
  48. Unpaid,
  49. /// Quote has been paid and wallet can mint
  50. Paid,
  51. /// ecash issued for quote
  52. Issued,
  53. }
  54. impl fmt::Display for QuoteState {
  55. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  56. match self {
  57. Self::Unpaid => write!(f, "UNPAID"),
  58. Self::Paid => write!(f, "PAID"),
  59. Self::Issued => write!(f, "ISSUED"),
  60. }
  61. }
  62. }
  63. impl FromStr for QuoteState {
  64. type Err = Error;
  65. fn from_str(state: &str) -> Result<Self, Self::Err> {
  66. match state {
  67. "PAID" => Ok(Self::Paid),
  68. "UNPAID" => Ok(Self::Unpaid),
  69. "ISSUED" => Ok(Self::Issued),
  70. _ => Err(Error::UnknownState),
  71. }
  72. }
  73. }
  74. /// Mint quote response [NUT-04]
  75. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  76. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  77. #[serde(bound = "Q: Serialize + DeserializeOwned")]
  78. pub struct MintQuoteBolt11Response<Q> {
  79. /// Quote Id
  80. pub quote: Q,
  81. /// Payment request to fulfil
  82. pub request: String,
  83. /// Amount
  84. // REVIEW: This is now required in the spec, we should remove the option once all mints update
  85. pub amount: Option<Amount>,
  86. /// Unit
  87. // REVIEW: This is now required in the spec, we should remove the option once all mints update
  88. pub unit: Option<CurrencyUnit>,
  89. /// Quote State
  90. pub state: QuoteState,
  91. /// Unix timestamp until the quote is valid
  92. pub expiry: Option<u64>,
  93. /// NUT-19 Pubkey
  94. #[serde(skip_serializing_if = "Option::is_none")]
  95. pub pubkey: Option<PublicKey>,
  96. }
  97. impl<Q: ToString> MintQuoteBolt11Response<Q> {
  98. /// Convert the MintQuote with a quote type Q to a String
  99. pub fn to_string_id(&self) -> MintQuoteBolt11Response<String> {
  100. MintQuoteBolt11Response {
  101. quote: self.quote.to_string(),
  102. request: self.request.clone(),
  103. state: self.state,
  104. expiry: self.expiry,
  105. pubkey: self.pubkey,
  106. amount: self.amount,
  107. unit: self.unit.clone(),
  108. }
  109. }
  110. }
  111. #[cfg(feature = "mint")]
  112. impl From<MintQuoteBolt11Response<QuoteId>> for MintQuoteBolt11Response<String> {
  113. fn from(value: MintQuoteBolt11Response<QuoteId>) -> Self {
  114. Self {
  115. quote: value.quote.to_string(),
  116. request: value.request,
  117. state: value.state,
  118. expiry: value.expiry,
  119. pubkey: value.pubkey,
  120. amount: value.amount,
  121. unit: value.unit.clone(),
  122. }
  123. }
  124. }
  125. /// BOLT11 melt quote request [NUT-23]
  126. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  127. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  128. pub struct MeltQuoteBolt11Request {
  129. /// Bolt11 invoice to be paid
  130. #[cfg_attr(feature = "swagger", schema(value_type = String))]
  131. pub request: Bolt11Invoice,
  132. /// Unit wallet would like to pay with
  133. pub unit: CurrencyUnit,
  134. /// Payment Options
  135. pub options: Option<MeltOptions>,
  136. }
  137. /// Melt Options
  138. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
  139. #[serde(untagged)]
  140. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  141. pub enum MeltOptions {
  142. /// Mpp Options
  143. Mpp {
  144. /// MPP
  145. mpp: Mpp,
  146. },
  147. /// Amountless options
  148. Amountless {
  149. /// Amountless
  150. amountless: Amountless,
  151. },
  152. }
  153. impl MeltOptions {
  154. /// Create new [`MeltOptions::Mpp`]
  155. pub fn new_mpp<A>(amount: A) -> Self
  156. where
  157. A: Into<Amount>,
  158. {
  159. Self::Mpp {
  160. mpp: Mpp {
  161. amount: amount.into(),
  162. },
  163. }
  164. }
  165. /// Create new [`MeltOptions::Amountless`]
  166. pub fn new_amountless<A>(amount_msat: A) -> Self
  167. where
  168. A: Into<Amount>,
  169. {
  170. Self::Amountless {
  171. amountless: Amountless {
  172. amount_msat: amount_msat.into(),
  173. },
  174. }
  175. }
  176. /// Payment amount
  177. pub fn amount_msat(&self) -> Amount {
  178. match self {
  179. Self::Mpp { mpp } => mpp.amount,
  180. Self::Amountless { amountless } => amountless.amount_msat,
  181. }
  182. }
  183. }
  184. /// Amountless payment
  185. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
  186. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  187. pub struct Amountless {
  188. /// Amount to pay in msat
  189. pub amount_msat: Amount,
  190. }
  191. impl MeltQuoteBolt11Request {
  192. /// Amount from [`MeltQuoteBolt11Request`]
  193. ///
  194. /// Amount can either be defined in the bolt11 invoice,
  195. /// in the request for an amountless bolt11 or in MPP option.
  196. pub fn amount_msat(&self) -> Result<Amount, Error> {
  197. let MeltQuoteBolt11Request {
  198. request,
  199. unit: _,
  200. options,
  201. ..
  202. } = self;
  203. match options {
  204. None => Ok(request
  205. .amount_milli_satoshis()
  206. .ok_or(Error::InvalidAmountRequest)?
  207. .into()),
  208. Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
  209. Some(MeltOptions::Amountless { amountless }) => {
  210. let amount = amountless.amount_msat;
  211. if let Some(amount_msat) = request.amount_milli_satoshis() {
  212. if amount != amount_msat.into() {
  213. return Err(Error::InvalidAmountRequest);
  214. }
  215. }
  216. Ok(amount)
  217. }
  218. }
  219. }
  220. }
  221. /// Melt quote response [NUT-05]
  222. #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
  223. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  224. #[serde(bound = "Q: Serialize")]
  225. pub struct MeltQuoteBolt11Response<Q> {
  226. /// Quote Id
  227. pub quote: Q,
  228. /// The amount that needs to be provided
  229. pub amount: Amount,
  230. /// The fee reserve that is required
  231. pub fee_reserve: Amount,
  232. /// Whether the request haas be paid
  233. // TODO: To be deprecated
  234. /// Deprecated
  235. pub paid: Option<bool>,
  236. /// Quote State
  237. pub state: MeltQuoteState,
  238. /// Unix timestamp until the quote is valid
  239. pub expiry: u64,
  240. /// Payment preimage
  241. #[serde(skip_serializing_if = "Option::is_none")]
  242. pub payment_preimage: Option<String>,
  243. /// Change
  244. #[serde(skip_serializing_if = "Option::is_none")]
  245. pub change: Option<Vec<BlindSignature>>,
  246. /// Payment request to fulfill
  247. // REVIEW: This is now required in the spec, we should remove the option once all mints update
  248. #[serde(skip_serializing_if = "Option::is_none")]
  249. pub request: Option<String>,
  250. /// Unit
  251. // REVIEW: This is now required in the spec, we should remove the option once all mints update
  252. #[serde(skip_serializing_if = "Option::is_none")]
  253. pub unit: Option<CurrencyUnit>,
  254. }
  255. impl<Q: ToString> MeltQuoteBolt11Response<Q> {
  256. /// Convert a `MeltQuoteBolt11Response` with type Q (generic/unknown) to a
  257. /// `MeltQuoteBolt11Response` with `String`
  258. pub fn to_string_id(self) -> MeltQuoteBolt11Response<String> {
  259. MeltQuoteBolt11Response {
  260. quote: self.quote.to_string(),
  261. amount: self.amount,
  262. fee_reserve: self.fee_reserve,
  263. paid: self.paid,
  264. state: self.state,
  265. expiry: self.expiry,
  266. payment_preimage: self.payment_preimage,
  267. change: self.change,
  268. request: self.request,
  269. unit: self.unit,
  270. }
  271. }
  272. }
  273. #[cfg(feature = "mint")]
  274. impl From<MeltQuoteBolt11Response<QuoteId>> for MeltQuoteBolt11Response<String> {
  275. fn from(value: MeltQuoteBolt11Response<QuoteId>) -> Self {
  276. Self {
  277. quote: value.quote.to_string(),
  278. amount: value.amount,
  279. fee_reserve: value.fee_reserve,
  280. paid: value.paid,
  281. state: value.state,
  282. expiry: value.expiry,
  283. payment_preimage: value.payment_preimage,
  284. change: value.change,
  285. request: value.request,
  286. unit: value.unit,
  287. }
  288. }
  289. }
  290. // A custom deserializer is needed until all mints
  291. // update some will return without the required state.
  292. impl<'de, Q: DeserializeOwned> Deserialize<'de> for MeltQuoteBolt11Response<Q> {
  293. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  294. where
  295. D: Deserializer<'de>,
  296. {
  297. let value = Value::deserialize(deserializer)?;
  298. let quote: Q = serde_json::from_value(
  299. value
  300. .get("quote")
  301. .ok_or(serde::de::Error::missing_field("quote"))?
  302. .clone(),
  303. )
  304. .map_err(|_| serde::de::Error::custom("Invalid quote if string"))?;
  305. let amount = value
  306. .get("amount")
  307. .ok_or(serde::de::Error::missing_field("amount"))?
  308. .as_u64()
  309. .ok_or(serde::de::Error::missing_field("amount"))?;
  310. let amount = Amount::from(amount);
  311. let fee_reserve = value
  312. .get("fee_reserve")
  313. .ok_or(serde::de::Error::missing_field("fee_reserve"))?
  314. .as_u64()
  315. .ok_or(serde::de::Error::missing_field("fee_reserve"))?;
  316. let fee_reserve = Amount::from(fee_reserve);
  317. let paid: Option<bool> = value.get("paid").and_then(|p| p.as_bool());
  318. let state: Option<String> = value
  319. .get("state")
  320. .and_then(|s| serde_json::from_value(s.clone()).ok());
  321. let (state, paid) = match (state, paid) {
  322. (None, None) => return Err(serde::de::Error::custom("State or paid must be defined")),
  323. (Some(state), _) => {
  324. let state: MeltQuoteState = MeltQuoteState::from_str(&state)
  325. .map_err(|_| serde::de::Error::custom("Unknown state"))?;
  326. let paid = state == MeltQuoteState::Paid;
  327. (state, paid)
  328. }
  329. (None, Some(paid)) => {
  330. let state = if paid {
  331. MeltQuoteState::Paid
  332. } else {
  333. MeltQuoteState::Unpaid
  334. };
  335. (state, paid)
  336. }
  337. };
  338. let expiry = value
  339. .get("expiry")
  340. .ok_or(serde::de::Error::missing_field("expiry"))?
  341. .as_u64()
  342. .ok_or(serde::de::Error::missing_field("expiry"))?;
  343. let payment_preimage: Option<String> = value
  344. .get("payment_preimage")
  345. .and_then(|p| serde_json::from_value(p.clone()).ok());
  346. let change: Option<Vec<BlindSignature>> = value
  347. .get("change")
  348. .and_then(|b| serde_json::from_value(b.clone()).ok());
  349. let request: Option<String> = value
  350. .get("request")
  351. .and_then(|r| serde_json::from_value(r.clone()).ok());
  352. let unit: Option<CurrencyUnit> = value
  353. .get("unit")
  354. .and_then(|u| serde_json::from_value(u.clone()).ok());
  355. Ok(Self {
  356. quote,
  357. amount,
  358. fee_reserve,
  359. paid: Some(paid),
  360. state,
  361. expiry,
  362. payment_preimage,
  363. change,
  364. request,
  365. unit,
  366. })
  367. }
  368. }