1use std::array::TryFromSliceError;
4use std::fmt;
5
6#[cfg(feature = "mint")]
7use cashu::quote_id::QuoteId;
8use cashu::{CurrencyUnit, PaymentMethod};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::Value;
11use thiserror::Error;
12
13use crate::nuts::Id;
14#[cfg(feature = "mint")]
15use crate::payment::PaymentIdentifier;
16use crate::util::hex;
17#[cfg(feature = "wallet")]
18use crate::wallet::WalletKey;
19use crate::Amount;
20
21#[derive(Debug, Error)]
23pub enum Error {
24 #[error("No Key for Amount")]
26 AmountKey,
27 #[error("Keyset id not known: `{0}`")]
29 KeysetUnknown(Id),
30 #[error("Unit unsupported")]
32 UnsupportedUnit,
33 #[error("Payment failed")]
35 PaymentFailed,
36 #[error("Payment pending")]
38 PaymentPending,
39 #[error("Request already paid")]
41 RequestAlreadyPaid,
42 #[error("Invalid payment request")]
44 InvalidPaymentRequest,
45 #[error("Invoice Amount undefined")]
47 InvoiceAmountUndefined,
48 #[error("Split Values must be less then or equal to amount")]
50 SplitValuesGreater,
51 #[error("Amount Overflow")]
53 AmountOverflow,
54 #[error("Cannot issue more than amount paid")]
56 OverIssue,
57 #[error("Signature missing or invalid")]
59 SignatureMissingOrInvalid,
60 #[error("Amount Less Invoice is not allowed")]
62 AmountLessNotAllowed,
63 #[error("Multi-Part Internal Melt Quotes are not supported")]
65 InternalMultiPartMeltQuote,
66 #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
68 MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
69 #[error("Clear Auth Required")]
71 ClearAuthRequired,
72 #[error("Blind Auth Required")]
74 BlindAuthRequired,
75 #[error("Clear Auth Failed")]
77 ClearAuthFailed,
78 #[error("Blind Auth Failed")]
80 BlindAuthFailed,
81 #[error("Auth settings undefined")]
83 AuthSettingsUndefined,
84 #[error("Mint time outside of tolerance")]
86 MintTimeExceedsTolerance,
87 #[error("Insufficient blind auth tokens, must reauth")]
89 InsufficientBlindAuthTokens,
90 #[error("Auth localstore undefined")]
92 AuthLocalstoreUndefined,
93 #[error("Wallet cat not set")]
95 CatNotSet,
96 #[error("Could not get mint info")]
98 CouldNotGetMintInfo,
99 #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
101 AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
102 #[error("Payment id seen for mint")]
104 DuplicatePaymentId,
105 #[error("Pubkey required")]
107 PubkeyRequired,
108 #[error("Missing pubkey")]
110 MissingPubkey,
111 #[error("Invalid payment method")]
113 InvalidPaymentMethod,
114 #[error("Amount undefined")]
116 AmountUndefined,
117 #[error("Payment method unsupported")]
119 UnsupportedPaymentMethod,
120 #[error("Payment method required")]
122 PaymentMethodRequired,
123 #[error("Could not parse bolt12")]
125 Bolt12parse,
126 #[error("Could not parse invoice")]
128 InvalidInvoice,
129
130 #[error("Failed to parse BIP353 address: {0}")]
132 Bip353Parse(String),
133
134 #[error("Operation timeout")]
136 Timeout,
137 #[cfg(feature = "mint")]
147 #[error(
148 "Onchain backend returned request_lookup_id {got:?} that does not match \
149 mint-supplied quote_id {expected}"
150 )]
151 OnchainQuoteLookupIdMismatch {
152 expected: QuoteId,
154 got: Option<PaymentIdentifier>,
156 },
157
158 #[cfg(feature = "mint")]
166 #[error("Onchain melt quote must contain at least one fee_options entry")]
167 OnchainFeeOptionsEmpty,
168
169 #[cfg(feature = "mint")]
174 #[error("Duplicate fee_index {index} in onchain fee_options")]
175 OnchainFeeOptionsDuplicateIndex {
176 index: u32,
178 },
179
180 #[cfg(feature = "mint")]
183 #[error("Onchain melt request fee_index {index} not found in quote fee_options")]
184 OnchainFeeIndexNotFound {
185 index: u32,
187 },
188
189 #[error("Failed to resolve BIP353 address: {0}")]
191 Bip353Resolve(String),
192 #[error("No BOLT12 offer found in BIP353 payment instructions")]
194 Bip353NoBolt12Offer,
195
196 #[error("Failed to parse BIP321 payment instruction: {0}")]
198 Bip321Parse(String),
199 #[error("Failed to encode BIP321 payment request: {0}")]
201 Bip321Encode(String),
202
203 #[error("Failed to parse Lightning address: {0}")]
205 LightningAddressParse(String),
206 #[error("Failed to request invoice from Lightning address service: {0}")]
208 LightningAddressRequest(String),
209
210 #[error("Internal send error: {0}")]
212 SendError(String),
213
214 #[error("Internal receive error: {0}")]
216 RecvError(String),
217
218 #[error("Minting is disabled")]
221 MintingDisabled,
222 #[error("Unknown quote")]
224 UnknownQuote,
225 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
227 ExpiredQuote(u64, u64),
228 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
230 AmountOutofLimitRange(Amount, Amount, Amount),
231 #[error("Quote not paid")]
233 UnpaidQuote,
234 #[error("Quote pending")]
236 PendingQuote,
237 #[error("Timed out waiting for pending melt to complete{}", .last_backend_error.as_ref().map(|e| format!(": last backend error: {}", e)).unwrap_or_default())]
243 PendingMeltTimeout {
244 last_backend_error: Option<String>,
246 },
247 #[error("Quote already issued")]
249 IssuedQuote,
250 #[error("Quote is already paid")]
252 PaidQuote,
253 #[error("Payment state is unknown")]
255 UnknownPaymentState,
256 #[error("Melting is disabled")]
258 MeltingDisabled,
259 #[error("Unknown Keyset")]
261 UnknownKeySet,
262 #[error("Blinded Message is already signed")]
264 BlindedMessageAlreadySigned,
265 #[error("Inactive Keyset")]
267 InactiveKeyset,
268 #[error("Keyset has expired")]
270 ExpiredKeyset,
271 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
273 TransactionUnbalanced(u64, u64, u64),
274 #[error("Duplicate Inputs")]
276 DuplicateInputs,
277 #[error("Duplicate outputs")]
279 DuplicateOutputs,
280 #[error("Maximum inputs exceeded: {actual} provided, max {max}")]
282 MaxInputsExceeded {
283 actual: usize,
285 max: usize,
287 },
288 #[error("Maximum outputs exceeded: {actual} provided, max {max}")]
290 MaxOutputsExceeded {
291 actual: usize,
293 max: usize,
295 },
296 #[error("Duplicate quote IDs")]
298 DuplicateQuoteIds,
299 #[error("Maximum batch size exceeded: {actual} provided, max {max}")]
301 BatchSizeExceeded {
302 actual: usize,
304 max: usize,
306 },
307 #[error("Proof content too large: {actual} bytes, max {max}")]
309 ProofContentTooLarge {
310 actual: usize,
312 max: usize,
314 },
315 #[error("Request field '{field}' too large: {actual} bytes, max {max}")]
317 RequestFieldTooLarge {
318 field: String,
320 actual: usize,
322 max: usize,
324 },
325 #[error("Cannot have multiple units")]
327 MultipleUnits,
328 #[error("Input unit must match output")]
330 UnitMismatch,
331 #[error("Sig all cannot be used in melt")]
333 SigAllUsedInMelt,
334 #[error("Token Already Spent")]
336 TokenAlreadySpent,
337 #[error("Token Pending")]
339 TokenPending,
340 #[error("Internal Error")]
342 Internal,
343 #[error("Oidc client not set")]
345 OidcNotSet,
346 #[error("Unit string picked collided: `{0}`")]
348 UnitStringCollision(CurrencyUnit),
349 #[error("P2PK condition not met `{0}`")]
352 P2PKConditionsNotMet(String),
353 #[error("Duplicate signature from same pubkey in P2PK")]
355 DuplicateSignatureError,
356 #[error("Spending condition locktime not provided")]
358 LocktimeNotProvided,
359 #[error("Invalid spending conditions: `{0}`")]
361 InvalidSpendConditions(String),
362 #[error("Incorrect wallet: `{0}`")]
364 IncorrectWallet(String),
365 #[error("Unknown wallet: `{0}`")]
367 #[cfg(feature = "wallet")]
368 UnknownWallet(WalletKey),
369 #[error("Max fee exceeded")]
371 MaxFeeExceeded,
372 #[error("Invalid NUT-13 restore options: `{field}` {reason}")]
374 InvalidNut13Options {
375 field: &'static str,
377 reason: &'static str,
379 },
380 #[error("Url path segments could not be joined")]
382 UrlPathSegments,
383 #[error("Unknown error response: `{0}`")]
385 UnknownErrorResponse(String),
386 #[error("Could not verify DLEQ proof")]
388 CouldNotVerifyDleq,
389 #[error("Dleq proof not provided for signature")]
391 DleqProofNotProvided,
392 #[error("Token does not match wallet mint")]
395 IncorrectMint,
396 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
398 MultiMintTokenNotSupported,
399 #[error("Preimage not provided")]
401 PreimageNotProvided,
402
403 #[error("Unknown mint: {mint_url}")]
405 UnknownMint {
406 mint_url: String,
408 },
409 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
411 TransferTimeout {
412 source_mint: String,
414 target_mint: String,
416 amount: Amount,
418 },
419 #[error("Insufficient funds")]
421 InsufficientFunds,
422 #[error("Unexpected proof state")]
424 UnexpectedProofState,
425 #[error("No active keyset")]
427 NoActiveKeyset,
428 #[error("Incorrect quote amount")]
430 IncorrectQuoteAmount,
431 #[error("Invoice Description not supported")]
433 InvoiceDescriptionUnsupported,
434 #[error("Invalid transaction direction")]
436 InvalidTransactionDirection,
437 #[error("Invalid transaction id")]
439 InvalidTransactionId,
440 #[error("Transaction not found")]
442 TransactionNotFound,
443 #[error("Invalid operation kind")]
445 InvalidOperationKind,
446 #[error("Invalid operation state")]
448 InvalidOperationState,
449 #[error("Operation not found")]
451 OperationNotFound,
452 #[error("Invalid KV store key or namespace: {0}")]
454 KVStoreInvalidKey(String),
455 #[error("Concurrent update detected")]
457 ConcurrentUpdate,
458 #[error("Invalid mint response: {0}")]
460 InvalidMintResponse(String),
461 #[error("Subscription error: {0}")]
463 SubscriptionError(String),
464 #[error("`{0}`")]
466 Custom(String),
467
468 #[error(transparent)]
471 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
472 #[error(transparent)]
474 Bip32(#[from] bitcoin::bip32::Error),
475 #[error(transparent)]
477 ParseInt(#[from] std::num::ParseIntError),
478 #[error(transparent)]
480 UrlParseError(#[from] url::ParseError),
481 #[error(transparent)]
483 Utf8ParseError(#[from] std::string::FromUtf8Error),
484 #[error(transparent)]
486 SerdeJsonError(#[from] serde_json::Error),
487 #[error(transparent)]
489 Base64Error(#[from] bitcoin::base64::DecodeError),
490 #[error(transparent)]
492 HexError(#[from] hex::Error),
493 #[error("Http transport error {0:?}: {1}")]
495 HttpError(Option<u16>, String),
496 #[cfg(feature = "mint")]
498 #[error(transparent)]
499 Uuid(#[from] uuid::Error),
500 #[error(transparent)]
503 CashuUrl(#[from] crate::mint_url::Error),
504 #[error(transparent)]
506 Secret(#[from] crate::secret::Error),
507 #[error(transparent)]
509 AmountError(#[from] crate::amount::Error),
510 #[error(transparent)]
512 DHKE(#[from] crate::dhke::Error),
513 #[error(transparent)]
515 NUT00(#[from] crate::nuts::nut00::Error),
516 #[error(transparent)]
518 NUT01(#[from] crate::nuts::nut01::Error),
519 #[error(transparent)]
521 NUT02(#[from] crate::nuts::nut02::Error),
522 #[error(transparent)]
524 NUT03(#[from] crate::nuts::nut03::Error),
525 #[error(transparent)]
527 NUT04(#[from] crate::nuts::nut04::Error),
528 #[error(transparent)]
530 NUT05(#[from] crate::nuts::nut05::Error),
531 #[error(transparent)]
533 NUT10(crate::nuts::nut10::Error),
534 #[error(transparent)]
536 NUT11(#[from] crate::nuts::nut11::Error),
537 #[error(transparent)]
539 NUT12(#[from] crate::nuts::nut12::Error),
540 #[error(transparent)]
542 #[cfg(feature = "wallet")]
543 NUT13(#[from] crate::nuts::nut13::Error),
544 #[error(transparent)]
546 NUT14(#[from] crate::nuts::nut14::Error),
547 #[error(transparent)]
549 NUT18(#[from] crate::nuts::nut18::Error),
550 #[error(transparent)]
552 NUT20(#[from] crate::nuts::nut20::Error),
553 #[error(transparent)]
555 NUT21(#[from] crate::nuts::nut21::Error),
556 #[error(transparent)]
558 NUT22(#[from] crate::nuts::nut22::Error),
559 #[error(transparent)]
561 NUT23(#[from] crate::nuts::nut23::Error),
562 #[error(transparent)]
564 #[cfg(feature = "mint")]
565 QuoteId(#[from] crate::quote_id::QuoteIdError),
566 #[error(transparent)]
568 TryFromSliceError(#[from] TryFromSliceError),
569 #[error(transparent)]
571 Database(crate::database::Error),
572 #[error(transparent)]
574 #[cfg(feature = "mint")]
575 Payment(#[from] crate::payment::Error),
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 #[test]
583 fn test_is_definitive_failure() {
584 assert!(Error::AmountOverflow.is_definitive_failure());
586 assert!(Error::TokenAlreadySpent.is_definitive_failure());
587 assert!(Error::MintingDisabled.is_definitive_failure());
588 assert!(Error::MaxInputsExceeded { actual: 2, max: 1 }.is_definitive_failure());
589 assert!(Error::MaxOutputsExceeded { actual: 2, max: 1 }.is_definitive_failure());
590
591 assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
593 assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
594 assert!(
595 Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
596 );
597
598 assert!(!Error::Timeout.is_definitive_failure());
600 assert!(!Error::Internal.is_definitive_failure());
601 assert!(!Error::ConcurrentUpdate.is_definitive_failure());
602
603 assert!(
605 !Error::HttpError(Some(500), "Internal Server Error".to_string())
606 .is_definitive_failure()
607 );
608 assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
609 assert!(
610 !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
611 );
612
613 assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
615 }
616
617 #[test]
618 fn test_pending_states_are_ambiguous_failures() {
619 assert!(!Error::TokenPending.is_definitive_failure());
623 assert!(!Error::PendingQuote.is_definitive_failure());
624 }
625
626 #[test]
627 fn test_max_outputs_and_inputs_error_responses_decode() {
628 let max_inputs = Error::from(ErrorResponse {
629 code: ErrorCode::MaxInputsExceeded,
630 detail: "Maximum inputs exceeded: 2 provided, max 1".to_string(),
631 });
632 assert!(matches!(
633 max_inputs,
634 Error::MaxInputsExceeded { actual: 2, max: 1 }
635 ));
636 assert!(max_inputs.is_definitive_failure());
637
638 let max_outputs = Error::from(ErrorResponse {
639 code: ErrorCode::MaxOutputsExceeded,
640 detail: "Maximum outputs exceeded: 2 provided, max 1".to_string(),
641 });
642 assert!(matches!(
643 max_outputs,
644 Error::MaxOutputsExceeded { actual: 2, max: 1 }
645 ));
646 assert!(max_outputs.is_definitive_failure());
647 }
648}
649
650impl Error {
651 pub fn is_definitive_failure(&self) -> bool {
660 match self {
661 Self::AmountKey
663 | Self::KeysetUnknown(_)
664 | Self::UnsupportedUnit
665 | Self::InvoiceAmountUndefined
666 | Self::SplitValuesGreater
667 | Self::AmountOverflow
668 | Self::OverIssue
669 | Self::SignatureMissingOrInvalid
670 | Self::AmountLessNotAllowed
671 | Self::InternalMultiPartMeltQuote
672 | Self::MppUnitMethodNotSupported(_, _)
673 | Self::AmountlessInvoiceNotSupported(_, _)
674 | Self::DuplicatePaymentId
675 | Self::PubkeyRequired
676 | Self::InvalidPaymentMethod
677 | Self::UnsupportedPaymentMethod
678 | Self::InvalidInvoice
679 | Self::MintingDisabled
680 | Self::UnknownQuote
681 | Self::ExpiredQuote(_, _)
682 | Self::AmountOutofLimitRange(_, _, _)
683 | Self::UnpaidQuote
684 | Self::IssuedQuote
685 | Self::PaidQuote
686 | Self::MeltingDisabled
687 | Self::UnknownKeySet
688 | Self::BlindedMessageAlreadySigned
689 | Self::InactiveKeyset
690 | Self::ExpiredKeyset
691 | Self::TransactionUnbalanced(_, _, _)
692 | Self::DuplicateInputs
693 | Self::DuplicateOutputs
694 | Self::MaxInputsExceeded { .. }
695 | Self::MaxOutputsExceeded { .. }
696 | Self::DuplicateQuoteIds
697 | Self::BatchSizeExceeded { .. }
698 | Self::MultipleUnits
699 | Self::UnitMismatch
700 | Self::SigAllUsedInMelt
701 | Self::TokenAlreadySpent
702 | Self::P2PKConditionsNotMet(_)
703 | Self::DuplicateSignatureError
704 | Self::LocktimeNotProvided
705 | Self::InvalidSpendConditions(_)
706 | Self::IncorrectWallet(_)
707 | Self::MaxFeeExceeded
708 | Self::InvalidNut13Options { .. }
709 | Self::DleqProofNotProvided
710 | Self::IncorrectMint
711 | Self::MultiMintTokenNotSupported
712 | Self::PreimageNotProvided
713 | Self::UnknownMint { .. }
714 | Self::UnexpectedProofState
715 | Self::NoActiveKeyset
716 | Self::IncorrectQuoteAmount
717 | Self::InvoiceDescriptionUnsupported
718 | Self::InvalidTransactionDirection
719 | Self::InvalidTransactionId
720 | Self::InvalidOperationKind
721 | Self::InvalidOperationState
722 | Self::OperationNotFound
723 | Self::KVStoreInvalidKey(_)
724 | Self::Bip353Parse(_)
725 | Self::Bip353NoBolt12Offer
726 | Self::Bip321Parse(_)
727 | Self::Bip321Encode(_)
728 | Self::LightningAddressParse(_) => true,
729
730 #[cfg(feature = "mint")]
731 Self::OnchainQuoteLookupIdMismatch { .. }
732 | Self::OnchainFeeOptionsEmpty
733 | Self::OnchainFeeOptionsDuplicateIndex { .. }
734 | Self::OnchainFeeIndexNotFound { .. } => true,
735
736 Self::HttpError(Some(status), _) => {
738 (400..500).contains(status)
741 }
742
743 Self::Timeout
745 | Self::Internal
746 | Self::UnknownPaymentState
747 | Self::PendingQuote
748 | Self::TokenPending
749 | Self::CouldNotGetMintInfo
750 | Self::UnknownErrorResponse(_)
751 | Self::InvalidMintResponse(_)
752 | Self::ConcurrentUpdate
753 | Self::SendError(_)
754 | Self::RecvError(_)
755 | Self::TransferTimeout { .. }
756 | Self::Bip353Resolve(_)
757 | Self::LightningAddressRequest(_) => false,
758
759 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
763 | Self::Custom(_) => false,
764
765 Self::ClearAuthRequired
767 | Self::BlindAuthRequired
768 | Self::ClearAuthFailed
769 | Self::BlindAuthFailed
770 | Self::InsufficientBlindAuthTokens
771 | Self::AuthSettingsUndefined
772 | Self::AuthLocalstoreUndefined
773 | Self::OidcNotSet => true,
774
775 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
779 Self::UrlParseError(_) => true,
780 Self::Utf8ParseError(_) => true,
781 Self::Base64Error(_) => true,
782 Self::HexError(_) => true,
783 #[cfg(feature = "mint")]
784 Self::Uuid(_) => true,
785 Self::CashuUrl(_) => true,
786 Self::Secret(_) => true,
787 Self::AmountError(_) => true,
788 Self::DHKE(_) => true, Self::NUT00(_) => true,
790 Self::NUT01(_) => true,
791 Self::NUT02(_) => true,
792 Self::NUT03(_) => true,
793 Self::NUT04(_) => true,
794 Self::NUT05(_) => true,
795 Self::NUT11(_) => true,
796 Self::NUT12(_) => true,
797 #[cfg(feature = "wallet")]
798 Self::NUT13(_) => true,
799 Self::NUT14(_) => true,
800 Self::NUT18(_) => true,
801 Self::NUT20(_) => true,
802 Self::NUT21(_) => true,
803 Self::NUT22(_) => true,
804 Self::NUT23(_) => true,
805 #[cfg(feature = "mint")]
806 Self::QuoteId(_) => true,
807 Self::TryFromSliceError(_) => true,
808 #[cfg(feature = "mint")]
809 Self::Payment(_) => false, _ => false,
813 }
814 }
815}
816
817impl From<crate::nuts::nut10::Error> for Error {
818 fn from(err: crate::nuts::nut10::Error) -> Self {
819 match err {
820 crate::nuts::nut10::Error::NUT11(nut11_err) => Self::NUT11(nut11_err),
821 crate::nuts::nut10::Error::NUT14(nut14_err) => Self::NUT14(nut14_err),
822 other => Self::NUT10(other),
823 }
824 }
825}
826
827#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
831pub struct ErrorResponse {
832 pub code: ErrorCode,
834 #[serde(default)]
836 pub detail: String,
837}
838
839impl fmt::Display for ErrorResponse {
840 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
841 write!(f, "code: {}, detail: {}", self.code, self.detail)
842 }
843}
844
845impl ErrorResponse {
846 pub fn new(code: ErrorCode, detail: String) -> Self {
848 Self { code, detail }
849 }
850
851 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
853 let value: Value = serde_json::from_str(json)?;
854
855 Self::from_value(value)
856 }
857
858 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
860 match serde_json::from_value::<ErrorResponse>(value.clone()) {
861 Ok(res) => Ok(res),
862 Err(_) => Ok(Self {
863 code: ErrorCode::Unknown(999),
864 detail: value.to_string(),
865 }),
866 }
867 }
868}
869
870fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
873 ErrorCode::WitnessMissingOrInvalid
875}
876
877impl From<Error> for ErrorResponse {
878 fn from(err: Error) -> ErrorResponse {
879 match err {
880 Error::TokenAlreadySpent => ErrorResponse {
881 code: ErrorCode::TokenAlreadySpent,
882 detail: err.to_string(),
883 },
884 Error::UnsupportedUnit => ErrorResponse {
885 code: ErrorCode::UnsupportedUnit,
886 detail: err.to_string(),
887 },
888 Error::PaymentFailed => ErrorResponse {
889 code: ErrorCode::LightningError,
890 detail: err.to_string(),
891 },
892 Error::RequestAlreadyPaid => ErrorResponse {
893 code: ErrorCode::InvoiceAlreadyPaid,
894 detail: "Invoice already paid.".to_string(),
895 },
896 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
897 ErrorResponse {
898 code: ErrorCode::TransactionUnbalanced,
899 detail: format!(
900 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
901 ),
902 }
903 }
904 Error::MintingDisabled => ErrorResponse {
905 code: ErrorCode::MintingDisabled,
906 detail: err.to_string(),
907 },
908 Error::BlindedMessageAlreadySigned => ErrorResponse {
909 code: ErrorCode::BlindedMessageAlreadySigned,
910 detail: err.to_string(),
911 },
912 Error::InsufficientFunds => ErrorResponse {
913 code: ErrorCode::TransactionUnbalanced,
914 detail: err.to_string(),
915 },
916 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
917 code: ErrorCode::AmountOutofLimitRange,
918 detail: err.to_string(),
919 },
920 Error::ExpiredQuote(_, _) => ErrorResponse {
921 code: ErrorCode::QuoteExpired,
922 detail: err.to_string(),
923 },
924 Error::PendingQuote => ErrorResponse {
925 code: ErrorCode::QuotePending,
926 detail: err.to_string(),
927 },
928 Error::PendingMeltTimeout { .. } => ErrorResponse {
929 code: ErrorCode::QuotePending,
930 detail: err.to_string(),
931 },
932 Error::TokenPending => ErrorResponse {
933 code: ErrorCode::TokenPending,
934 detail: err.to_string(),
935 },
936 Error::ClearAuthRequired => ErrorResponse {
937 code: ErrorCode::ClearAuthRequired,
938 detail: Error::ClearAuthRequired.to_string(),
939 },
940 Error::ClearAuthFailed => ErrorResponse {
941 code: ErrorCode::ClearAuthFailed,
942 detail: Error::ClearAuthFailed.to_string(),
943 },
944 Error::BlindAuthRequired => ErrorResponse {
945 code: ErrorCode::BlindAuthRequired,
946 detail: Error::BlindAuthRequired.to_string(),
947 },
948 Error::BlindAuthFailed => ErrorResponse {
949 code: ErrorCode::BlindAuthFailed,
950 detail: Error::BlindAuthFailed.to_string(),
951 },
952 Error::NUT20(err) => ErrorResponse {
953 code: ErrorCode::WitnessMissingOrInvalid,
954 detail: err.to_string(),
955 },
956 Error::DuplicateInputs => ErrorResponse {
957 code: ErrorCode::DuplicateInputs,
958 detail: err.to_string(),
959 },
960 Error::DuplicateOutputs => ErrorResponse {
961 code: ErrorCode::DuplicateOutputs,
962 detail: err.to_string(),
963 },
964 Error::MultipleUnits => ErrorResponse {
965 code: ErrorCode::MultipleUnits,
966 detail: err.to_string(),
967 },
968 Error::UnitMismatch => ErrorResponse {
969 code: ErrorCode::UnitMismatch,
970 detail: err.to_string(),
971 },
972 Error::UnpaidQuote => ErrorResponse {
973 code: ErrorCode::QuoteNotPaid,
974 detail: Error::UnpaidQuote.to_string(),
975 },
976 Error::NUT11(err) => {
977 let code = map_nut11_error(&err);
978 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
979 Some("P2PK signatures are required but not provided".to_string())
980 } else {
981 None
982 };
983 ErrorResponse {
984 code,
985 detail: match extra {
986 Some(extra) => format!("{err}. {extra}"),
987 None => err.to_string(),
988 },
989 }
990 },
991 Error::DuplicateSignatureError => ErrorResponse {
992 code: ErrorCode::WitnessMissingOrInvalid,
993 detail: err.to_string(),
994 },
995 Error::IssuedQuote => ErrorResponse {
996 code: ErrorCode::TokensAlreadyIssued,
997 detail: err.to_string(),
998 },
999 Error::UnknownKeySet => ErrorResponse {
1000 code: ErrorCode::KeysetNotFound,
1001 detail: err.to_string(),
1002 },
1003 Error::InactiveKeyset => ErrorResponse {
1004 code: ErrorCode::KeysetInactive,
1005 detail: err.to_string(),
1006 },
1007 Error::ExpiredKeyset => ErrorResponse {
1008 code: ErrorCode::KeysetExpired,
1009 detail: err.to_string(),
1010 },
1011 Error::AmountLessNotAllowed => ErrorResponse {
1012 code: ErrorCode::AmountlessInvoiceNotSupported,
1013 detail: err.to_string(),
1014 },
1015 Error::IncorrectQuoteAmount => ErrorResponse {
1016 code: ErrorCode::IncorrectQuoteAmount,
1017 detail: err.to_string(),
1018 },
1019 Error::PubkeyRequired => ErrorResponse {
1020 code: ErrorCode::PubkeyRequired,
1021 detail: err.to_string(),
1022 },
1023 Error::PaidQuote => ErrorResponse {
1024 code: ErrorCode::InvoiceAlreadyPaid,
1025 detail: err.to_string(),
1026 },
1027 Error::DuplicatePaymentId => ErrorResponse {
1028 code: ErrorCode::InvoiceAlreadyPaid,
1029 detail: err.to_string(),
1030 },
1031 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
1033 code: ErrorCode::InvoiceAlreadyPaid,
1034 detail: "Invoice already paid or pending".to_string(),
1035 },
1036
1037 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
1039 code: ErrorCode::TokenNotVerified,
1040 detail: err.to_string(),
1041 },
1042 Error::DHKE(_) => ErrorResponse {
1043 code: ErrorCode::Unknown(50000),
1044 detail: err.to_string(),
1045 },
1046
1047 Error::CouldNotVerifyDleq => ErrorResponse {
1049 code: ErrorCode::TokenNotVerified,
1050 detail: err.to_string(),
1051 },
1052 Error::SignatureMissingOrInvalid => ErrorResponse {
1053 code: ErrorCode::WitnessMissingOrInvalid,
1054 detail: err.to_string(),
1055 },
1056 Error::SigAllUsedInMelt => ErrorResponse {
1057 code: ErrorCode::WitnessMissingOrInvalid,
1058 detail: err.to_string(),
1059 },
1060
1061 Error::AmountKey => ErrorResponse {
1063 code: ErrorCode::KeysetNotFound,
1064 detail: err.to_string(),
1065 },
1066 Error::KeysetUnknown(_) => ErrorResponse {
1067 code: ErrorCode::KeysetNotFound,
1068 detail: err.to_string(),
1069 },
1070 Error::NoActiveKeyset => ErrorResponse {
1071 code: ErrorCode::KeysetInactive,
1072 detail: err.to_string(),
1073 },
1074
1075 Error::UnknownQuote => ErrorResponse {
1077 code: ErrorCode::Unknown(50000),
1078 detail: err.to_string(),
1079 },
1080 Error::MeltingDisabled => ErrorResponse {
1081 code: ErrorCode::MintingDisabled,
1082 detail: err.to_string(),
1083 },
1084 Error::PaymentPending => ErrorResponse {
1085 code: ErrorCode::QuotePending,
1086 detail: err.to_string(),
1087 },
1088 Error::UnknownPaymentState => ErrorResponse {
1089 code: ErrorCode::Unknown(50000),
1090 detail: err.to_string(),
1091 },
1092
1093 Error::SplitValuesGreater => ErrorResponse {
1095 code: ErrorCode::TransactionUnbalanced,
1096 detail: err.to_string(),
1097 },
1098 Error::AmountOverflow => ErrorResponse {
1099 code: ErrorCode::TransactionUnbalanced,
1100 detail: err.to_string(),
1101 },
1102 Error::OverIssue => ErrorResponse {
1103 code: ErrorCode::TransactionUnbalanced,
1104 detail: err.to_string(),
1105 },
1106
1107 Error::InvalidPaymentRequest => ErrorResponse {
1109 code: ErrorCode::Unknown(50000),
1110 detail: err.to_string(),
1111 },
1112 Error::InvoiceAmountUndefined => ErrorResponse {
1113 code: ErrorCode::AmountlessInvoiceNotSupported,
1114 detail: err.to_string(),
1115 },
1116
1117 Error::Internal => ErrorResponse {
1119 code: ErrorCode::Unknown(50000),
1120 detail: err.to_string(),
1121 },
1122 Error::Database(_) => ErrorResponse {
1123 code: ErrorCode::Unknown(50000),
1124 detail: err.to_string(),
1125 },
1126 Error::ConcurrentUpdate => ErrorResponse {
1127 code: ErrorCode::ConcurrentUpdate,
1128 detail: err.to_string(),
1129 },
1130 Error::MaxInputsExceeded { .. } => ErrorResponse {
1131 code: ErrorCode::MaxInputsExceeded,
1132 detail: err.to_string()
1133 },
1134 Error::MaxOutputsExceeded { .. } => ErrorResponse {
1135 code: ErrorCode::MaxOutputsExceeded,
1136 detail: err.to_string()
1137 },
1138 Error::DuplicateQuoteIds => ErrorResponse {
1139 code: ErrorCode::DuplicateQuoteIds,
1140 detail: err.to_string(),
1141 },
1142 Error::BatchSizeExceeded { .. } => ErrorResponse {
1143 code: ErrorCode::BatchSizeExceeded,
1144 detail: err.to_string(),
1145 },
1146 _ => ErrorResponse {
1148 code: ErrorCode::Unknown(50000),
1149 detail: err.to_string(),
1150 },
1151 }
1152 }
1153}
1154
1155#[cfg(feature = "mint")]
1156impl From<crate::database::Error> for Error {
1157 fn from(db_error: crate::database::Error) -> Self {
1158 match db_error {
1159 crate::database::Error::InvalidStateTransition(state) => match state {
1160 crate::state::Error::Pending => Self::TokenPending,
1161 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
1162 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
1163 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
1164 },
1165 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1166 db_error => Self::Database(db_error),
1167 }
1168 }
1169}
1170
1171#[cfg(not(feature = "mint"))]
1172impl From<crate::database::Error> for Error {
1173 fn from(db_error: crate::database::Error) -> Self {
1174 match db_error {
1175 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1176 db_error => Self::Database(db_error),
1177 }
1178 }
1179}
1180
1181fn parse_limit_counts(detail: &str) -> Option<(usize, usize)> {
1182 let (_, counts) = detail.rsplit_once(": ")?;
1183 let (actual, max) = counts.split_once(" provided, max ")?;
1184
1185 Some((actual.trim().parse().ok()?, max.trim().parse().ok()?))
1186}
1187
1188impl From<ErrorResponse> for Error {
1189 fn from(err: ErrorResponse) -> Error {
1190 match err.code {
1191 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1193 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1195 ErrorCode::TokenPending => Self::TokenPending,
1196 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1197 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1199 ErrorCode::AmountOutofLimitRange => {
1200 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1201 }
1202 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1203 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1204 ErrorCode::MaxInputsExceeded => {
1205 let (actual, max) = parse_limit_counts(&err.detail).unwrap_or((0, 0));
1206 Self::MaxInputsExceeded { actual, max }
1207 }
1208 ErrorCode::MaxOutputsExceeded => {
1209 let (actual, max) = parse_limit_counts(&err.detail).unwrap_or((0, 0));
1210 Self::MaxOutputsExceeded { actual, max }
1211 }
1212 ErrorCode::DuplicateQuoteIds => Self::DuplicateQuoteIds,
1213 ErrorCode::BatchSizeExceeded => Self::BatchSizeExceeded { actual: 0, max: 0 },
1214 ErrorCode::MultipleUnits => Self::MultipleUnits,
1215 ErrorCode::UnitMismatch => Self::UnitMismatch,
1216 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1217 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1218 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1219 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1221 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1222 ErrorCode::KeysetExpired => Self::ExpiredKeyset,
1223 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1225 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1226 ErrorCode::MintingDisabled => Self::MintingDisabled,
1227 ErrorCode::LightningError => Self::PaymentFailed,
1228 ErrorCode::QuotePending => Self::PendingQuote,
1229 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1230 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1231 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1232 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1233 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1235 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1236 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1238 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1239 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1240 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1241 _ => Self::UnknownErrorResponse(err.to_string()),
1242 }
1243 }
1244}
1245
1246#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1248pub enum ErrorCode {
1249 TokenNotVerified,
1252
1253 TokenAlreadySpent,
1256 TokenPending,
1258 BlindedMessageAlreadySigned,
1260 OutputsPending,
1262 TransactionUnbalanced,
1264 AmountOutofLimitRange,
1266 DuplicateInputs,
1268 DuplicateOutputs,
1270 MultipleUnits,
1272 UnitMismatch,
1274 AmountlessInvoiceNotSupported,
1276 IncorrectQuoteAmount,
1278 UnsupportedUnit,
1280 MaxInputsExceeded,
1282 MaxOutputsExceeded,
1284 DuplicateQuoteIds,
1286 BatchSizeExceeded,
1288 KeysetNotFound,
1291 KeysetInactive,
1293 KeysetExpired,
1295
1296 QuoteNotPaid,
1299 TokensAlreadyIssued,
1301 MintingDisabled,
1303 LightningError,
1305 QuotePending,
1307 InvoiceAlreadyPaid,
1309 QuoteExpired,
1311 WitnessMissingOrInvalid,
1313 PubkeyRequired,
1315
1316 ClearAuthRequired,
1319 ClearAuthFailed,
1321
1322 BlindAuthRequired,
1325 BlindAuthFailed,
1327 BatMintMaxExceeded,
1329 BatRateLimitExceeded,
1331
1332 ConcurrentUpdate,
1334
1335 Unknown(u16),
1337}
1338
1339impl ErrorCode {
1340 pub fn from_code(code: u16) -> Self {
1342 match code {
1343 10001 => Self::TokenNotVerified,
1345 11001 => Self::TokenAlreadySpent,
1347 11002 => Self::TokenPending,
1348 11003 => Self::BlindedMessageAlreadySigned,
1349 11004 => Self::OutputsPending,
1350 11005 => Self::TransactionUnbalanced,
1351 11006 => Self::AmountOutofLimitRange,
1352 11007 => Self::DuplicateInputs,
1353 11008 => Self::DuplicateOutputs,
1354 11009 => Self::MultipleUnits,
1355 11010 => Self::UnitMismatch,
1356 11011 => Self::AmountlessInvoiceNotSupported,
1357 11012 => Self::IncorrectQuoteAmount,
1358 11013 => Self::UnsupportedUnit,
1359 11014 => Self::MaxInputsExceeded,
1360 11015 => Self::MaxOutputsExceeded,
1361 11016 => Self::DuplicateQuoteIds,
1362 11017 => Self::BatchSizeExceeded,
1363 12001 => Self::KeysetNotFound,
1365 12002 => Self::KeysetInactive,
1366 12003 => Self::KeysetExpired,
1367 20001 => Self::QuoteNotPaid,
1369 20002 => Self::TokensAlreadyIssued,
1370 20003 => Self::MintingDisabled,
1371 20004 => Self::LightningError,
1372 20005 => Self::QuotePending,
1373 20006 => Self::InvoiceAlreadyPaid,
1374 20007 => Self::QuoteExpired,
1375 20008 => Self::WitnessMissingOrInvalid,
1376 20009 => Self::PubkeyRequired,
1377 30001 => Self::ClearAuthRequired,
1379 30002 => Self::ClearAuthFailed,
1380 31001 => Self::BlindAuthRequired,
1382 31002 => Self::BlindAuthFailed,
1383 31003 => Self::BatMintMaxExceeded,
1384 31004 => Self::BatRateLimitExceeded,
1385 _ => Self::Unknown(code),
1386 }
1387 }
1388
1389 pub fn to_code(&self) -> u16 {
1391 match self {
1392 Self::TokenNotVerified => 10001,
1394 Self::TokenAlreadySpent => 11001,
1396 Self::TokenPending => 11002,
1397 Self::BlindedMessageAlreadySigned => 11003,
1398 Self::OutputsPending => 11004,
1399 Self::TransactionUnbalanced => 11005,
1400 Self::AmountOutofLimitRange => 11006,
1401 Self::DuplicateInputs => 11007,
1402 Self::DuplicateOutputs => 11008,
1403 Self::MultipleUnits => 11009,
1404 Self::UnitMismatch => 11010,
1405 Self::AmountlessInvoiceNotSupported => 11011,
1406 Self::IncorrectQuoteAmount => 11012,
1407 Self::UnsupportedUnit => 11013,
1408 Self::MaxInputsExceeded => 11014,
1409 Self::MaxOutputsExceeded => 11015,
1410 Self::DuplicateQuoteIds => 11016,
1411 Self::BatchSizeExceeded => 11017,
1412 Self::KeysetNotFound => 12001,
1414 Self::KeysetInactive => 12002,
1415 Self::KeysetExpired => 12003,
1416 Self::QuoteNotPaid => 20001,
1418 Self::TokensAlreadyIssued => 20002,
1419 Self::MintingDisabled => 20003,
1420 Self::LightningError => 20004,
1421 Self::QuotePending => 20005,
1422 Self::InvoiceAlreadyPaid => 20006,
1423 Self::QuoteExpired => 20007,
1424 Self::WitnessMissingOrInvalid => 20008,
1425 Self::PubkeyRequired => 20009,
1426 Self::ClearAuthRequired => 30001,
1428 Self::ClearAuthFailed => 30002,
1429 Self::BlindAuthRequired => 31001,
1431 Self::BlindAuthFailed => 31002,
1432 Self::BatMintMaxExceeded => 31003,
1433 Self::BatRateLimitExceeded => 31004,
1434 Self::ConcurrentUpdate => 50000,
1435 Self::Unknown(code) => *code,
1436 }
1437 }
1438}
1439
1440impl Serialize for ErrorCode {
1441 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1442 where
1443 S: Serializer,
1444 {
1445 serializer.serialize_u16(self.to_code())
1446 }
1447}
1448
1449impl<'de> Deserialize<'de> for ErrorCode {
1450 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1451 where
1452 D: Deserializer<'de>,
1453 {
1454 let code = u16::deserialize(deserializer)?;
1455
1456 Ok(ErrorCode::from_code(code))
1457 }
1458}
1459
1460impl fmt::Display for ErrorCode {
1461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1462 write!(f, "{}", self.to_code())
1463 }
1464}