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
589 assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
591 assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
592 assert!(
593 Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
594 );
595
596 assert!(!Error::Timeout.is_definitive_failure());
598 assert!(!Error::Internal.is_definitive_failure());
599 assert!(!Error::ConcurrentUpdate.is_definitive_failure());
600
601 assert!(
603 !Error::HttpError(Some(500), "Internal Server Error".to_string())
604 .is_definitive_failure()
605 );
606 assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
607 assert!(
608 !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
609 );
610
611 assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
613 }
614
615 #[test]
616 fn test_pending_states_are_ambiguous_failures() {
617 assert!(!Error::TokenPending.is_definitive_failure());
621 assert!(!Error::PendingQuote.is_definitive_failure());
622 }
623}
624
625impl Error {
626 pub fn is_definitive_failure(&self) -> bool {
635 match self {
636 Self::AmountKey
638 | Self::KeysetUnknown(_)
639 | Self::UnsupportedUnit
640 | Self::InvoiceAmountUndefined
641 | Self::SplitValuesGreater
642 | Self::AmountOverflow
643 | Self::OverIssue
644 | Self::SignatureMissingOrInvalid
645 | Self::AmountLessNotAllowed
646 | Self::InternalMultiPartMeltQuote
647 | Self::MppUnitMethodNotSupported(_, _)
648 | Self::AmountlessInvoiceNotSupported(_, _)
649 | Self::DuplicatePaymentId
650 | Self::PubkeyRequired
651 | Self::InvalidPaymentMethod
652 | Self::UnsupportedPaymentMethod
653 | Self::InvalidInvoice
654 | Self::MintingDisabled
655 | Self::UnknownQuote
656 | Self::ExpiredQuote(_, _)
657 | Self::AmountOutofLimitRange(_, _, _)
658 | Self::UnpaidQuote
659 | Self::IssuedQuote
660 | Self::PaidQuote
661 | Self::MeltingDisabled
662 | Self::UnknownKeySet
663 | Self::BlindedMessageAlreadySigned
664 | Self::InactiveKeyset
665 | Self::ExpiredKeyset
666 | Self::TransactionUnbalanced(_, _, _)
667 | Self::DuplicateInputs
668 | Self::DuplicateOutputs
669 | Self::DuplicateQuoteIds
670 | Self::BatchSizeExceeded { .. }
671 | Self::MultipleUnits
672 | Self::UnitMismatch
673 | Self::SigAllUsedInMelt
674 | Self::TokenAlreadySpent
675 | Self::P2PKConditionsNotMet(_)
676 | Self::DuplicateSignatureError
677 | Self::LocktimeNotProvided
678 | Self::InvalidSpendConditions(_)
679 | Self::IncorrectWallet(_)
680 | Self::MaxFeeExceeded
681 | Self::InvalidNut13Options { .. }
682 | Self::DleqProofNotProvided
683 | Self::IncorrectMint
684 | Self::MultiMintTokenNotSupported
685 | Self::PreimageNotProvided
686 | Self::UnknownMint { .. }
687 | Self::UnexpectedProofState
688 | Self::NoActiveKeyset
689 | Self::IncorrectQuoteAmount
690 | Self::InvoiceDescriptionUnsupported
691 | Self::InvalidTransactionDirection
692 | Self::InvalidTransactionId
693 | Self::InvalidOperationKind
694 | Self::InvalidOperationState
695 | Self::OperationNotFound
696 | Self::KVStoreInvalidKey(_)
697 | Self::Bip353Parse(_)
698 | Self::Bip353NoBolt12Offer
699 | Self::Bip321Parse(_)
700 | Self::Bip321Encode(_)
701 | Self::LightningAddressParse(_) => true,
702
703 #[cfg(feature = "mint")]
704 Self::OnchainQuoteLookupIdMismatch { .. }
705 | Self::OnchainFeeOptionsEmpty
706 | Self::OnchainFeeOptionsDuplicateIndex { .. }
707 | Self::OnchainFeeIndexNotFound { .. } => true,
708
709 Self::HttpError(Some(status), _) => {
711 (400..500).contains(status)
714 }
715
716 Self::Timeout
718 | Self::Internal
719 | Self::UnknownPaymentState
720 | Self::PendingQuote
721 | Self::TokenPending
722 | Self::CouldNotGetMintInfo
723 | Self::UnknownErrorResponse(_)
724 | Self::InvalidMintResponse(_)
725 | Self::ConcurrentUpdate
726 | Self::SendError(_)
727 | Self::RecvError(_)
728 | Self::TransferTimeout { .. }
729 | Self::Bip353Resolve(_)
730 | Self::LightningAddressRequest(_) => false,
731
732 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
736 | Self::Custom(_) => false,
737
738 Self::ClearAuthRequired
740 | Self::BlindAuthRequired
741 | Self::ClearAuthFailed
742 | Self::BlindAuthFailed
743 | Self::InsufficientBlindAuthTokens
744 | Self::AuthSettingsUndefined
745 | Self::AuthLocalstoreUndefined
746 | Self::OidcNotSet => true,
747
748 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
752 Self::UrlParseError(_) => true,
753 Self::Utf8ParseError(_) => true,
754 Self::Base64Error(_) => true,
755 Self::HexError(_) => true,
756 #[cfg(feature = "mint")]
757 Self::Uuid(_) => true,
758 Self::CashuUrl(_) => true,
759 Self::Secret(_) => true,
760 Self::AmountError(_) => true,
761 Self::DHKE(_) => true, Self::NUT00(_) => true,
763 Self::NUT01(_) => true,
764 Self::NUT02(_) => true,
765 Self::NUT03(_) => true,
766 Self::NUT04(_) => true,
767 Self::NUT05(_) => true,
768 Self::NUT11(_) => true,
769 Self::NUT12(_) => true,
770 #[cfg(feature = "wallet")]
771 Self::NUT13(_) => true,
772 Self::NUT14(_) => true,
773 Self::NUT18(_) => true,
774 Self::NUT20(_) => true,
775 Self::NUT21(_) => true,
776 Self::NUT22(_) => true,
777 Self::NUT23(_) => true,
778 #[cfg(feature = "mint")]
779 Self::QuoteId(_) => true,
780 Self::TryFromSliceError(_) => true,
781 #[cfg(feature = "mint")]
782 Self::Payment(_) => false, _ => false,
786 }
787 }
788}
789
790impl From<crate::nuts::nut10::Error> for Error {
791 fn from(err: crate::nuts::nut10::Error) -> Self {
792 match err {
793 crate::nuts::nut10::Error::NUT11(nut11_err) => Self::NUT11(nut11_err),
794 crate::nuts::nut10::Error::NUT14(nut14_err) => Self::NUT14(nut14_err),
795 other => Self::NUT10(other),
796 }
797 }
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
804pub struct ErrorResponse {
805 pub code: ErrorCode,
807 #[serde(default)]
809 pub detail: String,
810}
811
812impl fmt::Display for ErrorResponse {
813 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
814 write!(f, "code: {}, detail: {}", self.code, self.detail)
815 }
816}
817
818impl ErrorResponse {
819 pub fn new(code: ErrorCode, detail: String) -> Self {
821 Self { code, detail }
822 }
823
824 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
826 let value: Value = serde_json::from_str(json)?;
827
828 Self::from_value(value)
829 }
830
831 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
833 match serde_json::from_value::<ErrorResponse>(value.clone()) {
834 Ok(res) => Ok(res),
835 Err(_) => Ok(Self {
836 code: ErrorCode::Unknown(999),
837 detail: value.to_string(),
838 }),
839 }
840 }
841}
842
843fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
846 ErrorCode::WitnessMissingOrInvalid
848}
849
850impl From<Error> for ErrorResponse {
851 fn from(err: Error) -> ErrorResponse {
852 match err {
853 Error::TokenAlreadySpent => ErrorResponse {
854 code: ErrorCode::TokenAlreadySpent,
855 detail: err.to_string(),
856 },
857 Error::UnsupportedUnit => ErrorResponse {
858 code: ErrorCode::UnsupportedUnit,
859 detail: err.to_string(),
860 },
861 Error::PaymentFailed => ErrorResponse {
862 code: ErrorCode::LightningError,
863 detail: err.to_string(),
864 },
865 Error::RequestAlreadyPaid => ErrorResponse {
866 code: ErrorCode::InvoiceAlreadyPaid,
867 detail: "Invoice already paid.".to_string(),
868 },
869 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
870 ErrorResponse {
871 code: ErrorCode::TransactionUnbalanced,
872 detail: format!(
873 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
874 ),
875 }
876 }
877 Error::MintingDisabled => ErrorResponse {
878 code: ErrorCode::MintingDisabled,
879 detail: err.to_string(),
880 },
881 Error::BlindedMessageAlreadySigned => ErrorResponse {
882 code: ErrorCode::BlindedMessageAlreadySigned,
883 detail: err.to_string(),
884 },
885 Error::InsufficientFunds => ErrorResponse {
886 code: ErrorCode::TransactionUnbalanced,
887 detail: err.to_string(),
888 },
889 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
890 code: ErrorCode::AmountOutofLimitRange,
891 detail: err.to_string(),
892 },
893 Error::ExpiredQuote(_, _) => ErrorResponse {
894 code: ErrorCode::QuoteExpired,
895 detail: err.to_string(),
896 },
897 Error::PendingQuote => ErrorResponse {
898 code: ErrorCode::QuotePending,
899 detail: err.to_string(),
900 },
901 Error::PendingMeltTimeout { .. } => ErrorResponse {
902 code: ErrorCode::QuotePending,
903 detail: err.to_string(),
904 },
905 Error::TokenPending => ErrorResponse {
906 code: ErrorCode::TokenPending,
907 detail: err.to_string(),
908 },
909 Error::ClearAuthRequired => ErrorResponse {
910 code: ErrorCode::ClearAuthRequired,
911 detail: Error::ClearAuthRequired.to_string(),
912 },
913 Error::ClearAuthFailed => ErrorResponse {
914 code: ErrorCode::ClearAuthFailed,
915 detail: Error::ClearAuthFailed.to_string(),
916 },
917 Error::BlindAuthRequired => ErrorResponse {
918 code: ErrorCode::BlindAuthRequired,
919 detail: Error::BlindAuthRequired.to_string(),
920 },
921 Error::BlindAuthFailed => ErrorResponse {
922 code: ErrorCode::BlindAuthFailed,
923 detail: Error::BlindAuthFailed.to_string(),
924 },
925 Error::NUT20(err) => ErrorResponse {
926 code: ErrorCode::WitnessMissingOrInvalid,
927 detail: err.to_string(),
928 },
929 Error::DuplicateInputs => ErrorResponse {
930 code: ErrorCode::DuplicateInputs,
931 detail: err.to_string(),
932 },
933 Error::DuplicateOutputs => ErrorResponse {
934 code: ErrorCode::DuplicateOutputs,
935 detail: err.to_string(),
936 },
937 Error::MultipleUnits => ErrorResponse {
938 code: ErrorCode::MultipleUnits,
939 detail: err.to_string(),
940 },
941 Error::UnitMismatch => ErrorResponse {
942 code: ErrorCode::UnitMismatch,
943 detail: err.to_string(),
944 },
945 Error::UnpaidQuote => ErrorResponse {
946 code: ErrorCode::QuoteNotPaid,
947 detail: Error::UnpaidQuote.to_string(),
948 },
949 Error::NUT11(err) => {
950 let code = map_nut11_error(&err);
951 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
952 Some("P2PK signatures are required but not provided".to_string())
953 } else {
954 None
955 };
956 ErrorResponse {
957 code,
958 detail: match extra {
959 Some(extra) => format!("{err}. {extra}"),
960 None => err.to_string(),
961 },
962 }
963 },
964 Error::DuplicateSignatureError => ErrorResponse {
965 code: ErrorCode::WitnessMissingOrInvalid,
966 detail: err.to_string(),
967 },
968 Error::IssuedQuote => ErrorResponse {
969 code: ErrorCode::TokensAlreadyIssued,
970 detail: err.to_string(),
971 },
972 Error::UnknownKeySet => ErrorResponse {
973 code: ErrorCode::KeysetNotFound,
974 detail: err.to_string(),
975 },
976 Error::InactiveKeyset => ErrorResponse {
977 code: ErrorCode::KeysetInactive,
978 detail: err.to_string(),
979 },
980 Error::ExpiredKeyset => ErrorResponse {
981 code: ErrorCode::KeysetExpired,
982 detail: err.to_string(),
983 },
984 Error::AmountLessNotAllowed => ErrorResponse {
985 code: ErrorCode::AmountlessInvoiceNotSupported,
986 detail: err.to_string(),
987 },
988 Error::IncorrectQuoteAmount => ErrorResponse {
989 code: ErrorCode::IncorrectQuoteAmount,
990 detail: err.to_string(),
991 },
992 Error::PubkeyRequired => ErrorResponse {
993 code: ErrorCode::PubkeyRequired,
994 detail: err.to_string(),
995 },
996 Error::PaidQuote => ErrorResponse {
997 code: ErrorCode::InvoiceAlreadyPaid,
998 detail: err.to_string(),
999 },
1000 Error::DuplicatePaymentId => ErrorResponse {
1001 code: ErrorCode::InvoiceAlreadyPaid,
1002 detail: err.to_string(),
1003 },
1004 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
1006 code: ErrorCode::InvoiceAlreadyPaid,
1007 detail: "Invoice already paid or pending".to_string(),
1008 },
1009
1010 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
1012 code: ErrorCode::TokenNotVerified,
1013 detail: err.to_string(),
1014 },
1015 Error::DHKE(_) => ErrorResponse {
1016 code: ErrorCode::Unknown(50000),
1017 detail: err.to_string(),
1018 },
1019
1020 Error::CouldNotVerifyDleq => ErrorResponse {
1022 code: ErrorCode::TokenNotVerified,
1023 detail: err.to_string(),
1024 },
1025 Error::SignatureMissingOrInvalid => ErrorResponse {
1026 code: ErrorCode::WitnessMissingOrInvalid,
1027 detail: err.to_string(),
1028 },
1029 Error::SigAllUsedInMelt => ErrorResponse {
1030 code: ErrorCode::WitnessMissingOrInvalid,
1031 detail: err.to_string(),
1032 },
1033
1034 Error::AmountKey => ErrorResponse {
1036 code: ErrorCode::KeysetNotFound,
1037 detail: err.to_string(),
1038 },
1039 Error::KeysetUnknown(_) => ErrorResponse {
1040 code: ErrorCode::KeysetNotFound,
1041 detail: err.to_string(),
1042 },
1043 Error::NoActiveKeyset => ErrorResponse {
1044 code: ErrorCode::KeysetInactive,
1045 detail: err.to_string(),
1046 },
1047
1048 Error::UnknownQuote => ErrorResponse {
1050 code: ErrorCode::Unknown(50000),
1051 detail: err.to_string(),
1052 },
1053 Error::MeltingDisabled => ErrorResponse {
1054 code: ErrorCode::MintingDisabled,
1055 detail: err.to_string(),
1056 },
1057 Error::PaymentPending => ErrorResponse {
1058 code: ErrorCode::QuotePending,
1059 detail: err.to_string(),
1060 },
1061 Error::UnknownPaymentState => ErrorResponse {
1062 code: ErrorCode::Unknown(50000),
1063 detail: err.to_string(),
1064 },
1065
1066 Error::SplitValuesGreater => ErrorResponse {
1068 code: ErrorCode::TransactionUnbalanced,
1069 detail: err.to_string(),
1070 },
1071 Error::AmountOverflow => ErrorResponse {
1072 code: ErrorCode::TransactionUnbalanced,
1073 detail: err.to_string(),
1074 },
1075 Error::OverIssue => ErrorResponse {
1076 code: ErrorCode::TransactionUnbalanced,
1077 detail: err.to_string(),
1078 },
1079
1080 Error::InvalidPaymentRequest => ErrorResponse {
1082 code: ErrorCode::Unknown(50000),
1083 detail: err.to_string(),
1084 },
1085 Error::InvoiceAmountUndefined => ErrorResponse {
1086 code: ErrorCode::AmountlessInvoiceNotSupported,
1087 detail: err.to_string(),
1088 },
1089
1090 Error::Internal => ErrorResponse {
1092 code: ErrorCode::Unknown(50000),
1093 detail: err.to_string(),
1094 },
1095 Error::Database(_) => ErrorResponse {
1096 code: ErrorCode::Unknown(50000),
1097 detail: err.to_string(),
1098 },
1099 Error::ConcurrentUpdate => ErrorResponse {
1100 code: ErrorCode::ConcurrentUpdate,
1101 detail: err.to_string(),
1102 },
1103 Error::MaxInputsExceeded { .. } => ErrorResponse {
1104 code: ErrorCode::MaxInputsExceeded,
1105 detail: err.to_string()
1106 },
1107 Error::MaxOutputsExceeded { .. } => ErrorResponse {
1108 code: ErrorCode::MaxOutputsExceeded,
1109 detail: err.to_string()
1110 },
1111 Error::DuplicateQuoteIds => ErrorResponse {
1112 code: ErrorCode::DuplicateQuoteIds,
1113 detail: err.to_string(),
1114 },
1115 Error::BatchSizeExceeded { .. } => ErrorResponse {
1116 code: ErrorCode::BatchSizeExceeded,
1117 detail: err.to_string(),
1118 },
1119 _ => ErrorResponse {
1121 code: ErrorCode::Unknown(50000),
1122 detail: err.to_string(),
1123 },
1124 }
1125 }
1126}
1127
1128#[cfg(feature = "mint")]
1129impl From<crate::database::Error> for Error {
1130 fn from(db_error: crate::database::Error) -> Self {
1131 match db_error {
1132 crate::database::Error::InvalidStateTransition(state) => match state {
1133 crate::state::Error::Pending => Self::TokenPending,
1134 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
1135 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
1136 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
1137 },
1138 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1139 db_error => Self::Database(db_error),
1140 }
1141 }
1142}
1143
1144#[cfg(not(feature = "mint"))]
1145impl From<crate::database::Error> for Error {
1146 fn from(db_error: crate::database::Error) -> Self {
1147 match db_error {
1148 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1149 db_error => Self::Database(db_error),
1150 }
1151 }
1152}
1153
1154impl From<ErrorResponse> for Error {
1155 fn from(err: ErrorResponse) -> Error {
1156 match err.code {
1157 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1159 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1161 ErrorCode::TokenPending => Self::TokenPending,
1162 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1163 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1165 ErrorCode::AmountOutofLimitRange => {
1166 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1167 }
1168 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1169 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1170 ErrorCode::DuplicateQuoteIds => Self::DuplicateQuoteIds,
1171 ErrorCode::BatchSizeExceeded => Self::BatchSizeExceeded { actual: 0, max: 0 },
1172 ErrorCode::MultipleUnits => Self::MultipleUnits,
1173 ErrorCode::UnitMismatch => Self::UnitMismatch,
1174 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1175 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1176 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1177 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1179 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1180 ErrorCode::KeysetExpired => Self::ExpiredKeyset,
1181 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1183 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1184 ErrorCode::MintingDisabled => Self::MintingDisabled,
1185 ErrorCode::LightningError => Self::PaymentFailed,
1186 ErrorCode::QuotePending => Self::PendingQuote,
1187 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1188 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1189 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1190 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1191 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1193 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1194 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1196 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1197 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1198 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1199 _ => Self::UnknownErrorResponse(err.to_string()),
1200 }
1201 }
1202}
1203
1204#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1206pub enum ErrorCode {
1207 TokenNotVerified,
1210
1211 TokenAlreadySpent,
1214 TokenPending,
1216 BlindedMessageAlreadySigned,
1218 OutputsPending,
1220 TransactionUnbalanced,
1222 AmountOutofLimitRange,
1224 DuplicateInputs,
1226 DuplicateOutputs,
1228 MultipleUnits,
1230 UnitMismatch,
1232 AmountlessInvoiceNotSupported,
1234 IncorrectQuoteAmount,
1236 UnsupportedUnit,
1238 MaxInputsExceeded,
1240 MaxOutputsExceeded,
1242 DuplicateQuoteIds,
1244 BatchSizeExceeded,
1246 KeysetNotFound,
1249 KeysetInactive,
1251 KeysetExpired,
1253
1254 QuoteNotPaid,
1257 TokensAlreadyIssued,
1259 MintingDisabled,
1261 LightningError,
1263 QuotePending,
1265 InvoiceAlreadyPaid,
1267 QuoteExpired,
1269 WitnessMissingOrInvalid,
1271 PubkeyRequired,
1273
1274 ClearAuthRequired,
1277 ClearAuthFailed,
1279
1280 BlindAuthRequired,
1283 BlindAuthFailed,
1285 BatMintMaxExceeded,
1287 BatRateLimitExceeded,
1289
1290 ConcurrentUpdate,
1292
1293 Unknown(u16),
1295}
1296
1297impl ErrorCode {
1298 pub fn from_code(code: u16) -> Self {
1300 match code {
1301 10001 => Self::TokenNotVerified,
1303 11001 => Self::TokenAlreadySpent,
1305 11002 => Self::TokenPending,
1306 11003 => Self::BlindedMessageAlreadySigned,
1307 11004 => Self::OutputsPending,
1308 11005 => Self::TransactionUnbalanced,
1309 11006 => Self::AmountOutofLimitRange,
1310 11007 => Self::DuplicateInputs,
1311 11008 => Self::DuplicateOutputs,
1312 11009 => Self::MultipleUnits,
1313 11010 => Self::UnitMismatch,
1314 11011 => Self::AmountlessInvoiceNotSupported,
1315 11012 => Self::IncorrectQuoteAmount,
1316 11013 => Self::UnsupportedUnit,
1317 11014 => Self::MaxInputsExceeded,
1318 11015 => Self::MaxOutputsExceeded,
1319 11016 => Self::DuplicateQuoteIds,
1320 11017 => Self::BatchSizeExceeded,
1321 12001 => Self::KeysetNotFound,
1323 12002 => Self::KeysetInactive,
1324 12003 => Self::KeysetExpired,
1325 20001 => Self::QuoteNotPaid,
1327 20002 => Self::TokensAlreadyIssued,
1328 20003 => Self::MintingDisabled,
1329 20004 => Self::LightningError,
1330 20005 => Self::QuotePending,
1331 20006 => Self::InvoiceAlreadyPaid,
1332 20007 => Self::QuoteExpired,
1333 20008 => Self::WitnessMissingOrInvalid,
1334 20009 => Self::PubkeyRequired,
1335 30001 => Self::ClearAuthRequired,
1337 30002 => Self::ClearAuthFailed,
1338 31001 => Self::BlindAuthRequired,
1340 31002 => Self::BlindAuthFailed,
1341 31003 => Self::BatMintMaxExceeded,
1342 31004 => Self::BatRateLimitExceeded,
1343 _ => Self::Unknown(code),
1344 }
1345 }
1346
1347 pub fn to_code(&self) -> u16 {
1349 match self {
1350 Self::TokenNotVerified => 10001,
1352 Self::TokenAlreadySpent => 11001,
1354 Self::TokenPending => 11002,
1355 Self::BlindedMessageAlreadySigned => 11003,
1356 Self::OutputsPending => 11004,
1357 Self::TransactionUnbalanced => 11005,
1358 Self::AmountOutofLimitRange => 11006,
1359 Self::DuplicateInputs => 11007,
1360 Self::DuplicateOutputs => 11008,
1361 Self::MultipleUnits => 11009,
1362 Self::UnitMismatch => 11010,
1363 Self::AmountlessInvoiceNotSupported => 11011,
1364 Self::IncorrectQuoteAmount => 11012,
1365 Self::UnsupportedUnit => 11013,
1366 Self::MaxInputsExceeded => 11014,
1367 Self::MaxOutputsExceeded => 11015,
1368 Self::DuplicateQuoteIds => 11016,
1369 Self::BatchSizeExceeded => 11017,
1370 Self::KeysetNotFound => 12001,
1372 Self::KeysetInactive => 12002,
1373 Self::KeysetExpired => 12003,
1374 Self::QuoteNotPaid => 20001,
1376 Self::TokensAlreadyIssued => 20002,
1377 Self::MintingDisabled => 20003,
1378 Self::LightningError => 20004,
1379 Self::QuotePending => 20005,
1380 Self::InvoiceAlreadyPaid => 20006,
1381 Self::QuoteExpired => 20007,
1382 Self::WitnessMissingOrInvalid => 20008,
1383 Self::PubkeyRequired => 20009,
1384 Self::ClearAuthRequired => 30001,
1386 Self::ClearAuthFailed => 30002,
1387 Self::BlindAuthRequired => 31001,
1389 Self::BlindAuthFailed => 31002,
1390 Self::BatMintMaxExceeded => 31003,
1391 Self::BatRateLimitExceeded => 31004,
1392 Self::ConcurrentUpdate => 50000,
1393 Self::Unknown(code) => *code,
1394 }
1395 }
1396}
1397
1398impl Serialize for ErrorCode {
1399 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1400 where
1401 S: Serializer,
1402 {
1403 serializer.serialize_u16(self.to_code())
1404 }
1405}
1406
1407impl<'de> Deserialize<'de> for ErrorCode {
1408 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1409 where
1410 D: Deserializer<'de>,
1411 {
1412 let code = u16::deserialize(deserializer)?;
1413
1414 Ok(ErrorCode::from_code(code))
1415 }
1416}
1417
1418impl fmt::Display for ErrorCode {
1419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1420 write!(f, "{}", self.to_code())
1421 }
1422}