nut05.rs 12 KB

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