nut05.rs 12 KB

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