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::nuts::{MeltQuoteState, MintQuoteState};
20use crate::payment::PaymentIdentifier;
21use crate::{Amount, CurrencyUnit, Error, Id, KeySetInfo, PublicKey};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum OperationKind {
27 Swap,
29 Mint,
31 Melt,
33}
34
35#[derive(Debug)]
67pub struct ProofsWithState {
68 proofs: Proofs,
69 pub state: State,
71}
72
73impl Deref for ProofsWithState {
74 type Target = Proofs;
75
76 fn deref(&self) -> &Self::Target {
77 &self.proofs
78 }
79}
80
81impl ProofsWithState {
82 pub fn new(proofs: Proofs, current_state: State) -> Self {
89 Self {
90 proofs,
91 state: current_state,
92 }
93 }
94}
95
96impl fmt::Display for OperationKind {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 OperationKind::Swap => write!(f, "swap"),
100 OperationKind::Mint => write!(f, "mint"),
101 OperationKind::Melt => write!(f, "melt"),
102 }
103 }
104}
105
106impl FromStr for OperationKind {
107 type Err = Error;
108 fn from_str(value: &str) -> Result<Self, Self::Err> {
109 let value = value.to_lowercase();
110 match value.as_str() {
111 "swap" => Ok(OperationKind::Swap),
112 "mint" => Ok(OperationKind::Mint),
113 "melt" => Ok(OperationKind::Melt),
114 _ => Err(Error::Custom(format!("Invalid operation kind: {value}"))),
115 }
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "snake_case")]
122pub enum SwapSagaState {
123 SetupComplete,
125 Signed,
127}
128
129impl fmt::Display for SwapSagaState {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 SwapSagaState::SetupComplete => write!(f, "setup_complete"),
133 SwapSagaState::Signed => write!(f, "signed"),
134 }
135 }
136}
137
138impl FromStr for SwapSagaState {
139 type Err = Error;
140 fn from_str(value: &str) -> Result<Self, Self::Err> {
141 let value = value.to_lowercase();
142 match value.as_str() {
143 "setup_complete" => Ok(SwapSagaState::SetupComplete),
144 "signed" => Ok(SwapSagaState::Signed),
145 _ => Err(Error::Custom(format!("Invalid swap saga state: {value}"))),
146 }
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum MeltSagaState {
154 SetupComplete,
156 PaymentAttempted,
158 Finalizing,
160}
161
162impl fmt::Display for MeltSagaState {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 match self {
165 MeltSagaState::SetupComplete => write!(f, "setup_complete"),
166 MeltSagaState::PaymentAttempted => write!(f, "payment_attempted"),
167 MeltSagaState::Finalizing => write!(f, "finalizing"),
168 }
169 }
170}
171
172impl FromStr for MeltSagaState {
173 type Err = Error;
174 fn from_str(value: &str) -> Result<Self, Self::Err> {
175 let value = value.to_lowercase();
176 match value.as_str() {
177 "setup_complete" => Ok(MeltSagaState::SetupComplete),
178 "payment_attempted" => Ok(MeltSagaState::PaymentAttempted),
179 "finalizing" => Ok(MeltSagaState::Finalizing),
180 _ => Err(Error::Custom(format!("Invalid melt saga state: {}", value))),
181 }
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(tag = "type", rename_all = "snake_case")]
188pub enum SagaStateEnum {
189 Swap(SwapSagaState),
191 Melt(MeltSagaState),
193 }
196
197impl SagaStateEnum {
198 pub fn new(operation_kind: OperationKind, s: &str) -> Result<Self, Error> {
200 match operation_kind {
201 OperationKind::Swap => Ok(SagaStateEnum::Swap(SwapSagaState::from_str(s)?)),
202 OperationKind::Melt => Ok(SagaStateEnum::Melt(MeltSagaState::from_str(s)?)),
203 OperationKind::Mint => Err(Error::Custom("Mint saga not implemented yet".to_string())),
204 }
205 }
206
207 pub fn state(&self) -> &str {
209 match self {
210 SagaStateEnum::Swap(state) => match state {
211 SwapSagaState::SetupComplete => "setup_complete",
212 SwapSagaState::Signed => "signed",
213 },
214 SagaStateEnum::Melt(state) => match state {
215 MeltSagaState::SetupComplete => "setup_complete",
216 MeltSagaState::PaymentAttempted => "payment_attempted",
217 MeltSagaState::Finalizing => "finalizing",
218 },
219 }
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
225pub struct Saga {
226 pub operation_id: Uuid,
228 pub operation_kind: OperationKind,
230 pub state: SagaStateEnum,
232 pub quote_id: Option<String>,
235 pub created_at: u64,
237 pub updated_at: u64,
239}
240
241impl Saga {
242 pub fn new_swap(operation_id: Uuid, state: SwapSagaState) -> Self {
244 let now = unix_time();
245 Self {
246 operation_id,
247 operation_kind: OperationKind::Swap,
248 state: SagaStateEnum::Swap(state),
249 quote_id: None,
250 created_at: now,
251 updated_at: now,
252 }
253 }
254
255 pub fn update_swap_state(&mut self, new_state: SwapSagaState) {
257 self.state = SagaStateEnum::Swap(new_state);
258 self.updated_at = unix_time();
259 }
260
261 pub fn new_melt(operation_id: Uuid, state: MeltSagaState, quote_id: String) -> Self {
263 let now = unix_time();
264 Self {
265 operation_id,
266 operation_kind: OperationKind::Melt,
267 state: SagaStateEnum::Melt(state),
268 quote_id: Some(quote_id),
269 created_at: now,
270 updated_at: now,
271 }
272 }
273
274 pub fn update_melt_state(&mut self, new_state: MeltSagaState) {
276 self.state = SagaStateEnum::Melt(new_state);
277 self.updated_at = unix_time();
278 }
279}
280
281#[derive(Debug)]
283pub struct Operation {
284 id: Uuid,
285 kind: OperationKind,
286 total_issued: Amount,
287 total_redeemed: Amount,
288 fee_collected: Amount,
289 complete_at: Option<u64>,
290 payment_amount: Option<Amount>,
292 payment_fee: Option<Amount>,
294 payment_method: Option<PaymentMethod>,
296}
297
298impl Operation {
299 pub fn new(
301 id: Uuid,
302 kind: OperationKind,
303 total_issued: Amount,
304 total_redeemed: Amount,
305 fee_collected: Amount,
306 complete_at: Option<u64>,
307 payment_method: Option<PaymentMethod>,
308 ) -> Self {
309 Self {
310 id,
311 kind,
312 total_issued,
313 total_redeemed,
314 fee_collected,
315 complete_at,
316 payment_amount: None,
317 payment_fee: None,
318 payment_method,
319 }
320 }
321
322 pub fn new_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
324 Self {
325 id: Uuid::new_v4(),
326 kind: OperationKind::Mint,
327 total_issued,
328 total_redeemed: Amount::ZERO,
329 fee_collected: Amount::ZERO,
330 complete_at: None,
331 payment_amount: None,
332 payment_fee: None,
333 payment_method: Some(payment_method),
334 }
335 }
336 pub fn new_melt(
340 total_redeemed: Amount,
341 fee_collected: Amount,
342 payment_method: PaymentMethod,
343 ) -> Self {
344 Self {
345 id: Uuid::new_v4(),
346 kind: OperationKind::Melt,
347 total_issued: Amount::ZERO,
348 total_redeemed,
349 fee_collected,
350 complete_at: None,
351 payment_amount: None,
352 payment_fee: None,
353 payment_method: Some(payment_method),
354 }
355 }
356
357 pub fn new_swap(total_issued: Amount, total_redeemed: Amount, fee_collected: Amount) -> Self {
359 Self {
360 id: Uuid::new_v4(),
361 kind: OperationKind::Swap,
362 total_issued,
363 total_redeemed,
364 fee_collected,
365 complete_at: None,
366 payment_amount: None,
367 payment_fee: None,
368 payment_method: None,
369 }
370 }
371
372 pub fn id(&self) -> &Uuid {
374 &self.id
375 }
376
377 pub fn kind(&self) -> OperationKind {
379 self.kind
380 }
381
382 pub fn total_issued(&self) -> Amount {
384 self.total_issued
385 }
386
387 pub fn total_redeemed(&self) -> Amount {
389 self.total_redeemed
390 }
391
392 pub fn fee_collected(&self) -> Amount {
394 self.fee_collected
395 }
396
397 pub fn completed_at(&self) -> &Option<u64> {
399 &self.complete_at
400 }
401
402 pub fn add_change(&mut self, change: Amount) {
404 self.total_issued = change;
405 }
406
407 pub fn payment_amount(&self) -> Option<Amount> {
409 self.payment_amount
410 }
411
412 pub fn payment_fee(&self) -> Option<Amount> {
414 self.payment_fee
415 }
416
417 pub fn set_payment_details(&mut self, payment_amount: Amount, payment_fee: Amount) {
419 self.payment_amount = Some(payment_amount);
420 self.payment_fee = Some(payment_fee);
421 }
422
423 pub fn payment_method(&self) -> Option<PaymentMethod> {
425 self.payment_method.clone()
426 }
427}
428
429#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
440pub struct MintQuoteChange {
441 pub payments: Option<Vec<IncomingPayment>>,
443 pub issuances: Option<Vec<Amount>>,
445}
446
447#[derive(Debug, Clone, Hash, PartialEq, Eq)]
449pub struct MintQuote {
450 pub id: QuoteId,
452 pub amount: Option<Amount<CurrencyUnit>>,
454 pub unit: CurrencyUnit,
456 pub request: String,
458 pub expiry: u64,
460 pub request_lookup_id: PaymentIdentifier,
462 pub pubkey: Option<PublicKey>,
464 pub created_time: u64,
466 amount_paid: Amount<CurrencyUnit>,
468 amount_issued: Amount<CurrencyUnit>,
470 pub payments: Vec<IncomingPayment>,
472 pub payment_method: PaymentMethod,
474 pub issuance: Vec<Issuance>,
476 pub extra_json: Option<serde_json::Value>,
478 changes: Option<MintQuoteChange>,
484}
485
486impl MintQuote {
487 #[allow(clippy::too_many_arguments)]
489 pub fn new(
490 id: Option<QuoteId>,
491 request: String,
492 unit: CurrencyUnit,
493 amount: Option<Amount<CurrencyUnit>>,
494 expiry: u64,
495 request_lookup_id: PaymentIdentifier,
496 pubkey: Option<PublicKey>,
497 amount_paid: Amount<CurrencyUnit>,
498 amount_issued: Amount<CurrencyUnit>,
499 payment_method: PaymentMethod,
500 created_time: u64,
501 payments: Vec<IncomingPayment>,
502 issuance: Vec<Issuance>,
503 extra_json: Option<serde_json::Value>,
504 ) -> Self {
505 let id = id.unwrap_or_else(QuoteId::new_uuid);
506
507 Self {
508 id,
509 amount,
510 unit: unit.clone(),
511 request,
512 expiry,
513 request_lookup_id,
514 pubkey,
515 created_time,
516 amount_paid,
517 amount_issued,
518 payment_method,
519 payments,
520 issuance,
521 extra_json,
522 changes: None,
523 }
524 }
525
526 #[instrument(skip(self))]
528 pub fn increment_amount_paid(
529 &mut self,
530 additional_amount: Amount<CurrencyUnit>,
531 ) -> Result<Amount, crate::Error> {
532 self.amount_paid = self
533 .amount_paid
534 .checked_add(&additional_amount)
535 .map_err(|_| crate::Error::AmountOverflow)?;
536 Ok(Amount::from(self.amount_paid.value()))
537 }
538
539 #[instrument(skip(self))]
541 pub fn amount_paid(&self) -> Amount<CurrencyUnit> {
542 self.amount_paid.clone()
543 }
544
545 #[instrument(skip(self))]
568 pub fn add_issuance(
569 &mut self,
570 additional_amount: Amount<CurrencyUnit>,
571 ) -> Result<Amount<CurrencyUnit>, crate::Error> {
572 let new_amount_issued = self
573 .amount_issued
574 .checked_add(&additional_amount)
575 .map_err(|_| crate::Error::AmountOverflow)?;
576
577 if new_amount_issued > self.amount_paid {
579 return Err(crate::Error::OverIssue);
580 }
581
582 self.changes
583 .get_or_insert_default()
584 .issuances
585 .get_or_insert_default()
586 .push(additional_amount.into());
587
588 self.amount_issued = new_amount_issued;
589
590 Ok(self.amount_issued.clone())
591 }
592
593 #[instrument(skip(self))]
595 pub fn amount_issued(&self) -> Amount<CurrencyUnit> {
596 self.amount_issued.clone()
597 }
598
599 #[instrument(skip(self))]
601 pub fn state(&self) -> MintQuoteState {
602 self.compute_quote_state()
603 }
604
605 pub fn payment_ids(&self) -> Vec<&String> {
607 self.payments.iter().map(|a| &a.payment_id).collect()
608 }
609
610 pub fn amount_mintable(&self) -> Amount<CurrencyUnit> {
617 self.amount_paid
618 .checked_sub(&self.amount_issued)
619 .unwrap_or_else(|_| Amount::new(0, self.unit.clone()))
620 }
621
622 pub fn take_changes(&mut self) -> Option<MintQuoteChange> {
632 self.changes.take()
633 }
634
635 #[instrument(skip(self))]
655 pub fn add_payment(
656 &mut self,
657 amount: Amount<CurrencyUnit>,
658 payment_id: String,
659 time: Option<u64>,
660 ) -> Result<(), crate::Error> {
661 let time = time.unwrap_or_else(unix_time);
662
663 let payment_ids = self.payment_ids();
664 if payment_ids.contains(&&payment_id) {
665 return Err(crate::Error::DuplicatePaymentId);
666 }
667
668 self.amount_paid = self
669 .amount_paid
670 .checked_add(&amount)
671 .map_err(|_| crate::Error::AmountOverflow)?;
672
673 let payment = IncomingPayment::new(amount, payment_id, time);
674
675 self.payments.push(payment.clone());
676
677 self.changes
678 .get_or_insert_default()
679 .payments
680 .get_or_insert_default()
681 .push(payment);
682
683 Ok(())
684 }
685
686 #[instrument(skip(self))]
688 fn compute_quote_state(&self) -> MintQuoteState {
689 let zero_amount = Amount::new(0, self.unit.clone());
690
691 if self.amount_paid == zero_amount && self.amount_issued == zero_amount {
692 return MintQuoteState::Unpaid;
693 }
694
695 match self.amount_paid.value().cmp(&self.amount_issued.value()) {
696 std::cmp::Ordering::Less => {
697 tracing::error!("We should not have issued more then has been paid");
698 MintQuoteState::Issued
699 }
700 std::cmp::Ordering::Equal => MintQuoteState::Issued,
701 std::cmp::Ordering::Greater => MintQuoteState::Paid,
702 }
703 }
704}
705
706#[derive(Debug, Clone, Hash, PartialEq, Eq)]
708pub struct IncomingPayment {
709 pub amount: Amount<CurrencyUnit>,
711 pub time: u64,
713 pub payment_id: String,
715}
716
717impl IncomingPayment {
718 pub fn new(amount: Amount<CurrencyUnit>, payment_id: String, time: u64) -> Self {
720 Self {
721 payment_id,
722 time,
723 amount,
724 }
725 }
726}
727
728#[derive(Debug, Clone, Hash, PartialEq, Eq)]
730pub struct Issuance {
731 pub amount: Amount<CurrencyUnit>,
733 pub time: u64,
735}
736
737impl Issuance {
738 pub fn new(amount: Amount<CurrencyUnit>, time: u64) -> Self {
740 Self { amount, time }
741 }
742}
743
744#[derive(Debug, Clone, Hash, PartialEq, Eq)]
746pub struct MeltQuote {
747 pub id: QuoteId,
749 pub unit: CurrencyUnit,
751 pub request: MeltPaymentRequest,
753 amount: Amount<CurrencyUnit>,
755 fee_reserve: Amount<CurrencyUnit>,
757 pub state: MeltQuoteState,
759 pub expiry: u64,
761 pub payment_preimage: Option<String>,
763 pub request_lookup_id: Option<PaymentIdentifier>,
765 pub options: Option<MeltOptions>,
769 pub created_time: u64,
771 pub paid_time: Option<u64>,
773 pub payment_method: PaymentMethod,
775}
776
777impl MeltQuote {
778 #[allow(clippy::too_many_arguments)]
780 pub fn new(
781 request: MeltPaymentRequest,
782 unit: CurrencyUnit,
783 amount: Amount<CurrencyUnit>,
784 fee_reserve: Amount<CurrencyUnit>,
785 expiry: u64,
786 request_lookup_id: Option<PaymentIdentifier>,
787 options: Option<MeltOptions>,
788 payment_method: PaymentMethod,
789 ) -> Self {
790 let id = Uuid::new_v4();
791
792 Self {
793 id: QuoteId::UUID(id),
794 unit: unit.clone(),
795 request,
796 amount,
797 fee_reserve,
798 state: MeltQuoteState::Unpaid,
799 expiry,
800 payment_preimage: None,
801 request_lookup_id,
802 options,
803 created_time: unix_time(),
804 paid_time: None,
805 payment_method,
806 }
807 }
808
809 #[inline]
811 pub fn amount(&self) -> Amount<CurrencyUnit> {
812 self.amount.clone()
813 }
814
815 #[inline]
817 pub fn fee_reserve(&self) -> Amount<CurrencyUnit> {
818 self.fee_reserve.clone()
819 }
820
821 pub fn total_needed(&self) -> Result<Amount, crate::Error> {
823 let total = self
824 .amount
825 .checked_add(&self.fee_reserve)
826 .map_err(|_| crate::Error::AmountOverflow)?;
827 Ok(Amount::from(total.value()))
828 }
829
830 #[allow(clippy::too_many_arguments)]
832 pub fn from_db(
833 id: QuoteId,
834 unit: CurrencyUnit,
835 request: MeltPaymentRequest,
836 amount: u64,
837 fee_reserve: u64,
838 state: MeltQuoteState,
839 expiry: u64,
840 payment_preimage: Option<String>,
841 request_lookup_id: Option<PaymentIdentifier>,
842 options: Option<MeltOptions>,
843 created_time: u64,
844 paid_time: Option<u64>,
845 payment_method: PaymentMethod,
846 ) -> Self {
847 Self {
848 id,
849 unit: unit.clone(),
850 request,
851 amount: Amount::new(amount, unit.clone()),
852 fee_reserve: Amount::new(fee_reserve, unit),
853 state,
854 expiry,
855 payment_preimage,
856 request_lookup_id,
857 options,
858 created_time,
859 paid_time,
860 payment_method,
861 }
862 }
863}
864
865#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
867pub struct MintKeySetInfo {
868 pub id: Id,
870 pub unit: CurrencyUnit,
872 pub active: bool,
875 pub valid_from: u64,
877 pub derivation_path: DerivationPath,
879 pub derivation_path_index: Option<u32>,
881 pub amounts: Vec<u64>,
883 #[serde(default = "default_fee")]
885 pub input_fee_ppk: u64,
886 pub final_expiry: Option<u64>,
888}
889
890pub fn default_fee() -> u64 {
892 0
893}
894
895impl From<MintKeySetInfo> for KeySetInfo {
896 fn from(keyset_info: MintKeySetInfo) -> Self {
897 Self {
898 id: keyset_info.id,
899 unit: keyset_info.unit,
900 active: keyset_info.active,
901 input_fee_ppk: keyset_info.input_fee_ppk,
902 final_expiry: keyset_info.final_expiry,
903 }
904 }
905}
906
907impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
908 fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
909 MintQuoteBolt11Response {
910 quote: mint_quote.id.clone(),
911 state: mint_quote.state(),
912 request: mint_quote.request,
913 expiry: Some(mint_quote.expiry),
914 pubkey: mint_quote.pubkey,
915 amount: mint_quote.amount.map(Into::into),
916 unit: Some(mint_quote.unit.clone()),
917 }
918 }
919}
920
921impl From<MintQuote> for MintQuoteBolt11Response<String> {
922 fn from(quote: MintQuote) -> Self {
923 let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
924
925 quote.into()
926 }
927}
928
929impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
930 type Error = crate::Error;
931
932 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
933 Ok(MintQuoteBolt12Response {
934 quote: mint_quote.id.clone(),
935 request: mint_quote.request,
936 expiry: Some(mint_quote.expiry),
937 amount_paid: Amount::from(mint_quote.amount_paid.value()),
938 amount_issued: Amount::from(mint_quote.amount_issued.value()),
939 pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
940 amount: mint_quote.amount.map(Into::into),
941 unit: mint_quote.unit,
942 })
943 }
944}
945
946impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
947 type Error = crate::Error;
948
949 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
950 let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
951
952 Ok(quote.into())
953 }
954}
955
956impl TryFrom<crate::mint::MintQuote> for crate::nuts::MintQuoteCustomResponse<QuoteId> {
957 type Error = crate::Error;
958
959 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
960 Ok(crate::nuts::MintQuoteCustomResponse {
961 state: mint_quote.state(),
962 quote: mint_quote.id.clone(),
963 request: mint_quote.request,
964 expiry: Some(mint_quote.expiry),
965 pubkey: mint_quote.pubkey,
966 amount: mint_quote.amount.map(Into::into),
967 unit: Some(mint_quote.unit),
968 extra: mint_quote.extra_json.unwrap_or_default(),
969 })
970 }
971}
972
973impl TryFrom<MintQuote> for crate::nuts::MintQuoteCustomResponse<String> {
974 type Error = crate::Error;
975
976 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
977 let quote: crate::nuts::MintQuoteCustomResponse<QuoteId> = quote.try_into()?;
978
979 Ok(quote.into())
980 }
981}
982
983impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
984 fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
985 MeltQuoteBolt11Response {
986 quote: melt_quote.id.clone(),
987 payment_preimage: None,
988 change: None,
989 state: melt_quote.state,
990 expiry: melt_quote.expiry,
991 amount: melt_quote.amount().clone().into(),
992 fee_reserve: melt_quote.fee_reserve().clone().into(),
993 request: None,
994 unit: Some(melt_quote.unit.clone()),
995 }
996 }
997}
998
999impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
1000 fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
1001 MeltQuoteBolt11Response {
1002 quote: melt_quote.id.clone(),
1003 amount: melt_quote.amount().clone().into(),
1004 fee_reserve: melt_quote.fee_reserve().clone().into(),
1005 state: melt_quote.state,
1006 expiry: melt_quote.expiry,
1007 payment_preimage: melt_quote.payment_preimage,
1008 change: None,
1009 request: Some(melt_quote.request.to_string()),
1010 unit: Some(melt_quote.unit.clone()),
1011 }
1012 }
1013}
1014
1015#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
1017pub enum MeltPaymentRequest {
1018 Bolt11 {
1020 bolt11: Bolt11Invoice,
1022 },
1023 Bolt12 {
1025 #[serde(with = "offer_serde")]
1027 offer: Box<Offer>,
1028 },
1029 Custom {
1031 method: String,
1033 request: String,
1035 },
1036}
1037
1038impl std::fmt::Display for MeltPaymentRequest {
1039 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1040 match self {
1041 MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
1042 MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
1043 MeltPaymentRequest::Custom { request, .. } => write!(f, "{request}"),
1044 }
1045 }
1046}
1047
1048mod offer_serde {
1049 use std::str::FromStr;
1050
1051 use serde::{self, Deserialize, Deserializer, Serializer};
1052
1053 use super::Offer;
1054
1055 pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
1056 where
1057 S: Serializer,
1058 {
1059 let s = offer.to_string();
1060 serializer.serialize_str(&s)
1061 }
1062
1063 pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
1064 where
1065 D: Deserializer<'de>,
1066 {
1067 let s = String::deserialize(deserializer)?;
1068 Ok(Box::new(Offer::from_str(&s).map_err(|_| {
1069 serde::de::Error::custom("Invalid Bolt12 Offer")
1070 })?))
1071 }
1072}