mint.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. //! Mint types
  2. use std::fmt;
  3. use std::str::FromStr;
  4. use bitcoin::bip32::DerivationPath;
  5. use cashu::quote_id::QuoteId;
  6. use cashu::util::unix_time;
  7. use cashu::{
  8. Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
  9. MintQuoteBolt12Response, PaymentMethod,
  10. };
  11. use lightning::offers::offer::Offer;
  12. use serde::{Deserialize, Serialize};
  13. use tracing::instrument;
  14. use uuid::Uuid;
  15. use crate::nuts::{MeltQuoteState, MintQuoteState};
  16. use crate::payment::PaymentIdentifier;
  17. use crate::{Amount, CurrencyUnit, Error, Id, KeySetInfo, PublicKey};
  18. /// Operation kind for saga persistence
  19. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
  20. #[serde(rename_all = "lowercase")]
  21. pub enum OperationKind {
  22. /// Swap operation
  23. Swap,
  24. /// Mint operation
  25. Mint,
  26. /// Melt operation
  27. Melt,
  28. }
  29. impl fmt::Display for OperationKind {
  30. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  31. match self {
  32. OperationKind::Swap => write!(f, "swap"),
  33. OperationKind::Mint => write!(f, "mint"),
  34. OperationKind::Melt => write!(f, "melt"),
  35. }
  36. }
  37. }
  38. impl FromStr for OperationKind {
  39. type Err = Error;
  40. fn from_str(value: &str) -> Result<Self, Self::Err> {
  41. let value = value.to_lowercase();
  42. match value.as_str() {
  43. "swap" => Ok(OperationKind::Swap),
  44. "mint" => Ok(OperationKind::Mint),
  45. "melt" => Ok(OperationKind::Melt),
  46. _ => Err(Error::Custom(format!("Invalid operation kind: {value}"))),
  47. }
  48. }
  49. }
  50. /// States specific to swap saga
  51. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  52. #[serde(rename_all = "snake_case")]
  53. pub enum SwapSagaState {
  54. /// Swap setup complete (proofs added, blinded messages added)
  55. SetupComplete,
  56. /// Outputs signed (signatures generated but not persisted)
  57. Signed,
  58. }
  59. impl fmt::Display for SwapSagaState {
  60. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  61. match self {
  62. SwapSagaState::SetupComplete => write!(f, "setup_complete"),
  63. SwapSagaState::Signed => write!(f, "signed"),
  64. }
  65. }
  66. }
  67. impl FromStr for SwapSagaState {
  68. type Err = Error;
  69. fn from_str(value: &str) -> Result<Self, Self::Err> {
  70. let value = value.to_lowercase();
  71. match value.as_str() {
  72. "setup_complete" => Ok(SwapSagaState::SetupComplete),
  73. "signed" => Ok(SwapSagaState::Signed),
  74. _ => Err(Error::Custom(format!("Invalid swap saga state: {value}"))),
  75. }
  76. }
  77. }
  78. /// States specific to melt saga
  79. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  80. #[serde(rename_all = "snake_case")]
  81. pub enum MeltSagaState {
  82. /// Setup complete (proofs reserved, quote verified)
  83. SetupComplete,
  84. /// Payment attempted to Lightning network (may or may not have succeeded)
  85. PaymentAttempted,
  86. }
  87. impl fmt::Display for MeltSagaState {
  88. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  89. match self {
  90. MeltSagaState::SetupComplete => write!(f, "setup_complete"),
  91. MeltSagaState::PaymentAttempted => write!(f, "payment_attempted"),
  92. }
  93. }
  94. }
  95. impl FromStr for MeltSagaState {
  96. type Err = Error;
  97. fn from_str(value: &str) -> Result<Self, Self::Err> {
  98. let value = value.to_lowercase();
  99. match value.as_str() {
  100. "setup_complete" => Ok(MeltSagaState::SetupComplete),
  101. "payment_attempted" => Ok(MeltSagaState::PaymentAttempted),
  102. _ => Err(Error::Custom(format!("Invalid melt saga state: {}", value))),
  103. }
  104. }
  105. }
  106. /// Saga state for different operation types
  107. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  108. #[serde(tag = "type", rename_all = "snake_case")]
  109. pub enum SagaStateEnum {
  110. /// Swap saga states
  111. Swap(SwapSagaState),
  112. /// Melt saga states
  113. Melt(MeltSagaState),
  114. // Future: Mint saga states
  115. // Mint(MintSagaState),
  116. }
  117. impl SagaStateEnum {
  118. /// Create from string given operation kind
  119. pub fn new(operation_kind: OperationKind, s: &str) -> Result<Self, Error> {
  120. match operation_kind {
  121. OperationKind::Swap => Ok(SagaStateEnum::Swap(SwapSagaState::from_str(s)?)),
  122. OperationKind::Melt => Ok(SagaStateEnum::Melt(MeltSagaState::from_str(s)?)),
  123. OperationKind::Mint => Err(Error::Custom("Mint saga not implemented yet".to_string())),
  124. }
  125. }
  126. /// Get string representation of the state
  127. pub fn state(&self) -> &str {
  128. match self {
  129. SagaStateEnum::Swap(state) => match state {
  130. SwapSagaState::SetupComplete => "setup_complete",
  131. SwapSagaState::Signed => "signed",
  132. },
  133. SagaStateEnum::Melt(state) => match state {
  134. MeltSagaState::SetupComplete => "setup_complete",
  135. MeltSagaState::PaymentAttempted => "payment_attempted",
  136. },
  137. }
  138. }
  139. }
  140. /// Persisted saga for recovery
  141. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  142. pub struct Saga {
  143. /// Operation ID (correlation key)
  144. pub operation_id: Uuid,
  145. /// Operation kind (swap, mint, melt)
  146. pub operation_kind: OperationKind,
  147. /// Current saga state (operation-specific)
  148. pub state: SagaStateEnum,
  149. /// Quote ID for melt operations (used for payment status lookup during recovery)
  150. /// None for swap operations
  151. pub quote_id: Option<String>,
  152. /// Unix timestamp when saga was created
  153. pub created_at: u64,
  154. /// Unix timestamp when saga was last updated
  155. pub updated_at: u64,
  156. }
  157. impl Saga {
  158. /// Create new swap saga
  159. pub fn new_swap(operation_id: Uuid, state: SwapSagaState) -> Self {
  160. let now = unix_time();
  161. Self {
  162. operation_id,
  163. operation_kind: OperationKind::Swap,
  164. state: SagaStateEnum::Swap(state),
  165. quote_id: None,
  166. created_at: now,
  167. updated_at: now,
  168. }
  169. }
  170. /// Update swap saga state
  171. pub fn update_swap_state(&mut self, new_state: SwapSagaState) {
  172. self.state = SagaStateEnum::Swap(new_state);
  173. self.updated_at = unix_time();
  174. }
  175. /// Create new melt saga
  176. pub fn new_melt(operation_id: Uuid, state: MeltSagaState, quote_id: String) -> Self {
  177. let now = unix_time();
  178. Self {
  179. operation_id,
  180. operation_kind: OperationKind::Melt,
  181. state: SagaStateEnum::Melt(state),
  182. quote_id: Some(quote_id),
  183. created_at: now,
  184. updated_at: now,
  185. }
  186. }
  187. /// Update melt saga state
  188. pub fn update_melt_state(&mut self, new_state: MeltSagaState) {
  189. self.state = SagaStateEnum::Melt(new_state);
  190. self.updated_at = unix_time();
  191. }
  192. }
  193. /// Operation
  194. pub struct Operation {
  195. id: Uuid,
  196. kind: OperationKind,
  197. total_issued: Amount,
  198. total_redeemed: Amount,
  199. fee_collected: Amount,
  200. complete_at: Option<u64>,
  201. /// Payment amount (only for melt operations)
  202. payment_amount: Option<Amount>,
  203. /// Payment fee (only for melt operations)
  204. payment_fee: Option<Amount>,
  205. /// Payment method (only for mint/melt operations)
  206. payment_method: Option<PaymentMethod>,
  207. }
  208. impl Operation {
  209. /// New
  210. pub fn new(
  211. id: Uuid,
  212. kind: OperationKind,
  213. total_issued: Amount,
  214. total_redeemed: Amount,
  215. fee_collected: Amount,
  216. complete_at: Option<u64>,
  217. payment_method: Option<PaymentMethod>,
  218. ) -> Self {
  219. Self {
  220. id,
  221. kind,
  222. total_issued,
  223. total_redeemed,
  224. fee_collected,
  225. complete_at,
  226. payment_amount: None,
  227. payment_fee: None,
  228. payment_method,
  229. }
  230. }
  231. /// Mint
  232. pub fn new_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
  233. Self {
  234. id: Uuid::new_v4(),
  235. kind: OperationKind::Mint,
  236. total_issued,
  237. total_redeemed: Amount::ZERO,
  238. fee_collected: Amount::ZERO,
  239. complete_at: None,
  240. payment_amount: None,
  241. payment_fee: None,
  242. payment_method: Some(payment_method),
  243. }
  244. }
  245. /// Melt
  246. ///
  247. /// In the context of a melt total_issued refrests to the change
  248. pub fn new_melt(
  249. total_redeemed: Amount,
  250. fee_collected: Amount,
  251. payment_method: PaymentMethod,
  252. ) -> Self {
  253. Self {
  254. id: Uuid::new_v4(),
  255. kind: OperationKind::Melt,
  256. total_issued: Amount::ZERO,
  257. total_redeemed,
  258. fee_collected,
  259. complete_at: None,
  260. payment_amount: None,
  261. payment_fee: None,
  262. payment_method: Some(payment_method),
  263. }
  264. }
  265. /// Swap
  266. pub fn new_swap(total_issued: Amount, total_redeemed: Amount, fee_collected: Amount) -> Self {
  267. Self {
  268. id: Uuid::new_v4(),
  269. kind: OperationKind::Swap,
  270. total_issued,
  271. total_redeemed,
  272. fee_collected,
  273. complete_at: None,
  274. payment_amount: None,
  275. payment_fee: None,
  276. payment_method: None,
  277. }
  278. }
  279. /// Operation id
  280. pub fn id(&self) -> &Uuid {
  281. &self.id
  282. }
  283. /// Operation kind
  284. pub fn kind(&self) -> OperationKind {
  285. self.kind
  286. }
  287. /// Total issued
  288. pub fn total_issued(&self) -> Amount {
  289. self.total_issued
  290. }
  291. /// Total redeemed
  292. pub fn total_redeemed(&self) -> Amount {
  293. self.total_redeemed
  294. }
  295. /// Fee collected
  296. pub fn fee_collected(&self) -> Amount {
  297. self.fee_collected
  298. }
  299. /// Completed time
  300. pub fn completed_at(&self) -> &Option<u64> {
  301. &self.complete_at
  302. }
  303. /// Add change
  304. pub fn add_change(&mut self, change: Amount) {
  305. self.total_issued = change;
  306. }
  307. /// Payment amount (only for melt operations)
  308. pub fn payment_amount(&self) -> Option<Amount> {
  309. self.payment_amount
  310. }
  311. /// Payment fee (only for melt operations)
  312. pub fn payment_fee(&self) -> Option<Amount> {
  313. self.payment_fee
  314. }
  315. /// Set payment details for melt operations
  316. pub fn set_payment_details(&mut self, payment_amount: Amount, payment_fee: Amount) {
  317. self.payment_amount = Some(payment_amount);
  318. self.payment_fee = Some(payment_fee);
  319. }
  320. /// Payment method (only for mint/melt operations)
  321. pub fn payment_method(&self) -> Option<PaymentMethod> {
  322. self.payment_method.clone()
  323. }
  324. }
  325. /// Mint Quote Info
  326. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  327. pub struct MintQuote {
  328. /// Quote id
  329. pub id: QuoteId,
  330. /// Amount of quote
  331. pub amount: Option<Amount>,
  332. /// Unit of quote
  333. pub unit: CurrencyUnit,
  334. /// Quote payment request e.g. bolt11
  335. pub request: String,
  336. /// Expiration time of quote
  337. pub expiry: u64,
  338. /// Value used by ln backend to look up state of request
  339. pub request_lookup_id: PaymentIdentifier,
  340. /// Pubkey
  341. pub pubkey: Option<PublicKey>,
  342. /// Unix time quote was created
  343. #[serde(default)]
  344. pub created_time: u64,
  345. /// Amount paid
  346. #[serde(default)]
  347. amount_paid: Amount,
  348. /// Amount issued
  349. #[serde(default)]
  350. amount_issued: Amount,
  351. /// Payment of payment(s) that filled quote
  352. #[serde(default)]
  353. pub payments: Vec<IncomingPayment>,
  354. /// Payment Method
  355. #[serde(default)]
  356. pub payment_method: PaymentMethod,
  357. /// Payment of payment(s) that filled quote
  358. #[serde(default)]
  359. pub issuance: Vec<Issuance>,
  360. }
  361. impl MintQuote {
  362. /// Create new [`MintQuote`]
  363. #[allow(clippy::too_many_arguments)]
  364. pub fn new(
  365. id: Option<QuoteId>,
  366. request: String,
  367. unit: CurrencyUnit,
  368. amount: Option<Amount>,
  369. expiry: u64,
  370. request_lookup_id: PaymentIdentifier,
  371. pubkey: Option<PublicKey>,
  372. amount_paid: Amount,
  373. amount_issued: Amount,
  374. payment_method: PaymentMethod,
  375. created_time: u64,
  376. payments: Vec<IncomingPayment>,
  377. issuance: Vec<Issuance>,
  378. ) -> Self {
  379. let id = id.unwrap_or_else(QuoteId::new_uuid);
  380. Self {
  381. id,
  382. amount,
  383. unit,
  384. request,
  385. expiry,
  386. request_lookup_id,
  387. pubkey,
  388. created_time,
  389. amount_paid,
  390. amount_issued,
  391. payment_method,
  392. payments,
  393. issuance,
  394. }
  395. }
  396. /// Increment the amount paid on the mint quote by a given amount
  397. #[instrument(skip(self))]
  398. pub fn increment_amount_paid(
  399. &mut self,
  400. additional_amount: Amount,
  401. ) -> Result<Amount, crate::Error> {
  402. self.amount_paid = self
  403. .amount_paid
  404. .checked_add(additional_amount)
  405. .ok_or(crate::Error::AmountOverflow)?;
  406. Ok(self.amount_paid)
  407. }
  408. /// Amount paid
  409. #[instrument(skip(self))]
  410. pub fn amount_paid(&self) -> Amount {
  411. self.amount_paid
  412. }
  413. /// Increment the amount issued on the mint quote by a given amount
  414. ///
  415. /// # Errors
  416. /// Returns an error if the new issued amount would exceed the paid amount
  417. /// (can't issue more than what's been paid) or if the addition would overflow.
  418. #[instrument(skip(self))]
  419. pub fn increment_amount_issued(
  420. &mut self,
  421. additional_amount: Amount,
  422. ) -> Result<Amount, crate::Error> {
  423. let new_amount_issued = self
  424. .amount_issued
  425. .checked_add(additional_amount)
  426. .ok_or(crate::Error::AmountOverflow)?;
  427. // Can't issue more than what's been paid
  428. if new_amount_issued > self.amount_paid {
  429. return Err(crate::Error::OverIssue);
  430. }
  431. self.amount_issued = new_amount_issued;
  432. Ok(self.amount_issued)
  433. }
  434. /// Amount issued
  435. #[instrument(skip(self))]
  436. pub fn amount_issued(&self) -> Amount {
  437. self.amount_issued
  438. }
  439. /// Get state of mint quote
  440. #[instrument(skip(self))]
  441. pub fn state(&self) -> MintQuoteState {
  442. self.compute_quote_state()
  443. }
  444. /// Existing payment ids of a mint quote
  445. pub fn payment_ids(&self) -> Vec<&String> {
  446. self.payments.iter().map(|a| &a.payment_id).collect()
  447. }
  448. /// Amount mintable
  449. /// Returns the amount that is still available for minting.
  450. ///
  451. /// The value is computed as the difference between the total amount that
  452. /// has been paid for this issuance (`self.amount_paid`) and the amount
  453. /// that has already been issued (`self.amount_issued`). In other words,
  454. pub fn amount_mintable(&self) -> Amount {
  455. self.amount_paid - self.amount_issued
  456. }
  457. /// Add a payment ID to the list of payment IDs
  458. ///
  459. /// Returns an error if the payment ID is already in the list
  460. #[instrument(skip(self))]
  461. pub fn add_payment(
  462. &mut self,
  463. amount: Amount,
  464. payment_id: String,
  465. time: u64,
  466. ) -> Result<(), crate::Error> {
  467. let payment_ids = self.payment_ids();
  468. if payment_ids.contains(&&payment_id) {
  469. return Err(crate::Error::DuplicatePaymentId);
  470. }
  471. let payment = IncomingPayment::new(amount, payment_id, time);
  472. self.payments.push(payment);
  473. Ok(())
  474. }
  475. /// Compute quote state
  476. #[instrument(skip(self))]
  477. fn compute_quote_state(&self) -> MintQuoteState {
  478. if self.amount_paid == Amount::ZERO && self.amount_issued == Amount::ZERO {
  479. return MintQuoteState::Unpaid;
  480. }
  481. match self.amount_paid.cmp(&self.amount_issued) {
  482. std::cmp::Ordering::Less => {
  483. // self.amount_paid is less than other (amount issued)
  484. // Handle case where paid amount is insufficient
  485. tracing::error!("We should not have issued more then has been paid");
  486. MintQuoteState::Issued
  487. }
  488. std::cmp::Ordering::Equal => {
  489. // We do this extra check for backwards compatibility for quotes where amount paid/issed was not tracked
  490. // self.amount_paid equals other (amount issued)
  491. // Handle case where paid amount exactly matches
  492. MintQuoteState::Issued
  493. }
  494. std::cmp::Ordering::Greater => {
  495. // self.amount_paid is greater than other (amount issued)
  496. // Handle case where paid amount exceeds required amount
  497. MintQuoteState::Paid
  498. }
  499. }
  500. }
  501. }
  502. /// Mint Payments
  503. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  504. pub struct IncomingPayment {
  505. /// Amount
  506. pub amount: Amount,
  507. /// Pyament unix time
  508. pub time: u64,
  509. /// Payment id
  510. pub payment_id: String,
  511. }
  512. impl IncomingPayment {
  513. /// New [`IncomingPayment`]
  514. pub fn new(amount: Amount, payment_id: String, time: u64) -> Self {
  515. Self {
  516. payment_id,
  517. time,
  518. amount,
  519. }
  520. }
  521. }
  522. /// Informattion about issued quote
  523. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  524. pub struct Issuance {
  525. /// Amount
  526. pub amount: Amount,
  527. /// Time
  528. pub time: u64,
  529. }
  530. impl Issuance {
  531. /// Create new [`Issuance`]
  532. pub fn new(amount: Amount, time: u64) -> Self {
  533. Self { amount, time }
  534. }
  535. }
  536. /// Melt Quote Info
  537. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  538. pub struct MeltQuote {
  539. /// Quote id
  540. pub id: QuoteId,
  541. /// Quote unit
  542. pub unit: CurrencyUnit,
  543. /// Quote amount
  544. pub amount: Amount,
  545. /// Quote Payment request e.g. bolt11
  546. pub request: MeltPaymentRequest,
  547. /// Quote fee reserve
  548. pub fee_reserve: Amount,
  549. /// Quote state
  550. pub state: MeltQuoteState,
  551. /// Expiration time of quote
  552. pub expiry: u64,
  553. /// Payment preimage
  554. pub payment_preimage: Option<String>,
  555. /// Value used by ln backend to look up state of request
  556. pub request_lookup_id: Option<PaymentIdentifier>,
  557. /// Payment options
  558. ///
  559. /// Used for amountless invoices and MPP payments
  560. pub options: Option<MeltOptions>,
  561. /// Unix time quote was created
  562. #[serde(default)]
  563. pub created_time: u64,
  564. /// Unix time quote was paid
  565. pub paid_time: Option<u64>,
  566. /// Payment method
  567. #[serde(default)]
  568. pub payment_method: PaymentMethod,
  569. }
  570. impl MeltQuote {
  571. /// Create new [`MeltQuote`]
  572. #[allow(clippy::too_many_arguments)]
  573. pub fn new(
  574. request: MeltPaymentRequest,
  575. unit: CurrencyUnit,
  576. amount: Amount,
  577. fee_reserve: Amount,
  578. expiry: u64,
  579. request_lookup_id: Option<PaymentIdentifier>,
  580. options: Option<MeltOptions>,
  581. payment_method: PaymentMethod,
  582. ) -> Self {
  583. let id = Uuid::new_v4();
  584. Self {
  585. id: QuoteId::UUID(id),
  586. amount,
  587. unit,
  588. request,
  589. fee_reserve,
  590. state: MeltQuoteState::Unpaid,
  591. expiry,
  592. payment_preimage: None,
  593. request_lookup_id,
  594. options,
  595. created_time: unix_time(),
  596. paid_time: None,
  597. payment_method,
  598. }
  599. }
  600. }
  601. /// Mint Keyset Info
  602. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  603. pub struct MintKeySetInfo {
  604. /// Keyset [`Id`]
  605. pub id: Id,
  606. /// Keyset [`CurrencyUnit`]
  607. pub unit: CurrencyUnit,
  608. /// Keyset active or inactive
  609. /// Mint will only issue new signatures on active keysets
  610. pub active: bool,
  611. /// Starting unix time Keyset is valid from
  612. pub valid_from: u64,
  613. /// [`DerivationPath`] keyset
  614. pub derivation_path: DerivationPath,
  615. /// DerivationPath index of Keyset
  616. pub derivation_path_index: Option<u32>,
  617. /// Supported amounts
  618. pub amounts: Vec<u64>,
  619. /// Input Fee ppk
  620. #[serde(default = "default_fee")]
  621. pub input_fee_ppk: u64,
  622. /// Final expiry
  623. pub final_expiry: Option<u64>,
  624. }
  625. /// Default fee
  626. pub fn default_fee() -> u64 {
  627. 0
  628. }
  629. impl From<MintKeySetInfo> for KeySetInfo {
  630. fn from(keyset_info: MintKeySetInfo) -> Self {
  631. Self {
  632. id: keyset_info.id,
  633. unit: keyset_info.unit,
  634. active: keyset_info.active,
  635. input_fee_ppk: keyset_info.input_fee_ppk,
  636. final_expiry: keyset_info.final_expiry,
  637. }
  638. }
  639. }
  640. impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
  641. fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
  642. MintQuoteBolt11Response {
  643. quote: mint_quote.id.clone(),
  644. state: mint_quote.state(),
  645. request: mint_quote.request,
  646. expiry: Some(mint_quote.expiry),
  647. pubkey: mint_quote.pubkey,
  648. amount: mint_quote.amount,
  649. unit: Some(mint_quote.unit.clone()),
  650. }
  651. }
  652. }
  653. impl From<MintQuote> for MintQuoteBolt11Response<String> {
  654. fn from(quote: MintQuote) -> Self {
  655. let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
  656. quote.into()
  657. }
  658. }
  659. impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
  660. type Error = crate::Error;
  661. fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
  662. Ok(MintQuoteBolt12Response {
  663. quote: mint_quote.id.clone(),
  664. request: mint_quote.request,
  665. expiry: Some(mint_quote.expiry),
  666. amount_paid: mint_quote.amount_paid,
  667. amount_issued: mint_quote.amount_issued,
  668. pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
  669. amount: mint_quote.amount,
  670. unit: mint_quote.unit,
  671. })
  672. }
  673. }
  674. impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
  675. type Error = crate::Error;
  676. fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
  677. let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
  678. Ok(quote.into())
  679. }
  680. }
  681. impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  682. fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  683. MeltQuoteBolt11Response {
  684. quote: melt_quote.id.clone(),
  685. payment_preimage: None,
  686. change: None,
  687. state: melt_quote.state,
  688. expiry: melt_quote.expiry,
  689. amount: melt_quote.amount,
  690. fee_reserve: melt_quote.fee_reserve,
  691. request: None,
  692. unit: Some(melt_quote.unit.clone()),
  693. }
  694. }
  695. }
  696. impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
  697. fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
  698. MeltQuoteBolt11Response {
  699. quote: melt_quote.id.clone(),
  700. amount: melt_quote.amount,
  701. fee_reserve: melt_quote.fee_reserve,
  702. state: melt_quote.state,
  703. expiry: melt_quote.expiry,
  704. payment_preimage: melt_quote.payment_preimage,
  705. change: None,
  706. request: Some(melt_quote.request.to_string()),
  707. unit: Some(melt_quote.unit.clone()),
  708. }
  709. }
  710. }
  711. /// Payment request
  712. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  713. pub enum MeltPaymentRequest {
  714. /// Bolt11 Payment
  715. Bolt11 {
  716. /// Bolt11 invoice
  717. bolt11: Bolt11Invoice,
  718. },
  719. /// Bolt12 Payment
  720. Bolt12 {
  721. /// Offer
  722. #[serde(with = "offer_serde")]
  723. offer: Box<Offer>,
  724. },
  725. }
  726. impl std::fmt::Display for MeltPaymentRequest {
  727. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  728. match self {
  729. MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
  730. MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
  731. }
  732. }
  733. }
  734. mod offer_serde {
  735. use std::str::FromStr;
  736. use serde::{self, Deserialize, Deserializer, Serializer};
  737. use super::Offer;
  738. pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
  739. where
  740. S: Serializer,
  741. {
  742. let s = offer.to_string();
  743. serializer.serialize_str(&s)
  744. }
  745. pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
  746. where
  747. D: Deserializer<'de>,
  748. {
  749. let s = String::deserialize(deserializer)?;
  750. Ok(Box::new(Offer::from_str(&s).map_err(|_| {
  751. serde::de::Error::custom("Invalid Bolt12 Offer")
  752. })?))
  753. }
  754. }