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
616impl Error {
617 pub fn is_definitive_failure(&self) -> bool {
626 match self {
627 Self::AmountKey
629 | Self::KeysetUnknown(_)
630 | Self::UnsupportedUnit
631 | Self::InvoiceAmountUndefined
632 | Self::SplitValuesGreater
633 | Self::AmountOverflow
634 | Self::OverIssue
635 | Self::SignatureMissingOrInvalid
636 | Self::AmountLessNotAllowed
637 | Self::InternalMultiPartMeltQuote
638 | Self::MppUnitMethodNotSupported(_, _)
639 | Self::AmountlessInvoiceNotSupported(_, _)
640 | Self::DuplicatePaymentId
641 | Self::PubkeyRequired
642 | Self::InvalidPaymentMethod
643 | Self::UnsupportedPaymentMethod
644 | Self::InvalidInvoice
645 | Self::MintingDisabled
646 | Self::UnknownQuote
647 | Self::ExpiredQuote(_, _)
648 | Self::AmountOutofLimitRange(_, _, _)
649 | Self::UnpaidQuote
650 | Self::PendingQuote
651 | Self::IssuedQuote
652 | Self::PaidQuote
653 | Self::MeltingDisabled
654 | Self::UnknownKeySet
655 | Self::BlindedMessageAlreadySigned
656 | Self::InactiveKeyset
657 | Self::ExpiredKeyset
658 | Self::TransactionUnbalanced(_, _, _)
659 | Self::DuplicateInputs
660 | Self::DuplicateOutputs
661 | Self::DuplicateQuoteIds
662 | Self::BatchSizeExceeded { .. }
663 | Self::MultipleUnits
664 | Self::UnitMismatch
665 | Self::SigAllUsedInMelt
666 | Self::TokenAlreadySpent
667 | Self::TokenPending
668 | Self::P2PKConditionsNotMet(_)
669 | Self::DuplicateSignatureError
670 | Self::LocktimeNotProvided
671 | Self::InvalidSpendConditions(_)
672 | Self::IncorrectWallet(_)
673 | Self::MaxFeeExceeded
674 | Self::InvalidNut13Options { .. }
675 | Self::DleqProofNotProvided
676 | Self::IncorrectMint
677 | Self::MultiMintTokenNotSupported
678 | Self::PreimageNotProvided
679 | Self::UnknownMint { .. }
680 | Self::UnexpectedProofState
681 | Self::NoActiveKeyset
682 | Self::IncorrectQuoteAmount
683 | Self::InvoiceDescriptionUnsupported
684 | Self::InvalidTransactionDirection
685 | Self::InvalidTransactionId
686 | Self::InvalidOperationKind
687 | Self::InvalidOperationState
688 | Self::OperationNotFound
689 | Self::KVStoreInvalidKey(_)
690 | Self::Bip353Parse(_)
691 | Self::Bip353NoBolt12Offer
692 | Self::Bip321Parse(_)
693 | Self::Bip321Encode(_)
694 | Self::LightningAddressParse(_) => true,
695
696 #[cfg(feature = "mint")]
697 Self::OnchainQuoteLookupIdMismatch { .. }
698 | Self::OnchainFeeOptionsEmpty
699 | Self::OnchainFeeOptionsDuplicateIndex { .. }
700 | Self::OnchainFeeIndexNotFound { .. } => true,
701
702 Self::HttpError(Some(status), _) => {
704 (400..500).contains(status)
707 }
708
709 Self::Timeout
711 | Self::Internal
712 | Self::UnknownPaymentState
713 | Self::CouldNotGetMintInfo
714 | Self::UnknownErrorResponse(_)
715 | Self::InvalidMintResponse(_)
716 | Self::ConcurrentUpdate
717 | Self::SendError(_)
718 | Self::RecvError(_)
719 | Self::TransferTimeout { .. }
720 | Self::Bip353Resolve(_)
721 | Self::LightningAddressRequest(_) => false,
722
723 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
727 | Self::Custom(_) => false,
728
729 Self::ClearAuthRequired
731 | Self::BlindAuthRequired
732 | Self::ClearAuthFailed
733 | Self::BlindAuthFailed
734 | Self::InsufficientBlindAuthTokens
735 | Self::AuthSettingsUndefined
736 | Self::AuthLocalstoreUndefined
737 | Self::OidcNotSet => true,
738
739 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
743 Self::UrlParseError(_) => true,
744 Self::Utf8ParseError(_) => true,
745 Self::Base64Error(_) => true,
746 Self::HexError(_) => true,
747 #[cfg(feature = "mint")]
748 Self::Uuid(_) => true,
749 Self::CashuUrl(_) => true,
750 Self::Secret(_) => true,
751 Self::AmountError(_) => true,
752 Self::DHKE(_) => true, Self::NUT00(_) => true,
754 Self::NUT01(_) => true,
755 Self::NUT02(_) => true,
756 Self::NUT03(_) => true,
757 Self::NUT04(_) => true,
758 Self::NUT05(_) => true,
759 Self::NUT11(_) => true,
760 Self::NUT12(_) => true,
761 #[cfg(feature = "wallet")]
762 Self::NUT13(_) => true,
763 Self::NUT14(_) => true,
764 Self::NUT18(_) => true,
765 Self::NUT20(_) => true,
766 Self::NUT21(_) => true,
767 Self::NUT22(_) => true,
768 Self::NUT23(_) => true,
769 #[cfg(feature = "mint")]
770 Self::QuoteId(_) => true,
771 Self::TryFromSliceError(_) => true,
772 #[cfg(feature = "mint")]
773 Self::Payment(_) => false, _ => false,
777 }
778 }
779}
780
781impl From<crate::nuts::nut10::Error> for Error {
782 fn from(err: crate::nuts::nut10::Error) -> Self {
783 match err {
784 crate::nuts::nut10::Error::NUT11(nut11_err) => Self::NUT11(nut11_err),
785 crate::nuts::nut10::Error::NUT14(nut14_err) => Self::NUT14(nut14_err),
786 other => Self::NUT10(other),
787 }
788 }
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
795pub struct ErrorResponse {
796 pub code: ErrorCode,
798 #[serde(default)]
800 pub detail: String,
801}
802
803impl fmt::Display for ErrorResponse {
804 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805 write!(f, "code: {}, detail: {}", self.code, self.detail)
806 }
807}
808
809impl ErrorResponse {
810 pub fn new(code: ErrorCode, detail: String) -> Self {
812 Self { code, detail }
813 }
814
815 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
817 let value: Value = serde_json::from_str(json)?;
818
819 Self::from_value(value)
820 }
821
822 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
824 match serde_json::from_value::<ErrorResponse>(value.clone()) {
825 Ok(res) => Ok(res),
826 Err(_) => Ok(Self {
827 code: ErrorCode::Unknown(999),
828 detail: value.to_string(),
829 }),
830 }
831 }
832}
833
834fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
837 ErrorCode::WitnessMissingOrInvalid
839}
840
841impl From<Error> for ErrorResponse {
842 fn from(err: Error) -> ErrorResponse {
843 match err {
844 Error::TokenAlreadySpent => ErrorResponse {
845 code: ErrorCode::TokenAlreadySpent,
846 detail: err.to_string(),
847 },
848 Error::UnsupportedUnit => ErrorResponse {
849 code: ErrorCode::UnsupportedUnit,
850 detail: err.to_string(),
851 },
852 Error::PaymentFailed => ErrorResponse {
853 code: ErrorCode::LightningError,
854 detail: err.to_string(),
855 },
856 Error::RequestAlreadyPaid => ErrorResponse {
857 code: ErrorCode::InvoiceAlreadyPaid,
858 detail: "Invoice already paid.".to_string(),
859 },
860 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
861 ErrorResponse {
862 code: ErrorCode::TransactionUnbalanced,
863 detail: format!(
864 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
865 ),
866 }
867 }
868 Error::MintingDisabled => ErrorResponse {
869 code: ErrorCode::MintingDisabled,
870 detail: err.to_string(),
871 },
872 Error::BlindedMessageAlreadySigned => ErrorResponse {
873 code: ErrorCode::BlindedMessageAlreadySigned,
874 detail: err.to_string(),
875 },
876 Error::InsufficientFunds => ErrorResponse {
877 code: ErrorCode::TransactionUnbalanced,
878 detail: err.to_string(),
879 },
880 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
881 code: ErrorCode::AmountOutofLimitRange,
882 detail: err.to_string(),
883 },
884 Error::ExpiredQuote(_, _) => ErrorResponse {
885 code: ErrorCode::QuoteExpired,
886 detail: err.to_string(),
887 },
888 Error::PendingQuote => ErrorResponse {
889 code: ErrorCode::QuotePending,
890 detail: err.to_string(),
891 },
892 Error::PendingMeltTimeout { .. } => ErrorResponse {
893 code: ErrorCode::QuotePending,
894 detail: err.to_string(),
895 },
896 Error::TokenPending => ErrorResponse {
897 code: ErrorCode::TokenPending,
898 detail: err.to_string(),
899 },
900 Error::ClearAuthRequired => ErrorResponse {
901 code: ErrorCode::ClearAuthRequired,
902 detail: Error::ClearAuthRequired.to_string(),
903 },
904 Error::ClearAuthFailed => ErrorResponse {
905 code: ErrorCode::ClearAuthFailed,
906 detail: Error::ClearAuthFailed.to_string(),
907 },
908 Error::BlindAuthRequired => ErrorResponse {
909 code: ErrorCode::BlindAuthRequired,
910 detail: Error::BlindAuthRequired.to_string(),
911 },
912 Error::BlindAuthFailed => ErrorResponse {
913 code: ErrorCode::BlindAuthFailed,
914 detail: Error::BlindAuthFailed.to_string(),
915 },
916 Error::NUT20(err) => ErrorResponse {
917 code: ErrorCode::WitnessMissingOrInvalid,
918 detail: err.to_string(),
919 },
920 Error::DuplicateInputs => ErrorResponse {
921 code: ErrorCode::DuplicateInputs,
922 detail: err.to_string(),
923 },
924 Error::DuplicateOutputs => ErrorResponse {
925 code: ErrorCode::DuplicateOutputs,
926 detail: err.to_string(),
927 },
928 Error::MultipleUnits => ErrorResponse {
929 code: ErrorCode::MultipleUnits,
930 detail: err.to_string(),
931 },
932 Error::UnitMismatch => ErrorResponse {
933 code: ErrorCode::UnitMismatch,
934 detail: err.to_string(),
935 },
936 Error::UnpaidQuote => ErrorResponse {
937 code: ErrorCode::QuoteNotPaid,
938 detail: Error::UnpaidQuote.to_string(),
939 },
940 Error::NUT11(err) => {
941 let code = map_nut11_error(&err);
942 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
943 Some("P2PK signatures are required but not provided".to_string())
944 } else {
945 None
946 };
947 ErrorResponse {
948 code,
949 detail: match extra {
950 Some(extra) => format!("{err}. {extra}"),
951 None => err.to_string(),
952 },
953 }
954 },
955 Error::DuplicateSignatureError => ErrorResponse {
956 code: ErrorCode::WitnessMissingOrInvalid,
957 detail: err.to_string(),
958 },
959 Error::IssuedQuote => ErrorResponse {
960 code: ErrorCode::TokensAlreadyIssued,
961 detail: err.to_string(),
962 },
963 Error::UnknownKeySet => ErrorResponse {
964 code: ErrorCode::KeysetNotFound,
965 detail: err.to_string(),
966 },
967 Error::InactiveKeyset => ErrorResponse {
968 code: ErrorCode::KeysetInactive,
969 detail: err.to_string(),
970 },
971 Error::ExpiredKeyset => ErrorResponse {
972 code: ErrorCode::KeysetExpired,
973 detail: err.to_string(),
974 },
975 Error::AmountLessNotAllowed => ErrorResponse {
976 code: ErrorCode::AmountlessInvoiceNotSupported,
977 detail: err.to_string(),
978 },
979 Error::IncorrectQuoteAmount => ErrorResponse {
980 code: ErrorCode::IncorrectQuoteAmount,
981 detail: err.to_string(),
982 },
983 Error::PubkeyRequired => ErrorResponse {
984 code: ErrorCode::PubkeyRequired,
985 detail: err.to_string(),
986 },
987 Error::PaidQuote => ErrorResponse {
988 code: ErrorCode::InvoiceAlreadyPaid,
989 detail: err.to_string(),
990 },
991 Error::DuplicatePaymentId => ErrorResponse {
992 code: ErrorCode::InvoiceAlreadyPaid,
993 detail: err.to_string(),
994 },
995 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
997 code: ErrorCode::InvoiceAlreadyPaid,
998 detail: "Invoice already paid or pending".to_string(),
999 },
1000
1001 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
1003 code: ErrorCode::TokenNotVerified,
1004 detail: err.to_string(),
1005 },
1006 Error::DHKE(_) => ErrorResponse {
1007 code: ErrorCode::Unknown(50000),
1008 detail: err.to_string(),
1009 },
1010
1011 Error::CouldNotVerifyDleq => ErrorResponse {
1013 code: ErrorCode::TokenNotVerified,
1014 detail: err.to_string(),
1015 },
1016 Error::SignatureMissingOrInvalid => ErrorResponse {
1017 code: ErrorCode::WitnessMissingOrInvalid,
1018 detail: err.to_string(),
1019 },
1020 Error::SigAllUsedInMelt => ErrorResponse {
1021 code: ErrorCode::WitnessMissingOrInvalid,
1022 detail: err.to_string(),
1023 },
1024
1025 Error::AmountKey => ErrorResponse {
1027 code: ErrorCode::KeysetNotFound,
1028 detail: err.to_string(),
1029 },
1030 Error::KeysetUnknown(_) => ErrorResponse {
1031 code: ErrorCode::KeysetNotFound,
1032 detail: err.to_string(),
1033 },
1034 Error::NoActiveKeyset => ErrorResponse {
1035 code: ErrorCode::KeysetInactive,
1036 detail: err.to_string(),
1037 },
1038
1039 Error::UnknownQuote => ErrorResponse {
1041 code: ErrorCode::Unknown(50000),
1042 detail: err.to_string(),
1043 },
1044 Error::MeltingDisabled => ErrorResponse {
1045 code: ErrorCode::MintingDisabled,
1046 detail: err.to_string(),
1047 },
1048 Error::PaymentPending => ErrorResponse {
1049 code: ErrorCode::QuotePending,
1050 detail: err.to_string(),
1051 },
1052 Error::UnknownPaymentState => ErrorResponse {
1053 code: ErrorCode::Unknown(50000),
1054 detail: err.to_string(),
1055 },
1056
1057 Error::SplitValuesGreater => ErrorResponse {
1059 code: ErrorCode::TransactionUnbalanced,
1060 detail: err.to_string(),
1061 },
1062 Error::AmountOverflow => ErrorResponse {
1063 code: ErrorCode::TransactionUnbalanced,
1064 detail: err.to_string(),
1065 },
1066 Error::OverIssue => ErrorResponse {
1067 code: ErrorCode::TransactionUnbalanced,
1068 detail: err.to_string(),
1069 },
1070
1071 Error::InvalidPaymentRequest => ErrorResponse {
1073 code: ErrorCode::Unknown(50000),
1074 detail: err.to_string(),
1075 },
1076 Error::InvoiceAmountUndefined => ErrorResponse {
1077 code: ErrorCode::AmountlessInvoiceNotSupported,
1078 detail: err.to_string(),
1079 },
1080
1081 Error::Internal => ErrorResponse {
1083 code: ErrorCode::Unknown(50000),
1084 detail: err.to_string(),
1085 },
1086 Error::Database(_) => ErrorResponse {
1087 code: ErrorCode::Unknown(50000),
1088 detail: err.to_string(),
1089 },
1090 Error::ConcurrentUpdate => ErrorResponse {
1091 code: ErrorCode::ConcurrentUpdate,
1092 detail: err.to_string(),
1093 },
1094 Error::MaxInputsExceeded { .. } => ErrorResponse {
1095 code: ErrorCode::MaxInputsExceeded,
1096 detail: err.to_string()
1097 },
1098 Error::MaxOutputsExceeded { .. } => ErrorResponse {
1099 code: ErrorCode::MaxOutputsExceeded,
1100 detail: err.to_string()
1101 },
1102 Error::DuplicateQuoteIds => ErrorResponse {
1103 code: ErrorCode::DuplicateQuoteIds,
1104 detail: err.to_string(),
1105 },
1106 Error::BatchSizeExceeded { .. } => ErrorResponse {
1107 code: ErrorCode::BatchSizeExceeded,
1108 detail: err.to_string(),
1109 },
1110 _ => ErrorResponse {
1112 code: ErrorCode::Unknown(50000),
1113 detail: err.to_string(),
1114 },
1115 }
1116 }
1117}
1118
1119#[cfg(feature = "mint")]
1120impl From<crate::database::Error> for Error {
1121 fn from(db_error: crate::database::Error) -> Self {
1122 match db_error {
1123 crate::database::Error::InvalidStateTransition(state) => match state {
1124 crate::state::Error::Pending => Self::TokenPending,
1125 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
1126 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
1127 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
1128 },
1129 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1130 db_error => Self::Database(db_error),
1131 }
1132 }
1133}
1134
1135#[cfg(not(feature = "mint"))]
1136impl From<crate::database::Error> for Error {
1137 fn from(db_error: crate::database::Error) -> Self {
1138 match db_error {
1139 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1140 db_error => Self::Database(db_error),
1141 }
1142 }
1143}
1144
1145impl From<ErrorResponse> for Error {
1146 fn from(err: ErrorResponse) -> Error {
1147 match err.code {
1148 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1150 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1152 ErrorCode::TokenPending => Self::TokenPending,
1153 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1154 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1156 ErrorCode::AmountOutofLimitRange => {
1157 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1158 }
1159 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1160 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1161 ErrorCode::DuplicateQuoteIds => Self::DuplicateQuoteIds,
1162 ErrorCode::BatchSizeExceeded => Self::BatchSizeExceeded { actual: 0, max: 0 },
1163 ErrorCode::MultipleUnits => Self::MultipleUnits,
1164 ErrorCode::UnitMismatch => Self::UnitMismatch,
1165 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1166 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1167 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1168 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1170 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1171 ErrorCode::KeysetExpired => Self::ExpiredKeyset,
1172 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1174 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1175 ErrorCode::MintingDisabled => Self::MintingDisabled,
1176 ErrorCode::LightningError => Self::PaymentFailed,
1177 ErrorCode::QuotePending => Self::PendingQuote,
1178 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1179 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1180 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1181 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1182 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1184 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1185 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1187 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1188 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1189 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1190 _ => Self::UnknownErrorResponse(err.to_string()),
1191 }
1192 }
1193}
1194
1195#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1197pub enum ErrorCode {
1198 TokenNotVerified,
1201
1202 TokenAlreadySpent,
1205 TokenPending,
1207 BlindedMessageAlreadySigned,
1209 OutputsPending,
1211 TransactionUnbalanced,
1213 AmountOutofLimitRange,
1215 DuplicateInputs,
1217 DuplicateOutputs,
1219 MultipleUnits,
1221 UnitMismatch,
1223 AmountlessInvoiceNotSupported,
1225 IncorrectQuoteAmount,
1227 UnsupportedUnit,
1229 MaxInputsExceeded,
1231 MaxOutputsExceeded,
1233 DuplicateQuoteIds,
1235 BatchSizeExceeded,
1237 KeysetNotFound,
1240 KeysetInactive,
1242 KeysetExpired,
1244
1245 QuoteNotPaid,
1248 TokensAlreadyIssued,
1250 MintingDisabled,
1252 LightningError,
1254 QuotePending,
1256 InvoiceAlreadyPaid,
1258 QuoteExpired,
1260 WitnessMissingOrInvalid,
1262 PubkeyRequired,
1264
1265 ClearAuthRequired,
1268 ClearAuthFailed,
1270
1271 BlindAuthRequired,
1274 BlindAuthFailed,
1276 BatMintMaxExceeded,
1278 BatRateLimitExceeded,
1280
1281 ConcurrentUpdate,
1283
1284 Unknown(u16),
1286}
1287
1288impl ErrorCode {
1289 pub fn from_code(code: u16) -> Self {
1291 match code {
1292 10001 => Self::TokenNotVerified,
1294 11001 => Self::TokenAlreadySpent,
1296 11002 => Self::TokenPending,
1297 11003 => Self::BlindedMessageAlreadySigned,
1298 11004 => Self::OutputsPending,
1299 11005 => Self::TransactionUnbalanced,
1300 11006 => Self::AmountOutofLimitRange,
1301 11007 => Self::DuplicateInputs,
1302 11008 => Self::DuplicateOutputs,
1303 11009 => Self::MultipleUnits,
1304 11010 => Self::UnitMismatch,
1305 11011 => Self::AmountlessInvoiceNotSupported,
1306 11012 => Self::IncorrectQuoteAmount,
1307 11013 => Self::UnsupportedUnit,
1308 11014 => Self::MaxInputsExceeded,
1309 11015 => Self::MaxOutputsExceeded,
1310 11016 => Self::DuplicateQuoteIds,
1311 11017 => Self::BatchSizeExceeded,
1312 12001 => Self::KeysetNotFound,
1314 12002 => Self::KeysetInactive,
1315 12003 => Self::KeysetExpired,
1316 20001 => Self::QuoteNotPaid,
1318 20002 => Self::TokensAlreadyIssued,
1319 20003 => Self::MintingDisabled,
1320 20004 => Self::LightningError,
1321 20005 => Self::QuotePending,
1322 20006 => Self::InvoiceAlreadyPaid,
1323 20007 => Self::QuoteExpired,
1324 20008 => Self::WitnessMissingOrInvalid,
1325 20009 => Self::PubkeyRequired,
1326 30001 => Self::ClearAuthRequired,
1328 30002 => Self::ClearAuthFailed,
1329 31001 => Self::BlindAuthRequired,
1331 31002 => Self::BlindAuthFailed,
1332 31003 => Self::BatMintMaxExceeded,
1333 31004 => Self::BatRateLimitExceeded,
1334 _ => Self::Unknown(code),
1335 }
1336 }
1337
1338 pub fn to_code(&self) -> u16 {
1340 match self {
1341 Self::TokenNotVerified => 10001,
1343 Self::TokenAlreadySpent => 11001,
1345 Self::TokenPending => 11002,
1346 Self::BlindedMessageAlreadySigned => 11003,
1347 Self::OutputsPending => 11004,
1348 Self::TransactionUnbalanced => 11005,
1349 Self::AmountOutofLimitRange => 11006,
1350 Self::DuplicateInputs => 11007,
1351 Self::DuplicateOutputs => 11008,
1352 Self::MultipleUnits => 11009,
1353 Self::UnitMismatch => 11010,
1354 Self::AmountlessInvoiceNotSupported => 11011,
1355 Self::IncorrectQuoteAmount => 11012,
1356 Self::UnsupportedUnit => 11013,
1357 Self::MaxInputsExceeded => 11014,
1358 Self::MaxOutputsExceeded => 11015,
1359 Self::DuplicateQuoteIds => 11016,
1360 Self::BatchSizeExceeded => 11017,
1361 Self::KeysetNotFound => 12001,
1363 Self::KeysetInactive => 12002,
1364 Self::KeysetExpired => 12003,
1365 Self::QuoteNotPaid => 20001,
1367 Self::TokensAlreadyIssued => 20002,
1368 Self::MintingDisabled => 20003,
1369 Self::LightningError => 20004,
1370 Self::QuotePending => 20005,
1371 Self::InvoiceAlreadyPaid => 20006,
1372 Self::QuoteExpired => 20007,
1373 Self::WitnessMissingOrInvalid => 20008,
1374 Self::PubkeyRequired => 20009,
1375 Self::ClearAuthRequired => 30001,
1377 Self::ClearAuthFailed => 30002,
1378 Self::BlindAuthRequired => 31001,
1380 Self::BlindAuthFailed => 31002,
1381 Self::BatMintMaxExceeded => 31003,
1382 Self::BatRateLimitExceeded => 31004,
1383 Self::ConcurrentUpdate => 50000,
1384 Self::Unknown(code) => *code,
1385 }
1386 }
1387}
1388
1389impl Serialize for ErrorCode {
1390 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1391 where
1392 S: Serializer,
1393 {
1394 serializer.serialize_u16(self.to_code())
1395 }
1396}
1397
1398impl<'de> Deserialize<'de> for ErrorCode {
1399 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1400 where
1401 D: Deserializer<'de>,
1402 {
1403 let code = u16::deserialize(deserializer)?;
1404
1405 Ok(ErrorCode::from_code(code))
1406 }
1407}
1408
1409impl fmt::Display for ErrorCode {
1410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1411 write!(f, "{}", self.to_code())
1412 }
1413}