mint.rs 24 KB

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