1use std::fmt;
4use std::ops::Deref;
5use std::str::FromStr;
6
7use bitcoin::bip32::DerivationPath;
8use cashu::quote_id::QuoteId;
9use cashu::util::unix_time;
10use cashu::{
11 Bolt11Invoice, MeltOptions, MeltQuoteBolt11Response, MintQuoteBolt11Response,
12 MintQuoteBolt12Response, PaymentMethod, Proofs, State,
13};
14use lightning::offers::offer::Offer;
15use serde::{Deserialize, Serialize};
16use tracing::instrument;
17use uuid::Uuid;
18
19use crate::common::IssuerVersion;
20use crate::nuts::{MeltQuoteState, MintQuoteState};
21use crate::payment::PaymentIdentifier;
22use crate::{Amount, CurrencyUnit, Error, Id, KeySetInfo, PublicKey};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum OperationKind {
28 Swap,
30 Mint,
32 Melt,
34}
35
36#[derive(Debug)]
68pub struct ProofsWithState {
69 proofs: Proofs,
70 pub state: State,
72}
73
74impl Deref for ProofsWithState {
75 type Target = Proofs;
76
77 fn deref(&self) -> &Self::Target {
78 &self.proofs
79 }
80}
81
82impl ProofsWithState {
83 pub fn new(proofs: Proofs, current_state: State) -> Self {
90 Self {
91 proofs,
92 state: current_state,
93 }
94 }
95}
96
97impl fmt::Display for OperationKind {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 OperationKind::Swap => write!(f, "swap"),
101 OperationKind::Mint => write!(f, "mint"),
102 OperationKind::Melt => write!(f, "melt"),
103 }
104 }
105}
106
107impl FromStr for OperationKind {
108 type Err = Error;
109 fn from_str(value: &str) -> Result<Self, Self::Err> {
110 let value = value.to_lowercase();
111 match value.as_str() {
112 "swap" => Ok(OperationKind::Swap),
113 "mint" => Ok(OperationKind::Mint),
114 "melt" => Ok(OperationKind::Melt),
115 _ => Err(Error::Custom(format!("Invalid operation kind: {value}"))),
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122#[serde(rename_all = "snake_case")]
123pub enum SwapSagaState {
124 SetupComplete,
126 Signed,
128}
129
130impl fmt::Display for SwapSagaState {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 SwapSagaState::SetupComplete => write!(f, "setup_complete"),
134 SwapSagaState::Signed => write!(f, "signed"),
135 }
136 }
137}
138
139impl FromStr for SwapSagaState {
140 type Err = Error;
141 fn from_str(value: &str) -> Result<Self, Self::Err> {
142 let value = value.to_lowercase();
143 match value.as_str() {
144 "setup_complete" => Ok(SwapSagaState::SetupComplete),
145 "signed" => Ok(SwapSagaState::Signed),
146 _ => Err(Error::Custom(format!("Invalid swap saga state: {value}"))),
147 }
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub enum MeltSagaState {
155 SetupComplete,
157 PaymentAttempted,
159 Finalizing,
161}
162
163impl fmt::Display for MeltSagaState {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 MeltSagaState::SetupComplete => write!(f, "setup_complete"),
167 MeltSagaState::PaymentAttempted => write!(f, "payment_attempted"),
168 MeltSagaState::Finalizing => write!(f, "finalizing"),
169 }
170 }
171}
172
173impl FromStr for MeltSagaState {
174 type Err = Error;
175 fn from_str(value: &str) -> Result<Self, Self::Err> {
176 let value = value.to_lowercase();
177 match value.as_str() {
178 "setup_complete" => Ok(MeltSagaState::SetupComplete),
179 "payment_attempted" => Ok(MeltSagaState::PaymentAttempted),
180 "finalizing" => Ok(MeltSagaState::Finalizing),
181 _ => Err(Error::Custom(format!("Invalid melt saga state: {}", value))),
182 }
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188#[serde(tag = "type", rename_all = "snake_case")]
189pub enum SagaStateEnum {
190 Swap(SwapSagaState),
192 Melt(MeltSagaState),
194 }
197
198impl SagaStateEnum {
199 pub fn new(operation_kind: OperationKind, s: &str) -> Result<Self, Error> {
201 match operation_kind {
202 OperationKind::Swap => Ok(SagaStateEnum::Swap(SwapSagaState::from_str(s)?)),
203 OperationKind::Melt => Ok(SagaStateEnum::Melt(MeltSagaState::from_str(s)?)),
204 OperationKind::Mint => Err(Error::Custom("Mint saga not implemented yet".to_string())),
205 }
206 }
207
208 pub fn state(&self) -> &str {
210 match self {
211 SagaStateEnum::Swap(state) => match state {
212 SwapSagaState::SetupComplete => "setup_complete",
213 SwapSagaState::Signed => "signed",
214 },
215 SagaStateEnum::Melt(state) => match state {
216 MeltSagaState::SetupComplete => "setup_complete",
217 MeltSagaState::PaymentAttempted => "payment_attempted",
218 MeltSagaState::Finalizing => "finalizing",
219 },
220 }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226pub struct Saga {
227 pub operation_id: Uuid,
229 pub operation_kind: OperationKind,
231 pub state: SagaStateEnum,
233 pub quote_id: Option<String>,
236 pub created_at: u64,
238 pub updated_at: u64,
240}
241
242impl Saga {
243 pub fn new_swap(operation_id: Uuid, state: SwapSagaState) -> Self {
245 let now = unix_time();
246 Self {
247 operation_id,
248 operation_kind: OperationKind::Swap,
249 state: SagaStateEnum::Swap(state),
250 quote_id: None,
251 created_at: now,
252 updated_at: now,
253 }
254 }
255
256 pub fn update_swap_state(&mut self, new_state: SwapSagaState) {
258 self.state = SagaStateEnum::Swap(new_state);
259 self.updated_at = unix_time();
260 }
261
262 pub fn new_melt(operation_id: Uuid, state: MeltSagaState, quote_id: String) -> Self {
264 let now = unix_time();
265 Self {
266 operation_id,
267 operation_kind: OperationKind::Melt,
268 state: SagaStateEnum::Melt(state),
269 quote_id: Some(quote_id),
270 created_at: now,
271 updated_at: now,
272 }
273 }
274
275 pub fn update_melt_state(&mut self, new_state: MeltSagaState) {
277 self.state = SagaStateEnum::Melt(new_state);
278 self.updated_at = unix_time();
279 }
280}
281
282#[derive(Debug)]
284pub struct Operation {
285 id: Uuid,
286 kind: OperationKind,
287 total_issued: Amount,
288 total_redeemed: Amount,
289 fee_collected: Amount,
290 complete_at: Option<u64>,
291 payment_amount: Option<Amount>,
293 payment_fee: Option<Amount>,
295 payment_method: Option<PaymentMethod>,
297}
298
299impl Operation {
300 pub fn new(
302 id: Uuid,
303 kind: OperationKind,
304 total_issued: Amount,
305 total_redeemed: Amount,
306 fee_collected: Amount,
307 complete_at: Option<u64>,
308 payment_method: Option<PaymentMethod>,
309 ) -> Self {
310 Self {
311 id,
312 kind,
313 total_issued,
314 total_redeemed,
315 fee_collected,
316 complete_at,
317 payment_amount: None,
318 payment_fee: None,
319 payment_method,
320 }
321 }
322
323 pub fn new_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
325 Self {
326 id: Uuid::new_v4(),
327 kind: OperationKind::Mint,
328 total_issued,
329 total_redeemed: Amount::ZERO,
330 fee_collected: Amount::ZERO,
331 complete_at: None,
332 payment_amount: None,
333 payment_fee: None,
334 payment_method: Some(payment_method),
335 }
336 }
337 pub fn new_melt(
341 total_redeemed: Amount,
342 fee_collected: Amount,
343 payment_method: PaymentMethod,
344 ) -> Self {
345 Self {
346 id: Uuid::new_v4(),
347 kind: OperationKind::Melt,
348 total_issued: Amount::ZERO,
349 total_redeemed,
350 fee_collected,
351 complete_at: None,
352 payment_amount: None,
353 payment_fee: None,
354 payment_method: Some(payment_method),
355 }
356 }
357
358 pub fn new_swap(total_issued: Amount, total_redeemed: Amount, fee_collected: Amount) -> Self {
360 Self {
361 id: Uuid::new_v4(),
362 kind: OperationKind::Swap,
363 total_issued,
364 total_redeemed,
365 fee_collected,
366 complete_at: None,
367 payment_amount: None,
368 payment_fee: None,
369 payment_method: None,
370 }
371 }
372
373 pub fn id(&self) -> &Uuid {
375 &self.id
376 }
377
378 pub fn kind(&self) -> OperationKind {
380 self.kind
381 }
382
383 pub fn total_issued(&self) -> Amount {
385 self.total_issued
386 }
387
388 pub fn total_redeemed(&self) -> Amount {
390 self.total_redeemed
391 }
392
393 pub fn fee_collected(&self) -> Amount {
395 self.fee_collected
396 }
397
398 pub fn completed_at(&self) -> &Option<u64> {
400 &self.complete_at
401 }
402
403 pub fn add_change(&mut self, change: Amount) {
405 self.total_issued = change;
406 }
407
408 pub fn payment_amount(&self) -> Option<Amount> {
410 self.payment_amount
411 }
412
413 pub fn payment_fee(&self) -> Option<Amount> {
415 self.payment_fee
416 }
417
418 pub fn set_payment_details(&mut self, payment_amount: Amount, payment_fee: Amount) {
420 self.payment_amount = Some(payment_amount);
421 self.payment_fee = Some(payment_fee);
422 }
423
424 pub fn payment_method(&self) -> Option<PaymentMethod> {
426 self.payment_method.clone()
427 }
428}
429
430#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
441pub struct MintQuoteChange {
442 pub payments: Option<Vec<IncomingPayment>>,
444 pub issuances: Option<Vec<Amount>>,
446}
447
448#[derive(Debug, Clone, Hash, PartialEq, Eq)]
450pub struct MintQuote {
451 pub id: QuoteId,
453 pub amount: Option<Amount<CurrencyUnit>>,
455 pub unit: CurrencyUnit,
457 pub request: String,
459 pub expiry: u64,
461 pub request_lookup_id: PaymentIdentifier,
463 pub pubkey: Option<PublicKey>,
465 pub created_time: u64,
467 amount_paid: Amount<CurrencyUnit>,
469 amount_issued: Amount<CurrencyUnit>,
471 pub payments: Vec<IncomingPayment>,
473 pub payment_method: PaymentMethod,
475 pub issuance: Vec<Issuance>,
477 pub extra_json: Option<serde_json::Value>,
479 changes: Option<MintQuoteChange>,
485}
486
487impl MintQuote {
488 #[allow(clippy::too_many_arguments)]
490 pub fn new(
491 id: Option<QuoteId>,
492 request: String,
493 unit: CurrencyUnit,
494 amount: Option<Amount<CurrencyUnit>>,
495 expiry: u64,
496 request_lookup_id: PaymentIdentifier,
497 pubkey: Option<PublicKey>,
498 amount_paid: Amount<CurrencyUnit>,
499 amount_issued: Amount<CurrencyUnit>,
500 payment_method: PaymentMethod,
501 created_time: u64,
502 payments: Vec<IncomingPayment>,
503 issuance: Vec<Issuance>,
504 extra_json: Option<serde_json::Value>,
505 ) -> Self {
506 let id = id.unwrap_or_else(QuoteId::new_uuid);
507
508 Self {
509 id,
510 amount,
511 unit: unit.clone(),
512 request,
513 expiry,
514 request_lookup_id,
515 pubkey,
516 created_time,
517 amount_paid,
518 amount_issued,
519 payment_method,
520 payments,
521 issuance,
522 extra_json,
523 changes: None,
524 }
525 }
526
527 #[instrument(skip(self))]
529 pub fn increment_amount_paid(
530 &mut self,
531 additional_amount: Amount<CurrencyUnit>,
532 ) -> Result<Amount, crate::Error> {
533 self.amount_paid = self
534 .amount_paid
535 .checked_add(&additional_amount)
536 .map_err(|_| crate::Error::AmountOverflow)?;
537 Ok(Amount::from(self.amount_paid.value()))
538 }
539
540 #[instrument(skip(self))]
542 pub fn amount_paid(&self) -> Amount<CurrencyUnit> {
543 self.amount_paid.clone()
544 }
545
546 #[instrument(skip(self))]
569 pub fn add_issuance(
570 &mut self,
571 additional_amount: Amount<CurrencyUnit>,
572 ) -> Result<Amount<CurrencyUnit>, crate::Error> {
573 let new_amount_issued = self
574 .amount_issued
575 .checked_add(&additional_amount)
576 .map_err(|_| crate::Error::AmountOverflow)?;
577
578 if new_amount_issued > self.amount_paid {
580 return Err(crate::Error::OverIssue);
581 }
582
583 self.changes
584 .get_or_insert_default()
585 .issuances
586 .get_or_insert_default()
587 .push(additional_amount.into());
588
589 self.amount_issued = new_amount_issued;
590
591 Ok(self.amount_issued.clone())
592 }
593
594 #[instrument(skip(self))]
596 pub fn amount_issued(&self) -> Amount<CurrencyUnit> {
597 self.amount_issued.clone()
598 }
599
600 #[instrument(skip(self))]
602 pub fn state(&self) -> MintQuoteState {
603 self.compute_quote_state()
604 }
605
606 pub fn payment_ids(&self) -> Vec<&String> {
608 self.payments.iter().map(|a| &a.payment_id).collect()
609 }
610
611 pub fn amount_mintable(&self) -> Amount<CurrencyUnit> {
618 self.amount_paid
619 .checked_sub(&self.amount_issued)
620 .unwrap_or_else(|_| Amount::new(0, self.unit.clone()))
621 }
622
623 pub fn take_changes(&mut self) -> Option<MintQuoteChange> {
633 self.changes.take()
634 }
635
636 #[instrument(skip(self))]
656 pub fn add_payment(
657 &mut self,
658 amount: Amount<CurrencyUnit>,
659 payment_id: String,
660 time: Option<u64>,
661 ) -> Result<(), crate::Error> {
662 let time = time.unwrap_or_else(unix_time);
663
664 let payment_ids = self.payment_ids();
665 if payment_ids.contains(&&payment_id) {
666 return Err(crate::Error::DuplicatePaymentId);
667 }
668
669 self.amount_paid = self
670 .amount_paid
671 .checked_add(&amount)
672 .map_err(|_| crate::Error::AmountOverflow)?;
673
674 let payment = IncomingPayment::new(amount, payment_id, time);
675
676 self.payments.push(payment.clone());
677
678 self.changes
679 .get_or_insert_default()
680 .payments
681 .get_or_insert_default()
682 .push(payment);
683
684 Ok(())
685 }
686
687 #[instrument(skip(self))]
689 fn compute_quote_state(&self) -> MintQuoteState {
690 let zero_amount = Amount::new(0, self.unit.clone());
691
692 if self.amount_paid == zero_amount && self.amount_issued == zero_amount {
693 return MintQuoteState::Unpaid;
694 }
695
696 match self.amount_paid.value().cmp(&self.amount_issued.value()) {
697 std::cmp::Ordering::Less => {
698 tracing::error!("We should not have issued more then has been paid");
699 MintQuoteState::Issued
700 }
701 std::cmp::Ordering::Equal => MintQuoteState::Issued,
702 std::cmp::Ordering::Greater => MintQuoteState::Paid,
703 }
704 }
705}
706
707#[derive(Debug, Clone, Hash, PartialEq, Eq)]
709pub struct IncomingPayment {
710 pub amount: Amount<CurrencyUnit>,
712 pub time: u64,
714 pub payment_id: String,
716}
717
718impl IncomingPayment {
719 pub fn new(amount: Amount<CurrencyUnit>, payment_id: String, time: u64) -> Self {
721 Self {
722 payment_id,
723 time,
724 amount,
725 }
726 }
727}
728
729#[derive(Debug, Clone, Hash, PartialEq, Eq)]
731pub struct Issuance {
732 pub amount: Amount<CurrencyUnit>,
734 pub time: u64,
736}
737
738impl Issuance {
739 pub fn new(amount: Amount<CurrencyUnit>, time: u64) -> Self {
741 Self { amount, time }
742 }
743}
744
745#[derive(Debug, Clone, Hash, PartialEq, Eq)]
747pub struct MeltQuote {
748 pub id: QuoteId,
750 pub unit: CurrencyUnit,
752 pub request: MeltPaymentRequest,
754 amount: Amount<CurrencyUnit>,
756 fee_reserve: Amount<CurrencyUnit>,
758 pub state: MeltQuoteState,
760 pub expiry: u64,
762 pub payment_preimage: Option<String>,
764 pub request_lookup_id: Option<PaymentIdentifier>,
766 pub options: Option<MeltOptions>,
770 pub created_time: u64,
772 pub paid_time: Option<u64>,
774 pub payment_method: PaymentMethod,
776}
777
778impl MeltQuote {
779 #[allow(clippy::too_many_arguments)]
781 pub fn new(
782 request: MeltPaymentRequest,
783 unit: CurrencyUnit,
784 amount: Amount<CurrencyUnit>,
785 fee_reserve: Amount<CurrencyUnit>,
786 expiry: u64,
787 request_lookup_id: Option<PaymentIdentifier>,
788 options: Option<MeltOptions>,
789 payment_method: PaymentMethod,
790 ) -> Self {
791 let id = Uuid::new_v4();
792
793 Self {
794 id: QuoteId::UUID(id),
795 unit: unit.clone(),
796 request,
797 amount,
798 fee_reserve,
799 state: MeltQuoteState::Unpaid,
800 expiry,
801 payment_preimage: None,
802 request_lookup_id,
803 options,
804 created_time: unix_time(),
805 paid_time: None,
806 payment_method,
807 }
808 }
809
810 #[inline]
812 pub fn amount(&self) -> Amount<CurrencyUnit> {
813 self.amount.clone()
814 }
815
816 #[inline]
818 pub fn fee_reserve(&self) -> Amount<CurrencyUnit> {
819 self.fee_reserve.clone()
820 }
821
822 pub fn total_needed(&self) -> Result<Amount, crate::Error> {
824 let total = self
825 .amount
826 .checked_add(&self.fee_reserve)
827 .map_err(|_| crate::Error::AmountOverflow)?;
828 Ok(Amount::from(total.value()))
829 }
830
831 #[allow(clippy::too_many_arguments)]
833 pub fn from_db(
834 id: QuoteId,
835 unit: CurrencyUnit,
836 request: MeltPaymentRequest,
837 amount: u64,
838 fee_reserve: u64,
839 state: MeltQuoteState,
840 expiry: u64,
841 payment_preimage: Option<String>,
842 request_lookup_id: Option<PaymentIdentifier>,
843 options: Option<MeltOptions>,
844 created_time: u64,
845 paid_time: Option<u64>,
846 payment_method: PaymentMethod,
847 ) -> Self {
848 Self {
849 id,
850 unit: unit.clone(),
851 request,
852 amount: Amount::new(amount, unit.clone()),
853 fee_reserve: Amount::new(fee_reserve, unit),
854 state,
855 expiry,
856 payment_preimage,
857 request_lookup_id,
858 options,
859 created_time,
860 paid_time,
861 payment_method,
862 }
863 }
864}
865
866#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
868pub struct MintKeySetInfo {
869 pub id: Id,
871 pub unit: CurrencyUnit,
873 pub active: bool,
876 pub valid_from: u64,
878 pub derivation_path: DerivationPath,
880 pub derivation_path_index: Option<u32>,
882 pub amounts: Vec<u64>,
884 #[serde(default = "default_fee")]
886 pub input_fee_ppk: u64,
887 pub final_expiry: Option<u64>,
889 pub issuer_version: Option<IssuerVersion>,
891}
892
893pub fn default_fee() -> u64 {
895 0
896}
897
898impl From<MintKeySetInfo> for KeySetInfo {
899 fn from(keyset_info: MintKeySetInfo) -> Self {
900 Self {
901 id: keyset_info.id,
902 unit: keyset_info.unit,
903 active: keyset_info.active,
904 input_fee_ppk: keyset_info.input_fee_ppk,
905 final_expiry: keyset_info.final_expiry,
906 }
907 }
908}
909
910impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
911 fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
912 MintQuoteBolt11Response {
913 quote: mint_quote.id.clone(),
914 state: mint_quote.state(),
915 request: mint_quote.request,
916 expiry: Some(mint_quote.expiry),
917 pubkey: mint_quote.pubkey,
918 amount: mint_quote.amount.map(Into::into),
919 unit: Some(mint_quote.unit.clone()),
920 }
921 }
922}
923
924impl From<MintQuote> for MintQuoteBolt11Response<String> {
925 fn from(quote: MintQuote) -> Self {
926 let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
927
928 quote.into()
929 }
930}
931
932impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
933 type Error = crate::Error;
934
935 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
936 Ok(MintQuoteBolt12Response {
937 quote: mint_quote.id.clone(),
938 request: mint_quote.request,
939 expiry: Some(mint_quote.expiry),
940 amount_paid: Amount::from(mint_quote.amount_paid.value()),
941 amount_issued: Amount::from(mint_quote.amount_issued.value()),
942 pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
943 amount: mint_quote.amount.map(Into::into),
944 unit: mint_quote.unit,
945 })
946 }
947}
948
949impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
950 type Error = crate::Error;
951
952 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
953 let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
954
955 Ok(quote.into())
956 }
957}
958
959impl TryFrom<crate::mint::MintQuote> for crate::nuts::MintQuoteCustomResponse<QuoteId> {
960 type Error = crate::Error;
961
962 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
963 Ok(crate::nuts::MintQuoteCustomResponse {
964 state: mint_quote.state(),
965 quote: mint_quote.id.clone(),
966 request: mint_quote.request,
967 expiry: Some(mint_quote.expiry),
968 pubkey: mint_quote.pubkey,
969 amount: mint_quote.amount.map(Into::into),
970 unit: Some(mint_quote.unit),
971 extra: mint_quote.extra_json.unwrap_or_default(),
972 })
973 }
974}
975
976impl TryFrom<MintQuote> for crate::nuts::MintQuoteCustomResponse<String> {
977 type Error = crate::Error;
978
979 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
980 let quote: crate::nuts::MintQuoteCustomResponse<QuoteId> = quote.try_into()?;
981
982 Ok(quote.into())
983 }
984}
985
986impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
987 fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
988 MeltQuoteBolt11Response {
989 quote: melt_quote.id.clone(),
990 payment_preimage: None,
991 change: None,
992 state: melt_quote.state,
993 expiry: melt_quote.expiry,
994 amount: melt_quote.amount().clone().into(),
995 fee_reserve: melt_quote.fee_reserve().clone().into(),
996 request: None,
997 unit: Some(melt_quote.unit.clone()),
998 }
999 }
1000}
1001
1002impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
1003 fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
1004 MeltQuoteBolt11Response {
1005 quote: melt_quote.id.clone(),
1006 amount: melt_quote.amount().clone().into(),
1007 fee_reserve: melt_quote.fee_reserve().clone().into(),
1008 state: melt_quote.state,
1009 expiry: melt_quote.expiry,
1010 payment_preimage: melt_quote.payment_preimage,
1011 change: None,
1012 request: Some(melt_quote.request.to_string()),
1013 unit: Some(melt_quote.unit.clone()),
1014 }
1015 }
1016}
1017
1018#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
1020pub enum MeltPaymentRequest {
1021 Bolt11 {
1023 bolt11: Bolt11Invoice,
1025 },
1026 Bolt12 {
1028 #[serde(with = "offer_serde")]
1030 offer: Box<Offer>,
1031 },
1032 Custom {
1034 method: String,
1036 request: String,
1038 },
1039}
1040
1041impl std::fmt::Display for MeltPaymentRequest {
1042 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1043 match self {
1044 MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
1045 MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
1046 MeltPaymentRequest::Custom { request, .. } => write!(f, "{request}"),
1047 }
1048 }
1049}
1050
1051mod offer_serde {
1052 use std::str::FromStr;
1053
1054 use serde::{self, Deserialize, Deserializer, Serializer};
1055
1056 use super::Offer;
1057
1058 pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
1059 where
1060 S: Serializer,
1061 {
1062 let s = offer.to_string();
1063 serializer.serialize_str(&s)
1064 }
1065
1066 pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
1067 where
1068 D: Deserializer<'de>,
1069 {
1070 let s = String::deserialize(deserializer)?;
1071 Ok(Box::new(Offer::from_str(&s).map_err(|_| {
1072 serde::de::Error::custom("Invalid Bolt12 Offer")
1073 })?))
1074 }
1075}