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::mint_quote::MintQuoteResponse;
21use crate::nuts::{MeltQuoteState, MintQuoteState};
22use crate::payment::PaymentIdentifier;
23use crate::{Amount, CurrencyUnit, Error, Id, KeySetInfo, PublicKey};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "lowercase")]
28pub enum OperationKind {
29 Swap,
31 Mint,
33 Melt,
35 BatchMint,
37}
38
39#[derive(Debug)]
71pub struct ProofsWithState {
72 proofs: Proofs,
73 pub state: State,
75}
76
77impl Deref for ProofsWithState {
78 type Target = Proofs;
79
80 fn deref(&self) -> &Self::Target {
81 &self.proofs
82 }
83}
84
85impl ProofsWithState {
86 pub fn new(proofs: Proofs, current_state: State) -> Self {
93 Self {
94 proofs,
95 state: current_state,
96 }
97 }
98}
99
100impl fmt::Display for OperationKind {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 match self {
103 OperationKind::Swap => write!(f, "swap"),
104 OperationKind::Mint => write!(f, "mint"),
105 OperationKind::Melt => write!(f, "melt"),
106 OperationKind::BatchMint => write!(f, "batch_mint"),
107 }
108 }
109}
110
111impl FromStr for OperationKind {
112 type Err = Error;
113 fn from_str(value: &str) -> Result<Self, Self::Err> {
114 let value = value.to_lowercase();
115 match value.as_str() {
116 "swap" => Ok(OperationKind::Swap),
117 "mint" => Ok(OperationKind::Mint),
118 "melt" => Ok(OperationKind::Melt),
119 "batch_mint" => Ok(OperationKind::BatchMint),
120 _ => Err(Error::Custom(format!("Invalid operation kind: {value}"))),
121 }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum SwapSagaState {
129 SetupComplete,
131 Signed,
133}
134
135impl fmt::Display for SwapSagaState {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 SwapSagaState::SetupComplete => write!(f, "setup_complete"),
139 SwapSagaState::Signed => write!(f, "signed"),
140 }
141 }
142}
143
144impl FromStr for SwapSagaState {
145 type Err = Error;
146 fn from_str(value: &str) -> Result<Self, Self::Err> {
147 let value = value.to_lowercase();
148 match value.as_str() {
149 "setup_complete" => Ok(SwapSagaState::SetupComplete),
150 "signed" => Ok(SwapSagaState::Signed),
151 _ => Err(Error::Custom(format!("Invalid swap saga state: {value}"))),
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(rename_all = "snake_case")]
159pub enum MeltSagaState {
160 SetupComplete,
162 PaymentAttempted,
164 Finalizing,
166}
167
168impl fmt::Display for MeltSagaState {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match self {
171 MeltSagaState::SetupComplete => write!(f, "setup_complete"),
172 MeltSagaState::PaymentAttempted => write!(f, "payment_attempted"),
173 MeltSagaState::Finalizing => write!(f, "finalizing"),
174 }
175 }
176}
177
178impl FromStr for MeltSagaState {
179 type Err = Error;
180 fn from_str(value: &str) -> Result<Self, Self::Err> {
181 let value = value.to_lowercase();
182 match value.as_str() {
183 "setup_complete" => Ok(MeltSagaState::SetupComplete),
184 "payment_attempted" => Ok(MeltSagaState::PaymentAttempted),
185 "finalizing" => Ok(MeltSagaState::Finalizing),
186 _ => Err(Error::Custom(format!("Invalid melt saga state: {}", value))),
187 }
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(tag = "type", rename_all = "snake_case")]
194pub enum SagaStateEnum {
195 Swap(SwapSagaState),
197 Melt(MeltSagaState),
199 }
202
203impl SagaStateEnum {
204 pub fn new(operation_kind: OperationKind, s: &str) -> Result<Self, Error> {
206 match operation_kind {
207 OperationKind::Swap => Ok(SagaStateEnum::Swap(SwapSagaState::from_str(s)?)),
208 OperationKind::Melt => Ok(SagaStateEnum::Melt(MeltSagaState::from_str(s)?)),
209 OperationKind::Mint | OperationKind::BatchMint => {
210 Err(Error::Custom("Mint saga not implemented yet".to_string()))
211 }
212 }
213 }
214
215 pub fn state(&self) -> &str {
217 match self {
218 SagaStateEnum::Swap(state) => match state {
219 SwapSagaState::SetupComplete => "setup_complete",
220 SwapSagaState::Signed => "signed",
221 },
222 SagaStateEnum::Melt(state) => match state {
223 MeltSagaState::SetupComplete => "setup_complete",
224 MeltSagaState::PaymentAttempted => "payment_attempted",
225 MeltSagaState::Finalizing => "finalizing",
226 },
227 }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct Saga {
234 pub operation_id: Uuid,
236 pub operation_kind: OperationKind,
238 pub state: SagaStateEnum,
240 pub quote_id: Option<String>,
243 pub created_at: u64,
245 pub updated_at: u64,
247}
248
249impl Saga {
250 pub fn new_swap(operation_id: Uuid, state: SwapSagaState) -> Self {
252 let now = unix_time();
253 Self {
254 operation_id,
255 operation_kind: OperationKind::Swap,
256 state: SagaStateEnum::Swap(state),
257 quote_id: None,
258 created_at: now,
259 updated_at: now,
260 }
261 }
262
263 pub fn update_swap_state(&mut self, new_state: SwapSagaState) {
265 self.state = SagaStateEnum::Swap(new_state);
266 self.updated_at = unix_time();
267 }
268
269 pub fn new_melt(operation_id: Uuid, state: MeltSagaState, quote_id: String) -> Self {
271 let now = unix_time();
272 Self {
273 operation_id,
274 operation_kind: OperationKind::Melt,
275 state: SagaStateEnum::Melt(state),
276 quote_id: Some(quote_id),
277 created_at: now,
278 updated_at: now,
279 }
280 }
281
282 pub fn update_melt_state(&mut self, new_state: MeltSagaState) {
284 self.state = SagaStateEnum::Melt(new_state);
285 self.updated_at = unix_time();
286 }
287}
288
289#[derive(Debug)]
291pub struct Operation {
292 id: Uuid,
293 kind: OperationKind,
294 total_issued: Amount,
295 total_redeemed: Amount,
296 fee_collected: Amount,
297 complete_at: Option<u64>,
298 payment_amount: Option<Amount>,
300 payment_fee: Option<Amount>,
302 payment_method: Option<PaymentMethod>,
304}
305
306impl Operation {
307 pub fn new(
309 id: Uuid,
310 kind: OperationKind,
311 total_issued: Amount,
312 total_redeemed: Amount,
313 fee_collected: Amount,
314 complete_at: Option<u64>,
315 payment_method: Option<PaymentMethod>,
316 ) -> Self {
317 Self {
318 id,
319 kind,
320 total_issued,
321 total_redeemed,
322 fee_collected,
323 complete_at,
324 payment_amount: None,
325 payment_fee: None,
326 payment_method,
327 }
328 }
329
330 pub fn new_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
332 Self {
333 id: Uuid::new_v4(),
334 kind: OperationKind::Mint,
335 total_issued,
336 total_redeemed: Amount::ZERO,
337 fee_collected: Amount::ZERO,
338 complete_at: None,
339 payment_amount: None,
340 payment_fee: None,
341 payment_method: Some(payment_method),
342 }
343 }
344
345 pub fn new_batch_mint(total_issued: Amount, payment_method: PaymentMethod) -> Self {
347 Self {
348 id: Uuid::new_v4(),
349 kind: OperationKind::BatchMint,
350 total_issued,
351 total_redeemed: Amount::ZERO,
352 fee_collected: Amount::ZERO,
353 complete_at: None,
354 payment_amount: None,
355 payment_fee: None,
356 payment_method: Some(payment_method),
357 }
358 }
359
360 pub fn new_melt(
364 total_redeemed: Amount,
365 fee_collected: Amount,
366 payment_method: PaymentMethod,
367 ) -> Self {
368 Self {
369 id: Uuid::new_v4(),
370 kind: OperationKind::Melt,
371 total_issued: Amount::ZERO,
372 total_redeemed,
373 fee_collected,
374 complete_at: None,
375 payment_amount: None,
376 payment_fee: None,
377 payment_method: Some(payment_method),
378 }
379 }
380
381 pub fn new_swap(total_issued: Amount, total_redeemed: Amount, fee_collected: Amount) -> Self {
383 Self {
384 id: Uuid::new_v4(),
385 kind: OperationKind::Swap,
386 total_issued,
387 total_redeemed,
388 fee_collected,
389 complete_at: None,
390 payment_amount: None,
391 payment_fee: None,
392 payment_method: None,
393 }
394 }
395
396 pub fn id(&self) -> &Uuid {
398 &self.id
399 }
400
401 pub fn kind(&self) -> OperationKind {
403 self.kind
404 }
405
406 pub fn total_issued(&self) -> Amount {
408 self.total_issued
409 }
410
411 pub fn total_redeemed(&self) -> Amount {
413 self.total_redeemed
414 }
415
416 pub fn fee_collected(&self) -> Amount {
418 self.fee_collected
419 }
420
421 pub fn completed_at(&self) -> &Option<u64> {
423 &self.complete_at
424 }
425
426 pub fn add_change(&mut self, change: Amount) {
428 self.total_issued = change;
429 }
430
431 pub fn payment_amount(&self) -> Option<Amount> {
433 self.payment_amount
434 }
435
436 pub fn payment_fee(&self) -> Option<Amount> {
438 self.payment_fee
439 }
440
441 pub fn set_payment_details(&mut self, payment_amount: Amount, payment_fee: Amount) {
443 self.payment_amount = Some(payment_amount);
444 self.payment_fee = Some(payment_fee);
445 }
446
447 pub fn payment_method(&self) -> Option<PaymentMethod> {
449 self.payment_method.clone()
450 }
451}
452
453#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
464pub struct MintQuoteChange {
465 pub payments: Option<Vec<IncomingPayment>>,
467 pub issuances: Option<Vec<Amount>>,
469}
470
471#[derive(Debug, Clone, Hash, PartialEq, Eq)]
473pub struct MintQuote {
474 pub id: QuoteId,
476 pub amount: Option<Amount<CurrencyUnit>>,
478 pub unit: CurrencyUnit,
480 pub request: String,
482 pub expiry: u64,
484 pub request_lookup_id: PaymentIdentifier,
486 pub pubkey: Option<PublicKey>,
488 pub created_time: u64,
490 amount_paid: Amount<CurrencyUnit>,
492 amount_issued: Amount<CurrencyUnit>,
494 pub payments: Vec<IncomingPayment>,
496 pub payment_method: PaymentMethod,
498 pub issuance: Vec<Issuance>,
500 pub extra_json: Option<serde_json::Value>,
502 changes: Option<MintQuoteChange>,
508}
509
510impl MintQuote {
511 #[allow(clippy::too_many_arguments)]
513 pub fn new(
514 id: Option<QuoteId>,
515 request: String,
516 unit: CurrencyUnit,
517 amount: Option<Amount<CurrencyUnit>>,
518 expiry: u64,
519 request_lookup_id: PaymentIdentifier,
520 pubkey: Option<PublicKey>,
521 amount_paid: Amount<CurrencyUnit>,
522 amount_issued: Amount<CurrencyUnit>,
523 payment_method: PaymentMethod,
524 created_time: u64,
525 payments: Vec<IncomingPayment>,
526 issuance: Vec<Issuance>,
527 extra_json: Option<serde_json::Value>,
528 ) -> Self {
529 let id = id.unwrap_or_else(QuoteId::new_uuid);
530
531 Self {
532 id,
533 amount,
534 unit: unit.clone(),
535 request,
536 expiry,
537 request_lookup_id,
538 pubkey,
539 created_time,
540 amount_paid,
541 amount_issued,
542 payment_method,
543 payments,
544 issuance,
545 extra_json,
546 changes: None,
547 }
548 }
549
550 #[instrument(skip(self))]
552 pub fn increment_amount_paid(
553 &mut self,
554 additional_amount: Amount<CurrencyUnit>,
555 ) -> Result<Amount, crate::Error> {
556 self.amount_paid = self
557 .amount_paid
558 .checked_add(&additional_amount)
559 .map_err(|_| crate::Error::AmountOverflow)?;
560 Ok(Amount::from(self.amount_paid.value()))
561 }
562
563 #[instrument(skip(self))]
565 pub fn amount_paid(&self) -> Amount<CurrencyUnit> {
566 self.amount_paid.clone()
567 }
568
569 #[instrument(skip(self))]
592 pub fn add_issuance(
593 &mut self,
594 additional_amount: Amount<CurrencyUnit>,
595 ) -> Result<Amount<CurrencyUnit>, crate::Error> {
596 let new_amount_issued = self
597 .amount_issued
598 .checked_add(&additional_amount)
599 .map_err(|_| crate::Error::AmountOverflow)?;
600
601 if new_amount_issued > self.amount_paid {
603 return Err(crate::Error::OverIssue);
604 }
605
606 self.changes
607 .get_or_insert_default()
608 .issuances
609 .get_or_insert_default()
610 .push(additional_amount.into());
611
612 self.amount_issued = new_amount_issued;
613
614 Ok(self.amount_issued.clone())
615 }
616
617 #[instrument(skip(self))]
619 pub fn amount_issued(&self) -> Amount<CurrencyUnit> {
620 self.amount_issued.clone()
621 }
622
623 #[instrument(skip(self))]
625 pub fn state(&self) -> MintQuoteState {
626 self.compute_quote_state()
627 }
628
629 pub fn payment_ids(&self) -> Vec<&String> {
631 self.payments.iter().map(|a| &a.payment_id).collect()
632 }
633
634 pub fn amount_mintable(&self) -> Amount<CurrencyUnit> {
641 self.amount_paid
642 .checked_sub(&self.amount_issued)
643 .unwrap_or_else(|_| Amount::new(0, self.unit.clone()))
644 }
645
646 pub fn take_changes(&mut self) -> Option<MintQuoteChange> {
656 self.changes.take()
657 }
658
659 #[instrument(skip(self))]
679 pub fn add_payment(
680 &mut self,
681 amount: Amount<CurrencyUnit>,
682 payment_id: String,
683 time: Option<u64>,
684 ) -> Result<(), crate::Error> {
685 let time = time.unwrap_or_else(unix_time);
686
687 let payment_ids = self.payment_ids();
688 if payment_ids.contains(&&payment_id) {
689 return Err(crate::Error::DuplicatePaymentId);
690 }
691
692 self.amount_paid = self
693 .amount_paid
694 .checked_add(&amount)
695 .map_err(|_| crate::Error::AmountOverflow)?;
696
697 let payment = IncomingPayment::new(amount, payment_id, time);
698
699 self.payments.push(payment.clone());
700
701 self.changes
702 .get_or_insert_default()
703 .payments
704 .get_or_insert_default()
705 .push(payment);
706
707 Ok(())
708 }
709
710 #[instrument(skip(self))]
712 fn compute_quote_state(&self) -> MintQuoteState {
713 let zero_amount = Amount::new(0, self.unit.clone());
714
715 if self.amount_paid == zero_amount && self.amount_issued == zero_amount {
716 return MintQuoteState::Unpaid;
717 }
718
719 match self.amount_paid.value().cmp(&self.amount_issued.value()) {
720 std::cmp::Ordering::Less => {
721 tracing::error!("We should not have issued more then has been paid");
722 MintQuoteState::Issued
723 }
724 std::cmp::Ordering::Equal => MintQuoteState::Issued,
725 std::cmp::Ordering::Greater => MintQuoteState::Paid,
726 }
727 }
728}
729
730#[derive(Debug, Clone, Hash, PartialEq, Eq)]
732pub struct IncomingPayment {
733 pub amount: Amount<CurrencyUnit>,
735 pub time: u64,
737 pub payment_id: String,
739}
740
741impl IncomingPayment {
742 pub fn new(amount: Amount<CurrencyUnit>, payment_id: String, time: u64) -> Self {
744 Self {
745 payment_id,
746 time,
747 amount,
748 }
749 }
750}
751
752#[derive(Debug, Clone, Hash, PartialEq, Eq)]
754pub struct Issuance {
755 pub amount: Amount<CurrencyUnit>,
757 pub time: u64,
759}
760
761impl Issuance {
762 pub fn new(amount: Amount<CurrencyUnit>, time: u64) -> Self {
764 Self { amount, time }
765 }
766}
767
768#[derive(Debug, Clone, Hash, PartialEq, Eq)]
770pub struct MeltQuote {
771 pub id: QuoteId,
773 pub unit: CurrencyUnit,
775 pub request: MeltPaymentRequest,
777 amount: Amount<CurrencyUnit>,
779 fee_reserve: Amount<CurrencyUnit>,
781 pub state: MeltQuoteState,
783 pub expiry: u64,
785 pub payment_preimage: Option<String>,
787 pub request_lookup_id: Option<PaymentIdentifier>,
789 pub options: Option<MeltOptions>,
793 pub created_time: u64,
795 pub paid_time: Option<u64>,
797 pub payment_method: PaymentMethod,
799}
800
801impl MeltQuote {
802 #[allow(clippy::too_many_arguments)]
804 pub fn new(
805 id: Option<QuoteId>,
806 request: MeltPaymentRequest,
807 unit: CurrencyUnit,
808 amount: Amount<CurrencyUnit>,
809 fee_reserve: Amount<CurrencyUnit>,
810 expiry: u64,
811 request_lookup_id: Option<PaymentIdentifier>,
812 options: Option<MeltOptions>,
813 payment_method: PaymentMethod,
814 ) -> Self {
815 let id = id.unwrap_or_else(QuoteId::new_uuid);
816
817 Self {
818 id,
819 unit: unit.clone(),
820 request,
821 amount,
822 fee_reserve,
823 state: MeltQuoteState::Unpaid,
824 expiry,
825 payment_preimage: None,
826 request_lookup_id,
827 options,
828 created_time: unix_time(),
829 paid_time: None,
830 payment_method,
831 }
832 }
833
834 #[inline]
836 pub fn amount(&self) -> Amount<CurrencyUnit> {
837 self.amount.clone()
838 }
839
840 #[inline]
842 pub fn fee_reserve(&self) -> Amount<CurrencyUnit> {
843 self.fee_reserve.clone()
844 }
845
846 pub fn total_needed(&self) -> Result<Amount, crate::Error> {
848 let total = self
849 .amount
850 .checked_add(&self.fee_reserve)
851 .map_err(|_| crate::Error::AmountOverflow)?;
852 Ok(Amount::from(total.value()))
853 }
854
855 #[allow(clippy::too_many_arguments)]
857 pub fn from_db(
858 id: QuoteId,
859 unit: CurrencyUnit,
860 request: MeltPaymentRequest,
861 amount: u64,
862 fee_reserve: u64,
863 state: MeltQuoteState,
864 expiry: u64,
865 payment_preimage: Option<String>,
866 request_lookup_id: Option<PaymentIdentifier>,
867 options: Option<MeltOptions>,
868 created_time: u64,
869 paid_time: Option<u64>,
870 payment_method: PaymentMethod,
871 ) -> Self {
872 Self {
873 id,
874 unit: unit.clone(),
875 request,
876 amount: Amount::new(amount, unit.clone()),
877 fee_reserve: Amount::new(fee_reserve, unit),
878 state,
879 expiry,
880 payment_preimage,
881 request_lookup_id,
882 options,
883 created_time,
884 paid_time,
885 payment_method,
886 }
887 }
888}
889
890#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
892pub struct MintKeySetInfo {
893 pub id: Id,
895 pub unit: CurrencyUnit,
897 pub active: bool,
900 pub valid_from: u64,
902 pub derivation_path: DerivationPath,
904 pub derivation_path_index: Option<u32>,
906 pub amounts: Vec<u64>,
908 #[serde(default = "default_fee")]
910 pub input_fee_ppk: u64,
911 pub final_expiry: Option<u64>,
913 pub issuer_version: Option<IssuerVersion>,
915}
916
917pub fn default_fee() -> u64 {
919 0
920}
921
922impl From<MintKeySetInfo> for KeySetInfo {
923 fn from(keyset_info: MintKeySetInfo) -> Self {
924 Self {
925 id: keyset_info.id,
926 unit: keyset_info.unit,
927 active: keyset_info.active,
928 input_fee_ppk: keyset_info.input_fee_ppk,
929 final_expiry: keyset_info.final_expiry,
930 }
931 }
932}
933
934impl From<MintQuote> for MintQuoteBolt11Response<QuoteId> {
935 fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<QuoteId> {
936 MintQuoteBolt11Response {
937 quote: mint_quote.id.clone(),
938 state: mint_quote.state(),
939 request: mint_quote.request,
940 expiry: Some(mint_quote.expiry),
941 pubkey: mint_quote.pubkey,
942 amount: mint_quote.amount.map(Into::into),
943 unit: Some(mint_quote.unit.clone()),
944 }
945 }
946}
947
948impl From<MintQuote> for MintQuoteBolt11Response<String> {
949 fn from(quote: MintQuote) -> Self {
950 let quote: MintQuoteBolt11Response<QuoteId> = quote.into();
951
952 quote.into()
953 }
954}
955
956impl TryFrom<crate::mint::MintQuote> for MintQuoteBolt12Response<QuoteId> {
957 type Error = crate::Error;
958
959 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
960 Ok(MintQuoteBolt12Response {
961 quote: mint_quote.id.clone(),
962 request: mint_quote.request,
963 expiry: Some(mint_quote.expiry),
964 amount_paid: Amount::from(mint_quote.amount_paid.value()),
965 amount_issued: Amount::from(mint_quote.amount_issued.value()),
966 pubkey: mint_quote.pubkey.ok_or(crate::Error::PubkeyRequired)?,
967 amount: mint_quote.amount.map(Into::into),
968 unit: mint_quote.unit,
969 })
970 }
971}
972
973impl TryFrom<MintQuote> for MintQuoteBolt12Response<String> {
974 type Error = crate::Error;
975
976 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
977 let quote: MintQuoteBolt12Response<QuoteId> = quote.try_into()?;
978
979 Ok(quote.into())
980 }
981}
982
983impl TryFrom<crate::mint::MintQuote> for crate::nuts::MintQuoteCustomResponse<QuoteId> {
984 type Error = crate::Error;
985
986 fn try_from(mint_quote: crate::mint::MintQuote) -> Result<Self, Self::Error> {
987 Ok(crate::nuts::MintQuoteCustomResponse {
988 state: mint_quote.state(),
989 quote: mint_quote.id.clone(),
990 request: mint_quote.request,
991 expiry: Some(mint_quote.expiry),
992 pubkey: mint_quote.pubkey,
993 amount: mint_quote.amount.map(Into::into),
994 unit: Some(mint_quote.unit),
995 extra: mint_quote.extra_json.unwrap_or_default(),
996 })
997 }
998}
999
1000impl TryFrom<MintQuote> for crate::nuts::MintQuoteCustomResponse<String> {
1001 type Error = crate::Error;
1002
1003 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
1004 let quote: crate::nuts::MintQuoteCustomResponse<QuoteId> = quote.try_into()?;
1005
1006 Ok(quote.into())
1007 }
1008}
1009
1010impl TryFrom<MintQuoteResponse<QuoteId>> for MintQuoteBolt11Response<QuoteId> {
1011 type Error = Error;
1012
1013 fn try_from(response: MintQuoteResponse<QuoteId>) -> Result<Self, Self::Error> {
1014 match response {
1015 MintQuoteResponse::Bolt11(bolt11_response) => Ok(bolt11_response),
1016 _ => Err(Error::InvalidPaymentMethod),
1017 }
1018 }
1019}
1020
1021impl TryFrom<MintQuoteResponse<QuoteId>> for MintQuoteBolt12Response<QuoteId> {
1022 type Error = Error;
1023
1024 fn try_from(response: MintQuoteResponse<QuoteId>) -> Result<Self, Self::Error> {
1025 match response {
1026 MintQuoteResponse::Bolt12(bolt12_response) => Ok(bolt12_response),
1027 _ => Err(Error::InvalidPaymentMethod),
1028 }
1029 }
1030}
1031
1032impl TryFrom<MintQuote> for MintQuoteResponse<QuoteId> {
1033 type Error = Error;
1034
1035 fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
1036 if quote.payment_method.is_bolt11() {
1037 let bolt11_response: MintQuoteBolt11Response<QuoteId> = quote.into();
1038 Ok(MintQuoteResponse::Bolt11(bolt11_response))
1039 } else if quote.payment_method.is_bolt12() {
1040 let bolt12_response = MintQuoteBolt12Response::try_from(quote)?;
1041 Ok(MintQuoteResponse::Bolt12(bolt12_response))
1042 } else {
1043 let method = quote.payment_method.clone();
1044 let custom_response = crate::nuts::MintQuoteCustomResponse::try_from(quote)?;
1045 Ok(MintQuoteResponse::Custom((method, custom_response)))
1046 }
1047 }
1048}
1049
1050impl From<MintQuoteResponse<QuoteId>> for MintQuoteBolt11Response<String> {
1051 fn from(response: MintQuoteResponse<QuoteId>) -> Self {
1052 match response {
1053 MintQuoteResponse::Bolt11(bolt11_response) => MintQuoteBolt11Response {
1054 quote: bolt11_response.quote.to_string(),
1055 state: bolt11_response.state,
1056 request: bolt11_response.request,
1057 expiry: bolt11_response.expiry,
1058 pubkey: bolt11_response.pubkey,
1059 amount: bolt11_response.amount,
1060 unit: bolt11_response.unit,
1061 },
1062 _ => panic!("Expected Bolt11 response"),
1063 }
1064 }
1065}
1066
1067impl From<&MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
1068 fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
1069 MeltQuoteBolt11Response {
1070 quote: melt_quote.id.clone(),
1071 payment_preimage: None,
1072 change: None,
1073 state: melt_quote.state,
1074 expiry: melt_quote.expiry,
1075 amount: melt_quote.amount().clone().into(),
1076 fee_reserve: melt_quote.fee_reserve().clone().into(),
1077 request: None,
1078 unit: Some(melt_quote.unit.clone()),
1079 }
1080 }
1081}
1082
1083impl From<MeltQuote> for MeltQuoteBolt11Response<QuoteId> {
1084 fn from(melt_quote: MeltQuote) -> MeltQuoteBolt11Response<QuoteId> {
1085 MeltQuoteBolt11Response {
1086 quote: melt_quote.id.clone(),
1087 amount: melt_quote.amount().clone().into(),
1088 fee_reserve: melt_quote.fee_reserve().clone().into(),
1089 state: melt_quote.state,
1090 expiry: melt_quote.expiry,
1091 payment_preimage: melt_quote.payment_preimage,
1092 change: None,
1093 request: Some(melt_quote.request.to_string()),
1094 unit: Some(melt_quote.unit.clone()),
1095 }
1096 }
1097}
1098
1099#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
1101pub enum MeltPaymentRequest {
1102 Bolt11 {
1104 bolt11: Bolt11Invoice,
1106 },
1107 Bolt12 {
1109 #[serde(with = "offer_serde")]
1111 offer: Box<Offer>,
1112 },
1113 Custom {
1115 method: String,
1117 request: String,
1119 },
1120}
1121
1122impl std::fmt::Display for MeltPaymentRequest {
1123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1124 match self {
1125 MeltPaymentRequest::Bolt11 { bolt11 } => write!(f, "{bolt11}"),
1126 MeltPaymentRequest::Bolt12 { offer } => write!(f, "{offer}"),
1127 MeltPaymentRequest::Custom { request, .. } => write!(f, "{request}"),
1128 }
1129 }
1130}
1131
1132mod offer_serde {
1133 use std::str::FromStr;
1134
1135 use serde::{self, Deserialize, Deserializer, Serializer};
1136
1137 use super::Offer;
1138
1139 pub fn serialize<S>(offer: &Offer, serializer: S) -> Result<S::Ok, S::Error>
1140 where
1141 S: Serializer,
1142 {
1143 let s = offer.to_string();
1144 serializer.serialize_str(&s)
1145 }
1146
1147 pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<Offer>, D::Error>
1148 where
1149 D: Deserializer<'de>,
1150 {
1151 let s = String::deserialize(deserializer)?;
1152 Ok(Box::new(Offer::from_str(&s).map_err(|_| {
1153 serde::de::Error::custom("Invalid Bolt12 Offer")
1154 })?))
1155 }
1156}