mint.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  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. /// Amount mintable
  131. /// Returns the amount that is still available for minting.
  132. ///
  133. /// The value is computed as the difference between the total amount that
  134. /// has been paid for this issuance (`self.amount_paid`) and the amount
  135. /// that has already been issued (`self.amount_issued`). In other words,
  136. pub fn amount_mintable(&self) -> Amount {
  137. self.amount_paid - self.amount_issued
  138. }
  139. /// Add a payment ID to the list of payment IDs
  140. ///
  141. /// Returns an error if the payment ID is already in the list
  142. #[instrument(skip(self))]
  143. pub fn add_payment(
  144. &mut self,
  145. amount: Amount,
  146. payment_id: String,
  147. time: u64,
  148. ) -> Result<(), crate::Error> {
  149. let payment_ids = self.payment_ids();
  150. if payment_ids.contains(&&payment_id) {
  151. return Err(crate::Error::DuplicatePaymentId);
  152. }
  153. let payment = IncomingPayment::new(amount, payment_id, time);
  154. self.payments.push(payment);
  155. Ok(())
  156. }
  157. /// Compute quote state
  158. #[instrument(skip(self))]
  159. fn compute_quote_state(&self) -> MintQuoteState {
  160. if self.amount_paid == Amount::ZERO && self.amount_issued == Amount::ZERO {
  161. return MintQuoteState::Unpaid;
  162. }
  163. match self.amount_paid.cmp(&self.amount_issued) {
  164. std::cmp::Ordering::Less => {
  165. // self.amount_paid is less than other (amount issued)
  166. // Handle case where paid amount is insufficient
  167. tracing::error!("We should not have issued more then has been paid");
  168. MintQuoteState::Issued
  169. }
  170. std::cmp::Ordering::Equal => {
  171. // We do this extra check for backwards compatibility for quotes where amount paid/issed was not tracked
  172. // self.amount_paid equals other (amount issued)
  173. // Handle case where paid amount exactly matches
  174. MintQuoteState::Issued
  175. }
  176. std::cmp::Ordering::Greater => {
  177. // self.amount_paid is greater than other (amount issued)
  178. // Handle case where paid amount exceeds required amount
  179. MintQuoteState::Paid
  180. }
  181. }
  182. }
  183. }
  184. /// Mint Payments
  185. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  186. pub struct IncomingPayment {
  187. /// Amount
  188. pub amount: Amount,
  189. /// Pyament unix time
  190. pub time: u64,
  191. /// Payment id
  192. pub payment_id: String,
  193. }
  194. impl IncomingPayment {
  195. /// New [`IncomingPayment`]
  196. pub fn new(amount: Amount, payment_id: String, time: u64) -> Self {
  197. Self {
  198. payment_id,
  199. time,
  200. amount,
  201. }
  202. }
  203. }
  204. /// Informattion about issued quote
  205. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  206. pub struct Issuance {
  207. /// Amount
  208. pub amount: Amount,
  209. /// Time
  210. pub time: u64,
  211. }
  212. impl Issuance {
  213. /// Create new [`Issuance`]
  214. pub fn new(amount: Amount, time: u64) -> Self {
  215. Self { amount, time }
  216. }
  217. }
  218. /// Melt Quote Info
  219. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  220. pub struct MeltQuote {
  221. /// Quote id
  222. pub id: QuoteId,
  223. /// Quote unit
  224. pub unit: CurrencyUnit,
  225. /// Quote amount
  226. pub amount: Amount,
  227. /// Quote Payment request e.g. bolt11
  228. pub request: MeltPaymentRequest,
  229. /// Quote fee reserve
  230. pub fee_reserve: Amount,
  231. /// Quote state
  232. pub state: MeltQuoteState,
  233. /// Expiration time of quote
  234. pub expiry: u64,
  235. /// Payment preimage
  236. pub payment_preimage: Option<String>,
  237. /// Value used by ln backend to look up state of request
  238. pub request_lookup_id: Option<PaymentIdentifier>,
  239. /// Payment options
  240. ///
  241. /// Used for amountless invoices and MPP payments
  242. pub options: Option<MeltOptions>,
  243. /// Unix time quote was created
  244. #[serde(default)]
  245. pub created_time: u64,
  246. /// Unix time quote was paid
  247. pub paid_time: Option<u64>,
  248. /// Payment method
  249. #[serde(default)]
  250. pub payment_method: PaymentMethod,
  251. }
  252. impl MeltQuote {
  253. /// Create new [`MeltQuote`]
  254. #[allow(clippy::too_many_arguments)]
  255. pub fn new(
  256. request: MeltPaymentRequest,
  257. unit: CurrencyUnit,
  258. amount: Amount,
  259. fee_reserve: Amount,
  260. expiry: u64,
  261. request_lookup_id: Option<PaymentIdentifier>,
  262. options: Option<MeltOptions>,
  263. payment_method: PaymentMethod,
  264. ) -> Self {
  265. let id = Uuid::new_v4();
  266. Self {
  267. id: QuoteId::UUID(id),
  268. amount,
  269. unit,
  270. request,
  271. fee_reserve,
  272. state: MeltQuoteState::Unpaid,
  273. expiry,
  274. payment_preimage: None,
  275. request_lookup_id,
  276. options,
  277. created_time: unix_time(),
  278. paid_time: None,
  279. payment_method,
  280. }
  281. }
  282. }
  283. /// Mint Keyset Info
  284. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  285. pub struct MintKeySetInfo {
  286. /// Keyset [`Id`]
  287. pub id: Id,
  288. /// Keyset [`CurrencyUnit`]
  289. pub unit: CurrencyUnit,
  290. /// Keyset active or inactive
  291. /// Mint will only issue new signatures on active keysets
  292. pub active: bool,
  293. /// Starting unix time Keyset is valid from
  294. pub valid_from: u64,
  295. /// [`DerivationPath`] keyset
  296. pub derivation_path: DerivationPath,
  297. /// DerivationPath index of Keyset
  298. pub derivation_path_index: Option<u32>,
  299. /// Max order of keyset
  300. pub max_order: u8,
  301. /// Supported amounts
  302. pub amounts: Vec<u64>,
  303. /// Input Fee ppk
  304. #[serde(default = "default_fee")]
  305. pub input_fee_ppk: u64,
  306. /// Final expiry
  307. pub final_expiry: Option<u64>,
  308. }
  309. /// Default fee
  310. pub fn default_fee() -> u64 {
  311. 0
  312. }
  313. impl From<MintKeySetInfo> for KeySetInfo {
  314. fn from(keyset_info: MintKeySetInfo) -> Self {
  315. Self {
  316. id: keyset_info.id,
  317. unit: keyset_info.unit,
  318. active: keyset_info.active,
  319. input_fee_ppk: keyset_info.input_fee_ppk,
  320. final_expiry: keyset_info.final_expiry,
  321. }
  322. }
  323. }
  324. impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
  325. fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
  326. MintQuoteBolt11Response {
  327. quote: mint_quote.id.clone(),
  328. state: mint_quote.state(),
  329. request: mint_quote.request,
  330. expiry: Some(mint_quote.expiry),
  331. pubkey: mint_quote.pubkey,
  332. amount: mint_quote.amount,
  333. unit: Some(mint_quote.unit.clone()),
  334. }
  335. }
  336. }
  337. impl From<MintQuote> for MintQuoteBolt11Response<String> {
  338. fn from(quote: MintQuote) -> Self {
  339. let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
  340. quote.into()
  341. }
  342. }
  343. impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
  344. type Error = crate::Error;
  345. fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
  346. Ok(MintQuoteBolt12Response {
  347. quote: mint_quote.id.clone(),
  348. request: mint_quote.request,
  349. expiry: Some(mint_quote.expiry),
  350. amount_paid: mint_quote.amount_paid,
  351. amount_issued: mint_quote.amount_issued,
  352. pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
  353. amount: mint_quote.amount,
  354. unit: mint_quote.unit,
  355. })
  356. }
  357. }
  358. impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
  359. type Error = crate::Error;
  360. fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
  361. let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
  362. Ok(quote.into())
  363. }
  364. }
  365. impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  366. fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  367. MeltQuoteBolt11Response {
  368. quote: melt_quote.id.clone(),
  369. payment_preimage: None,
  370. change: None,
  371. state: melt_quote.state,
  372. paid: Some(melt_quote.state == MeltQuoteState::Paid),
  373. expiry: melt_quote.expiry,
  374. amount: melt_quote.amount,
  375. fee_reserve: melt_quote.fee_reserve,
  376. request: None,
  377. unit: Some(melt_quote.unit.clone()),
  378. }
  379. }
  380. }
  381. impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  382. fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  383. let paid = melt_quote.state == MeltQuoteState::Paid;
  384. MeltQuoteBolt11Response {
  385. quote: melt_quote.id.clone(),
  386. amount: melt_quote.amount,
  387. fee_reserve: melt_quote.fee_reserve,
  388. paid: Some(paid),
  389. state: melt_quote.state,
  390. expiry: melt_quote.expiry,
  391. payment_preimage: melt_quote.payment_preimage,
  392. change: None,
  393. request: Some(melt_quote.request.to_string()),
  394. unit: Some(melt_quote.unit.clone()),
  395. }
  396. }
  397. }
  398. /// Payment request
  399. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  400. pub enum MeltPaymentRequest {
  401. /// Bolt11 Payment
  402. Bolt11 {
  403. /// Bolt11 invoice
  404. bolt11: Bolt11Invoice,
  405. },
  406. /// Bolt12 Payment
  407. Bolt12 {
  408. /// Offer
  409. #[serde(with = "offer_serde")]
  410. offer: Box<Offer>,
  411. },
  412. }
  413. impl std::fmt::Display for MeltPaymentRequest {
  414. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  415. match self {
  416. MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
  417. MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
  418. }
  419. }
  420. }
  421. mod offer_serde {
  422. use std::str::FromStr;
  423. use serde::{self, Deserialize, Deserializer, Serializer};
  424. use super::Offer;
  425. pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
  426. where
  427. S: Serializer,
  428. {
  429. let s = offer.to_string();
  430. serializer.serialize_str(&s)
  431. }
  432. pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
  433. where
  434. D: Deserializer<'de>,
  435. {
  436. let s = String::deserialize(deserializer)?;
  437. Ok(Box::new(Offer::from_str(&s).map_err(|_| {
  438. serde::de::Error::custom("Invalid Bolt12 Offer")
  439. })?))
  440. }
  441. }