mint.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. //! Mint types
  2. use bitcoin::bip32::DerivationPath;
  3. use cashu::quote_id::QuoteId;
  4. use cashu::util::unix_time;
  5. use cashu::{
  6. Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
  7. MintQuoteBolt12Response, PaymentMethod,
  8. };
  9. use lightning::offers::offer::Offer;
  10. use serde::{Deserialize, Serialize};
  11. use tracing::instrument;
  12. use uuid::Uuid;
  13. use crate::nuts::{MeltQuoteState, MintQuoteState};
  14. use crate::payment::PaymentIdentifier;
  15. use crate::{Amount, CurrencyUnit, Id, KeySetInfo, PublicKey};
  16. /// Mint Quote Info
  17. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  18. pub struct MintQuote {
  19. /// Quote id
  20. pub id: QuoteId,
  21. /// Amount of quote
  22. pub amount: Option<Amount>,
  23. /// Unit of quote
  24. pub unit: CurrencyUnit,
  25. /// Quote payment request e.g. bolt11
  26. pub request: String,
  27. /// Expiration time of quote
  28. pub expiry: u64,
  29. /// Value used by ln backend to look up state of request
  30. pub request_lookup_id: PaymentIdentifier,
  31. /// Pubkey
  32. pub pubkey: Option<PublicKey>,
  33. /// Unix time quote was created
  34. #[serde(default)]
  35. pub created_time: u64,
  36. /// Amount paid
  37. #[serde(default)]
  38. amount_paid: Amount,
  39. /// Amount issued
  40. #[serde(default)]
  41. amount_issued: Amount,
  42. /// Payment of payment(s) that filled quote
  43. #[serde(default)]
  44. pub payments: Vec<IncomingPayment>,
  45. /// Payment Method
  46. #[serde(default)]
  47. pub payment_method: PaymentMethod,
  48. /// Payment of payment(s) that filled quote
  49. #[serde(default)]
  50. pub issuance: Vec<Issuance>,
  51. }
  52. impl MintQuote {
  53. /// Create new [`MintQuote`]
  54. #[allow(clippy::too_many_arguments)]
  55. pub fn new(
  56. id: Option<QuoteId>,
  57. request: String,
  58. unit: CurrencyUnit,
  59. amount: Option<Amount>,
  60. expiry: u64,
  61. request_lookup_id: PaymentIdentifier,
  62. pubkey: Option<PublicKey>,
  63. amount_paid: Amount,
  64. amount_issued: Amount,
  65. payment_method: PaymentMethod,
  66. created_time: u64,
  67. payments: Vec<IncomingPayment>,
  68. issuance: Vec<Issuance>,
  69. ) -> Self {
  70. let id = id.unwrap_or_else(QuoteId::new_uuid);
  71. Self {
  72. id,
  73. amount,
  74. unit,
  75. request,
  76. expiry,
  77. request_lookup_id,
  78. pubkey,
  79. created_time,
  80. amount_paid,
  81. amount_issued,
  82. payment_method,
  83. payments,
  84. issuance,
  85. }
  86. }
  87. /// Increment the amount paid on the mint quote by a given amount
  88. #[instrument(skip(self))]
  89. pub fn increment_amount_paid(
  90. &mut self,
  91. additional_amount: Amount,
  92. ) -> Result<Amount, crate::Error> {
  93. self.amount_paid = self
  94. .amount_paid
  95. .checked_add(additional_amount)
  96. .ok_or(crate::Error::AmountOverflow)?;
  97. Ok(self.amount_paid)
  98. }
  99. /// Amount paid
  100. #[instrument(skip(self))]
  101. pub fn amount_paid(&self) -> Amount {
  102. self.amount_paid
  103. }
  104. /// Increment the amount issued on the mint quote by a given amount
  105. #[instrument(skip(self))]
  106. pub fn increment_amount_issued(
  107. &mut self,
  108. additional_amount: Amount,
  109. ) -> Result<Amount, crate::Error> {
  110. self.amount_issued = self
  111. .amount_issued
  112. .checked_add(additional_amount)
  113. .ok_or(crate::Error::AmountOverflow)?;
  114. Ok(self.amount_issued)
  115. }
  116. /// Amount issued
  117. #[instrument(skip(self))]
  118. pub fn amount_issued(&self) -> Amount {
  119. self.amount_issued
  120. }
  121. /// Get state of mint quote
  122. #[instrument(skip(self))]
  123. pub fn state(&self) -> MintQuoteState {
  124. self.compute_quote_state()
  125. }
  126. /// Existing payment ids of a mint quote
  127. pub fn payment_ids(&self) -> Vec<&String> {
  128. self.payments.iter().map(|a| &a.payment_id).collect()
  129. }
  130. /// Add a payment ID to the list of payment IDs
  131. ///
  132. /// Returns an error if the payment ID is already in the list
  133. #[instrument(skip(self))]
  134. pub fn add_payment(
  135. &mut self,
  136. amount: Amount,
  137. payment_id: String,
  138. time: u64,
  139. ) -> Result<(), crate::Error> {
  140. let payment_ids = self.payment_ids();
  141. if payment_ids.contains(&&payment_id) {
  142. return Err(crate::Error::DuplicatePaymentId);
  143. }
  144. let payment = IncomingPayment::new(amount, payment_id, time);
  145. self.payments.push(payment);
  146. Ok(())
  147. }
  148. /// Compute quote state
  149. #[instrument(skip(self))]
  150. fn compute_quote_state(&self) -> MintQuoteState {
  151. if self.amount_paid == Amount::ZERO && self.amount_issued == Amount::ZERO {
  152. return MintQuoteState::Unpaid;
  153. }
  154. match self.amount_paid.cmp(&self.amount_issued) {
  155. std::cmp::Ordering::Less => {
  156. // self.amount_paid is less than other (amount issued)
  157. // Handle case where paid amount is insufficient
  158. tracing::error!("We should not have issued more then has been paid");
  159. MintQuoteState::Issued
  160. }
  161. std::cmp::Ordering::Equal => {
  162. // We do this extra check for backwards compatibility for quotes where amount paid/issed was not tracked
  163. // self.amount_paid equals other (amount issued)
  164. // Handle case where paid amount exactly matches
  165. MintQuoteState::Issued
  166. }
  167. std::cmp::Ordering::Greater => {
  168. // self.amount_paid is greater than other (amount issued)
  169. // Handle case where paid amount exceeds required amount
  170. MintQuoteState::Paid
  171. }
  172. }
  173. }
  174. }
  175. /// Mint Payments
  176. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  177. pub struct IncomingPayment {
  178. /// Amount
  179. pub amount: Amount,
  180. /// Pyament unix time
  181. pub time: u64,
  182. /// Payment id
  183. pub payment_id: String,
  184. }
  185. impl IncomingPayment {
  186. /// New [`IncomingPayment`]
  187. pub fn new(amount: Amount, payment_id: String, time: u64) -> Self {
  188. Self {
  189. payment_id,
  190. time,
  191. amount,
  192. }
  193. }
  194. }
  195. /// Informattion about issued quote
  196. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  197. pub struct Issuance {
  198. /// Amount
  199. pub amount: Amount,
  200. /// Time
  201. pub time: u64,
  202. }
  203. impl Issuance {
  204. /// Create new [`Issuance`]
  205. pub fn new(amount: Amount, time: u64) -> Self {
  206. Self { amount, time }
  207. }
  208. }
  209. /// Melt Quote Info
  210. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  211. pub struct MeltQuote {
  212. /// Quote id
  213. pub id: QuoteId,
  214. /// Quote unit
  215. pub unit: CurrencyUnit,
  216. /// Quote amount
  217. pub amount: Amount,
  218. /// Quote Payment request e.g. bolt11
  219. pub request: MeltPaymentRequest,
  220. /// Quote fee reserve
  221. pub fee_reserve: Amount,
  222. /// Quote state
  223. pub state: MeltQuoteState,
  224. /// Expiration time of quote
  225. pub expiry: u64,
  226. /// Payment preimage
  227. pub payment_preimage: Option<String>,
  228. /// Value used by ln backend to look up state of request
  229. pub request_lookup_id: Option<PaymentIdentifier>,
  230. /// Payment options
  231. ///
  232. /// Used for amountless invoices and MPP payments
  233. pub options: Option<MeltOptions>,
  234. /// Unix time quote was created
  235. #[serde(default)]
  236. pub created_time: u64,
  237. /// Unix time quote was paid
  238. pub paid_time: Option<u64>,
  239. /// Payment method
  240. #[serde(default)]
  241. pub payment_method: PaymentMethod,
  242. }
  243. impl MeltQuote {
  244. /// Create new [`MeltQuote`]
  245. #[allow(clippy::too_many_arguments)]
  246. pub fn new(
  247. request: MeltPaymentRequest,
  248. unit: CurrencyUnit,
  249. amount: Amount,
  250. fee_reserve: Amount,
  251. expiry: u64,
  252. request_lookup_id: Option<PaymentIdentifier>,
  253. options: Option<MeltOptions>,
  254. payment_method: PaymentMethod,
  255. ) -> Self {
  256. let id = Uuid::new_v4();
  257. Self {
  258. id: QuoteId::UUID(id),
  259. amount,
  260. unit,
  261. request,
  262. fee_reserve,
  263. state: MeltQuoteState::Unpaid,
  264. expiry,
  265. payment_preimage: None,
  266. request_lookup_id,
  267. options,
  268. created_time: unix_time(),
  269. paid_time: None,
  270. payment_method,
  271. }
  272. }
  273. }
  274. /// Mint Keyset Info
  275. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  276. pub struct MintKeySetInfo {
  277. /// Keyset [`Id`]
  278. pub id: Id,
  279. /// Keyset [`CurrencyUnit`]
  280. pub unit: CurrencyUnit,
  281. /// Keyset active or inactive
  282. /// Mint will only issue new signatures on active keysets
  283. pub active: bool,
  284. /// Starting unix time Keyset is valid from
  285. pub valid_from: u64,
  286. /// [`DerivationPath`] keyset
  287. pub derivation_path: DerivationPath,
  288. /// DerivationPath index of Keyset
  289. pub derivation_path_index: Option<u32>,
  290. /// Max order of keyset
  291. pub max_order: u8,
  292. /// Supported amounts
  293. pub amounts: Vec<u64>,
  294. /// Input Fee ppk
  295. #[serde(default = "default_fee")]
  296. pub input_fee_ppk: u64,
  297. /// Final expiry
  298. pub final_expiry: Option<u64>,
  299. }
  300. /// Default fee
  301. pub fn default_fee() -> u64 {
  302. 0
  303. }
  304. impl From<MintKeySetInfo> for KeySetInfo {
  305. fn from(keyset_info: MintKeySetInfo) -> Self {
  306. Self {
  307. id: keyset_info.id,
  308. unit: keyset_info.unit,
  309. active: keyset_info.active,
  310. input_fee_ppk: keyset_info.input_fee_ppk,
  311. final_expiry: keyset_info.final_expiry,
  312. }
  313. }
  314. }
  315. impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
  316. fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
  317. MintQuoteBolt11Response {
  318. quote: mint_quote.id.clone(),
  319. state: mint_quote.state(),
  320. request: mint_quote.request,
  321. expiry: Some(mint_quote.expiry),
  322. pubkey: mint_quote.pubkey,
  323. amount: mint_quote.amount,
  324. unit: Some(mint_quote.unit.clone()),
  325. }
  326. }
  327. }
  328. impl From<MintQuote> for MintQuoteBolt11Response<String> {
  329. fn from(quote: MintQuote) -> Self {
  330. let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
  331. quote.into()
  332. }
  333. }
  334. impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
  335. type Error = crate::Error;
  336. fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
  337. Ok(MintQuoteBolt12Response {
  338. quote: mint_quote.id.clone(),
  339. request: mint_quote.request,
  340. expiry: Some(mint_quote.expiry),
  341. amount_paid: mint_quote.amount_paid,
  342. amount_issued: mint_quote.amount_issued,
  343. pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
  344. amount: mint_quote.amount,
  345. unit: mint_quote.unit,
  346. })
  347. }
  348. }
  349. impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
  350. type Error = crate::Error;
  351. fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
  352. let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
  353. Ok(quote.into())
  354. }
  355. }
  356. impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  357. fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  358. MeltQuoteBolt11Response {
  359. quote: melt_quote.id.clone(),
  360. payment_preimage: None,
  361. change: None,
  362. state: melt_quote.state,
  363. paid: Some(melt_quote.state == MeltQuoteState::Paid),
  364. expiry: melt_quote.expiry,
  365. amount: melt_quote.amount,
  366. fee_reserve: melt_quote.fee_reserve,
  367. request: None,
  368. unit: Some(melt_quote.unit.clone()),
  369. }
  370. }
  371. }
  372. impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  373. fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  374. let paid = melt_quote.state == MeltQuoteState::Paid;
  375. MeltQuoteBolt11Response {
  376. quote: melt_quote.id.clone(),
  377. amount: melt_quote.amount,
  378. fee_reserve: melt_quote.fee_reserve,
  379. paid: Some(paid),
  380. state: melt_quote.state,
  381. expiry: melt_quote.expiry,
  382. payment_preimage: melt_quote.payment_preimage,
  383. change: None,
  384. request: Some(melt_quote.request.to_string()),
  385. unit: Some(melt_quote.unit.clone()),
  386. }
  387. }
  388. }
  389. /// Payment request
  390. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  391. pub enum MeltPaymentRequest {
  392. /// Bolt11 Payment
  393. Bolt11 {
  394. /// Bolt11 invoice
  395. bolt11: Bolt11Invoice,
  396. },
  397. /// Bolt12 Payment
  398. Bolt12 {
  399. /// Offer
  400. #[serde(with = "offer_serde")]
  401. offer: Box<Offer>,
  402. },
  403. }
  404. impl std::fmt::Display for MeltPaymentRequest {
  405. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  406. match self {
  407. MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
  408. MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
  409. }
  410. }
  411. }
  412. mod offer_serde {
  413. use std::str::FromStr;
  414. use serde::{self, Deserialize, Deserializer, Serializer};
  415. use super::Offer;
  416. pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
  417. where
  418. S: Serializer,
  419. {
  420. let s = offer.to_string();
  421. serializer.serialize_str(&s)
  422. }
  423. pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
  424. where
  425. D: Deserializer<'de>,
  426. {
  427. let s = String::deserialize(deserializer)?;
  428. Ok(Box::new(Offer::from_str(&s).map_err(|_| {
  429. serde::de::Error::custom("Invalid Bolt12 Offer")
  430. })?))
  431. }
  432. }