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("Url path segments could not be joined")]
374 UrlPathSegments,
375 #[error("Unknown error response: `{0}`")]
377 UnknownErrorResponse(String),
378 #[error("Could not verify DLEQ proof")]
380 CouldNotVerifyDleq,
381 #[error("Dleq proof not provided for signature")]
383 DleqProofNotProvided,
384 #[error("Token does not match wallet mint")]
387 IncorrectMint,
388 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
390 MultiMintTokenNotSupported,
391 #[error("Preimage not provided")]
393 PreimageNotProvided,
394
395 #[error("Unknown mint: {mint_url}")]
397 UnknownMint {
398 mint_url: String,
400 },
401 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
403 TransferTimeout {
404 source_mint: String,
406 target_mint: String,
408 amount: Amount,
410 },
411 #[error("Insufficient funds")]
413 InsufficientFunds,
414 #[error("Unexpected proof state")]
416 UnexpectedProofState,
417 #[error("No active keyset")]
419 NoActiveKeyset,
420 #[error("Incorrect quote amount")]
422 IncorrectQuoteAmount,
423 #[error("Invoice Description not supported")]
425 InvoiceDescriptionUnsupported,
426 #[error("Invalid transaction direction")]
428 InvalidTransactionDirection,
429 #[error("Invalid transaction id")]
431 InvalidTransactionId,
432 #[error("Transaction not found")]
434 TransactionNotFound,
435 #[error("Invalid operation kind")]
437 InvalidOperationKind,
438 #[error("Invalid operation state")]
440 InvalidOperationState,
441 #[error("Operation not found")]
443 OperationNotFound,
444 #[error("Invalid KV store key or namespace: {0}")]
446 KVStoreInvalidKey(String),
447 #[error("Concurrent update detected")]
449 ConcurrentUpdate,
450 #[error("Invalid mint response: {0}")]
452 InvalidMintResponse(String),
453 #[error("Subscription error: {0}")]
455 SubscriptionError(String),
456 #[error("`{0}`")]
458 Custom(String),
459
460 #[error(transparent)]
463 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
464 #[error(transparent)]
466 Bip32(#[from] bitcoin::bip32::Error),
467 #[error(transparent)]
469 ParseInt(#[from] std::num::ParseIntError),
470 #[error(transparent)]
472 UrlParseError(#[from] url::ParseError),
473 #[error(transparent)]
475 Utf8ParseError(#[from] std::string::FromUtf8Error),
476 #[error(transparent)]
478 SerdeJsonError(#[from] serde_json::Error),
479 #[error(transparent)]
481 Base64Error(#[from] bitcoin::base64::DecodeError),
482 #[error(transparent)]
484 HexError(#[from] hex::Error),
485 #[error("Http transport error {0:?}: {1}")]
487 HttpError(Option<u16>, String),
488 #[cfg(feature = "mint")]
490 #[error(transparent)]
491 Uuid(#[from] uuid::Error),
492 #[error(transparent)]
495 CashuUrl(#[from] crate::mint_url::Error),
496 #[error(transparent)]
498 Secret(#[from] crate::secret::Error),
499 #[error(transparent)]
501 AmountError(#[from] crate::amount::Error),
502 #[error(transparent)]
504 DHKE(#[from] crate::dhke::Error),
505 #[error(transparent)]
507 NUT00(#[from] crate::nuts::nut00::Error),
508 #[error(transparent)]
510 NUT01(#[from] crate::nuts::nut01::Error),
511 #[error(transparent)]
513 NUT02(#[from] crate::nuts::nut02::Error),
514 #[error(transparent)]
516 NUT03(#[from] crate::nuts::nut03::Error),
517 #[error(transparent)]
519 NUT04(#[from] crate::nuts::nut04::Error),
520 #[error(transparent)]
522 NUT05(#[from] crate::nuts::nut05::Error),
523 #[error(transparent)]
525 NUT10(crate::nuts::nut10::Error),
526 #[error(transparent)]
528 NUT11(#[from] crate::nuts::nut11::Error),
529 #[error(transparent)]
531 NUT12(#[from] crate::nuts::nut12::Error),
532 #[error(transparent)]
534 #[cfg(feature = "wallet")]
535 NUT13(#[from] crate::nuts::nut13::Error),
536 #[error(transparent)]
538 NUT14(#[from] crate::nuts::nut14::Error),
539 #[error(transparent)]
541 NUT18(#[from] crate::nuts::nut18::Error),
542 #[error(transparent)]
544 NUT20(#[from] crate::nuts::nut20::Error),
545 #[error(transparent)]
547 NUT21(#[from] crate::nuts::nut21::Error),
548 #[error(transparent)]
550 NUT22(#[from] crate::nuts::nut22::Error),
551 #[error(transparent)]
553 NUT23(#[from] crate::nuts::nut23::Error),
554 #[error(transparent)]
556 #[cfg(feature = "mint")]
557 QuoteId(#[from] crate::quote_id::QuoteIdError),
558 #[error(transparent)]
560 TryFromSliceError(#[from] TryFromSliceError),
561 #[error(transparent)]
563 Database(crate::database::Error),
564 #[error(transparent)]
566 #[cfg(feature = "mint")]
567 Payment(#[from] crate::payment::Error),
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn test_is_definitive_failure() {
576 assert!(Error::AmountOverflow.is_definitive_failure());
578 assert!(Error::TokenAlreadySpent.is_definitive_failure());
579 assert!(Error::MintingDisabled.is_definitive_failure());
580
581 assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
583 assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
584 assert!(
585 Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
586 );
587
588 assert!(!Error::Timeout.is_definitive_failure());
590 assert!(!Error::Internal.is_definitive_failure());
591 assert!(!Error::ConcurrentUpdate.is_definitive_failure());
592
593 assert!(
595 !Error::HttpError(Some(500), "Internal Server Error".to_string())
596 .is_definitive_failure()
597 );
598 assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
599 assert!(
600 !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
601 );
602
603 assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
605 }
606}
607
608impl Error {
609 pub fn is_definitive_failure(&self) -> bool {
618 match self {
619 Self::AmountKey
621 | Self::KeysetUnknown(_)
622 | Self::UnsupportedUnit
623 | Self::InvoiceAmountUndefined
624 | Self::SplitValuesGreater
625 | Self::AmountOverflow
626 | Self::OverIssue
627 | Self::SignatureMissingOrInvalid
628 | Self::AmountLessNotAllowed
629 | Self::InternalMultiPartMeltQuote
630 | Self::MppUnitMethodNotSupported(_, _)
631 | Self::AmountlessInvoiceNotSupported(_, _)
632 | Self::DuplicatePaymentId
633 | Self::PubkeyRequired
634 | Self::InvalidPaymentMethod
635 | Self::UnsupportedPaymentMethod
636 | Self::InvalidInvoice
637 | Self::MintingDisabled
638 | Self::UnknownQuote
639 | Self::ExpiredQuote(_, _)
640 | Self::AmountOutofLimitRange(_, _, _)
641 | Self::UnpaidQuote
642 | Self::PendingQuote
643 | Self::IssuedQuote
644 | Self::PaidQuote
645 | Self::MeltingDisabled
646 | Self::UnknownKeySet
647 | Self::BlindedMessageAlreadySigned
648 | Self::InactiveKeyset
649 | Self::ExpiredKeyset
650 | Self::TransactionUnbalanced(_, _, _)
651 | Self::DuplicateInputs
652 | Self::DuplicateOutputs
653 | Self::DuplicateQuoteIds
654 | Self::BatchSizeExceeded { .. }
655 | Self::MultipleUnits
656 | Self::UnitMismatch
657 | Self::SigAllUsedInMelt
658 | Self::TokenAlreadySpent
659 | Self::TokenPending
660 | Self::P2PKConditionsNotMet(_)
661 | Self::DuplicateSignatureError
662 | Self::LocktimeNotProvided
663 | Self::InvalidSpendConditions(_)
664 | Self::IncorrectWallet(_)
665 | Self::MaxFeeExceeded
666 | Self::DleqProofNotProvided
667 | Self::IncorrectMint
668 | Self::MultiMintTokenNotSupported
669 | Self::PreimageNotProvided
670 | Self::UnknownMint { .. }
671 | Self::UnexpectedProofState
672 | Self::NoActiveKeyset
673 | Self::IncorrectQuoteAmount
674 | Self::InvoiceDescriptionUnsupported
675 | Self::InvalidTransactionDirection
676 | Self::InvalidTransactionId
677 | Self::InvalidOperationKind
678 | Self::InvalidOperationState
679 | Self::OperationNotFound
680 | Self::KVStoreInvalidKey(_)
681 | Self::Bip353Parse(_)
682 | Self::Bip353NoBolt12Offer
683 | Self::Bip321Parse(_)
684 | Self::Bip321Encode(_)
685 | Self::LightningAddressParse(_) => true,
686
687 #[cfg(feature = "mint")]
688 Self::OnchainQuoteLookupIdMismatch { .. }
689 | Self::OnchainFeeOptionsEmpty
690 | Self::OnchainFeeOptionsDuplicateIndex { .. }
691 | Self::OnchainFeeIndexNotFound { .. } => true,
692
693 Self::HttpError(Some(status), _) => {
695 (400..500).contains(status)
698 }
699
700 Self::Timeout
702 | Self::Internal
703 | Self::UnknownPaymentState
704 | Self::CouldNotGetMintInfo
705 | Self::UnknownErrorResponse(_)
706 | Self::InvalidMintResponse(_)
707 | Self::ConcurrentUpdate
708 | Self::SendError(_)
709 | Self::RecvError(_)
710 | Self::TransferTimeout { .. }
711 | Self::Bip353Resolve(_)
712 | Self::LightningAddressRequest(_) => false,
713
714 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
718 | Self::Custom(_) => false,
719
720 Self::ClearAuthRequired
722 | Self::BlindAuthRequired
723 | Self::ClearAuthFailed
724 | Self::BlindAuthFailed
725 | Self::InsufficientBlindAuthTokens
726 | Self::AuthSettingsUndefined
727 | Self::AuthLocalstoreUndefined
728 | Self::OidcNotSet => true,
729
730 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
734 Self::UrlParseError(_) => true,
735 Self::Utf8ParseError(_) => true,
736 Self::Base64Error(_) => true,
737 Self::HexError(_) => true,
738 #[cfg(feature = "mint")]
739 Self::Uuid(_) => true,
740 Self::CashuUrl(_) => true,
741 Self::Secret(_) => true,
742 Self::AmountError(_) => true,
743 Self::DHKE(_) => true, Self::NUT00(_) => true,
745 Self::NUT01(_) => true,
746 Self::NUT02(_) => true,
747 Self::NUT03(_) => true,
748 Self::NUT04(_) => true,
749 Self::NUT05(_) => true,
750 Self::NUT11(_) => true,
751 Self::NUT12(_) => true,
752 #[cfg(feature = "wallet")]
753 Self::NUT13(_) => true,
754 Self::NUT14(_) => true,
755 Self::NUT18(_) => true,
756 Self::NUT20(_) => true,
757 Self::NUT21(_) => true,
758 Self::NUT22(_) => true,
759 Self::NUT23(_) => true,
760 #[cfg(feature = "mint")]
761 Self::QuoteId(_) => true,
762 Self::TryFromSliceError(_) => true,
763 #[cfg(feature = "mint")]
764 Self::Payment(_) => false, _ => false,
768 }
769 }
770}
771
772impl From<crate::nuts::nut10::Error> for Error {
773 fn from(err: crate::nuts::nut10::Error) -> Self {
774 match err {
775 crate::nuts::nut10::Error::NUT11(nut11_err) => Self::NUT11(nut11_err),
776 crate::nuts::nut10::Error::NUT14(nut14_err) => Self::NUT14(nut14_err),
777 other => Self::NUT10(other),
778 }
779 }
780}
781
782#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
786#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
787pub struct ErrorResponse {
788 pub code: ErrorCode,
790 #[serde(default)]
792 pub detail: String,
793}
794
795impl fmt::Display for ErrorResponse {
796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
797 write!(f, "code: {}, detail: {}", self.code, self.detail)
798 }
799}
800
801impl ErrorResponse {
802 pub fn new(code: ErrorCode, detail: String) -> Self {
804 Self { code, detail }
805 }
806
807 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
809 let value: Value = serde_json::from_str(json)?;
810
811 Self::from_value(value)
812 }
813
814 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
816 match serde_json::from_value::<ErrorResponse>(value.clone()) {
817 Ok(res) => Ok(res),
818 Err(_) => Ok(Self {
819 code: ErrorCode::Unknown(999),
820 detail: value.to_string(),
821 }),
822 }
823 }
824}
825
826fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
829 ErrorCode::WitnessMissingOrInvalid
831}
832
833impl From<Error> for ErrorResponse {
834 fn from(err: Error) -> ErrorResponse {
835 match err {
836 Error::TokenAlreadySpent => ErrorResponse {
837 code: ErrorCode::TokenAlreadySpent,
838 detail: err.to_string(),
839 },
840 Error::UnsupportedUnit => ErrorResponse {
841 code: ErrorCode::UnsupportedUnit,
842 detail: err.to_string(),
843 },
844 Error::PaymentFailed => ErrorResponse {
845 code: ErrorCode::LightningError,
846 detail: err.to_string(),
847 },
848 Error::RequestAlreadyPaid => ErrorResponse {
849 code: ErrorCode::InvoiceAlreadyPaid,
850 detail: "Invoice already paid.".to_string(),
851 },
852 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
853 ErrorResponse {
854 code: ErrorCode::TransactionUnbalanced,
855 detail: format!(
856 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
857 ),
858 }
859 }
860 Error::MintingDisabled => ErrorResponse {
861 code: ErrorCode::MintingDisabled,
862 detail: err.to_string(),
863 },
864 Error::BlindedMessageAlreadySigned => ErrorResponse {
865 code: ErrorCode::BlindedMessageAlreadySigned,
866 detail: err.to_string(),
867 },
868 Error::InsufficientFunds => ErrorResponse {
869 code: ErrorCode::TransactionUnbalanced,
870 detail: err.to_string(),
871 },
872 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
873 code: ErrorCode::AmountOutofLimitRange,
874 detail: err.to_string(),
875 },
876 Error::ExpiredQuote(_, _) => ErrorResponse {
877 code: ErrorCode::QuoteExpired,
878 detail: err.to_string(),
879 },
880 Error::PendingQuote => ErrorResponse {
881 code: ErrorCode::QuotePending,
882 detail: err.to_string(),
883 },
884 Error::PendingMeltTimeout { .. } => ErrorResponse {
885 code: ErrorCode::QuotePending,
886 detail: err.to_string(),
887 },
888 Error::TokenPending => ErrorResponse {
889 code: ErrorCode::TokenPending,
890 detail: err.to_string(),
891 },
892 Error::ClearAuthRequired => ErrorResponse {
893 code: ErrorCode::ClearAuthRequired,
894 detail: Error::ClearAuthRequired.to_string(),
895 },
896 Error::ClearAuthFailed => ErrorResponse {
897 code: ErrorCode::ClearAuthFailed,
898 detail: Error::ClearAuthFailed.to_string(),
899 },
900 Error::BlindAuthRequired => ErrorResponse {
901 code: ErrorCode::BlindAuthRequired,
902 detail: Error::BlindAuthRequired.to_string(),
903 },
904 Error::BlindAuthFailed => ErrorResponse {
905 code: ErrorCode::BlindAuthFailed,
906 detail: Error::BlindAuthFailed.to_string(),
907 },
908 Error::NUT20(err) => ErrorResponse {
909 code: ErrorCode::WitnessMissingOrInvalid,
910 detail: err.to_string(),
911 },
912 Error::DuplicateInputs => ErrorResponse {
913 code: ErrorCode::DuplicateInputs,
914 detail: err.to_string(),
915 },
916 Error::DuplicateOutputs => ErrorResponse {
917 code: ErrorCode::DuplicateOutputs,
918 detail: err.to_string(),
919 },
920 Error::MultipleUnits => ErrorResponse {
921 code: ErrorCode::MultipleUnits,
922 detail: err.to_string(),
923 },
924 Error::UnitMismatch => ErrorResponse {
925 code: ErrorCode::UnitMismatch,
926 detail: err.to_string(),
927 },
928 Error::UnpaidQuote => ErrorResponse {
929 code: ErrorCode::QuoteNotPaid,
930 detail: Error::UnpaidQuote.to_string(),
931 },
932 Error::NUT11(err) => {
933 let code = map_nut11_error(&err);
934 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
935 Some("P2PK signatures are required but not provided".to_string())
936 } else {
937 None
938 };
939 ErrorResponse {
940 code,
941 detail: match extra {
942 Some(extra) => format!("{err}. {extra}"),
943 None => err.to_string(),
944 },
945 }
946 },
947 Error::DuplicateSignatureError => ErrorResponse {
948 code: ErrorCode::WitnessMissingOrInvalid,
949 detail: err.to_string(),
950 },
951 Error::IssuedQuote => ErrorResponse {
952 code: ErrorCode::TokensAlreadyIssued,
953 detail: err.to_string(),
954 },
955 Error::UnknownKeySet => ErrorResponse {
956 code: ErrorCode::KeysetNotFound,
957 detail: err.to_string(),
958 },
959 Error::InactiveKeyset => ErrorResponse {
960 code: ErrorCode::KeysetInactive,
961 detail: err.to_string(),
962 },
963 Error::ExpiredKeyset => ErrorResponse {
964 code: ErrorCode::KeysetExpired,
965 detail: err.to_string(),
966 },
967 Error::AmountLessNotAllowed => ErrorResponse {
968 code: ErrorCode::AmountlessInvoiceNotSupported,
969 detail: err.to_string(),
970 },
971 Error::IncorrectQuoteAmount => ErrorResponse {
972 code: ErrorCode::IncorrectQuoteAmount,
973 detail: err.to_string(),
974 },
975 Error::PubkeyRequired => ErrorResponse {
976 code: ErrorCode::PubkeyRequired,
977 detail: err.to_string(),
978 },
979 Error::PaidQuote => ErrorResponse {
980 code: ErrorCode::InvoiceAlreadyPaid,
981 detail: err.to_string(),
982 },
983 Error::DuplicatePaymentId => ErrorResponse {
984 code: ErrorCode::InvoiceAlreadyPaid,
985 detail: err.to_string(),
986 },
987 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
989 code: ErrorCode::InvoiceAlreadyPaid,
990 detail: "Invoice already paid or pending".to_string(),
991 },
992
993 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
995 code: ErrorCode::TokenNotVerified,
996 detail: err.to_string(),
997 },
998 Error::DHKE(_) => ErrorResponse {
999 code: ErrorCode::Unknown(50000),
1000 detail: err.to_string(),
1001 },
1002
1003 Error::CouldNotVerifyDleq => ErrorResponse {
1005 code: ErrorCode::TokenNotVerified,
1006 detail: err.to_string(),
1007 },
1008 Error::SignatureMissingOrInvalid => ErrorResponse {
1009 code: ErrorCode::WitnessMissingOrInvalid,
1010 detail: err.to_string(),
1011 },
1012 Error::SigAllUsedInMelt => ErrorResponse {
1013 code: ErrorCode::WitnessMissingOrInvalid,
1014 detail: err.to_string(),
1015 },
1016
1017 Error::AmountKey => ErrorResponse {
1019 code: ErrorCode::KeysetNotFound,
1020 detail: err.to_string(),
1021 },
1022 Error::KeysetUnknown(_) => ErrorResponse {
1023 code: ErrorCode::KeysetNotFound,
1024 detail: err.to_string(),
1025 },
1026 Error::NoActiveKeyset => ErrorResponse {
1027 code: ErrorCode::KeysetInactive,
1028 detail: err.to_string(),
1029 },
1030
1031 Error::UnknownQuote => ErrorResponse {
1033 code: ErrorCode::Unknown(50000),
1034 detail: err.to_string(),
1035 },
1036 Error::MeltingDisabled => ErrorResponse {
1037 code: ErrorCode::MintingDisabled,
1038 detail: err.to_string(),
1039 },
1040 Error::PaymentPending => ErrorResponse {
1041 code: ErrorCode::QuotePending,
1042 detail: err.to_string(),
1043 },
1044 Error::UnknownPaymentState => ErrorResponse {
1045 code: ErrorCode::Unknown(50000),
1046 detail: err.to_string(),
1047 },
1048
1049 Error::SplitValuesGreater => ErrorResponse {
1051 code: ErrorCode::TransactionUnbalanced,
1052 detail: err.to_string(),
1053 },
1054 Error::AmountOverflow => ErrorResponse {
1055 code: ErrorCode::TransactionUnbalanced,
1056 detail: err.to_string(),
1057 },
1058 Error::OverIssue => ErrorResponse {
1059 code: ErrorCode::TransactionUnbalanced,
1060 detail: err.to_string(),
1061 },
1062
1063 Error::InvalidPaymentRequest => ErrorResponse {
1065 code: ErrorCode::Unknown(50000),
1066 detail: err.to_string(),
1067 },
1068 Error::InvoiceAmountUndefined => ErrorResponse {
1069 code: ErrorCode::AmountlessInvoiceNotSupported,
1070 detail: err.to_string(),
1071 },
1072
1073 Error::Internal => ErrorResponse {
1075 code: ErrorCode::Unknown(50000),
1076 detail: err.to_string(),
1077 },
1078 Error::Database(_) => ErrorResponse {
1079 code: ErrorCode::Unknown(50000),
1080 detail: err.to_string(),
1081 },
1082 Error::ConcurrentUpdate => ErrorResponse {
1083 code: ErrorCode::ConcurrentUpdate,
1084 detail: err.to_string(),
1085 },
1086 Error::MaxInputsExceeded { .. } => ErrorResponse {
1087 code: ErrorCode::MaxInputsExceeded,
1088 detail: err.to_string()
1089 },
1090 Error::MaxOutputsExceeded { .. } => ErrorResponse {
1091 code: ErrorCode::MaxOutputsExceeded,
1092 detail: err.to_string()
1093 },
1094 Error::DuplicateQuoteIds => ErrorResponse {
1095 code: ErrorCode::DuplicateQuoteIds,
1096 detail: err.to_string(),
1097 },
1098 Error::BatchSizeExceeded { .. } => ErrorResponse {
1099 code: ErrorCode::BatchSizeExceeded,
1100 detail: err.to_string(),
1101 },
1102 _ => ErrorResponse {
1104 code: ErrorCode::Unknown(50000),
1105 detail: err.to_string(),
1106 },
1107 }
1108 }
1109}
1110
1111#[cfg(feature = "mint")]
1112impl From<crate::database::Error> for Error {
1113 fn from(db_error: crate::database::Error) -> Self {
1114 match db_error {
1115 crate::database::Error::InvalidStateTransition(state) => match state {
1116 crate::state::Error::Pending => Self::TokenPending,
1117 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
1118 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
1119 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
1120 },
1121 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1122 db_error => Self::Database(db_error),
1123 }
1124 }
1125}
1126
1127#[cfg(not(feature = "mint"))]
1128impl From<crate::database::Error> for Error {
1129 fn from(db_error: crate::database::Error) -> Self {
1130 match db_error {
1131 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1132 db_error => Self::Database(db_error),
1133 }
1134 }
1135}
1136
1137impl From<ErrorResponse> for Error {
1138 fn from(err: ErrorResponse) -> Error {
1139 match err.code {
1140 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1142 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1144 ErrorCode::TokenPending => Self::TokenPending,
1145 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1146 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1148 ErrorCode::AmountOutofLimitRange => {
1149 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1150 }
1151 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1152 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1153 ErrorCode::DuplicateQuoteIds => Self::DuplicateQuoteIds,
1154 ErrorCode::BatchSizeExceeded => Self::BatchSizeExceeded { actual: 0, max: 0 },
1155 ErrorCode::MultipleUnits => Self::MultipleUnits,
1156 ErrorCode::UnitMismatch => Self::UnitMismatch,
1157 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1158 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1159 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1160 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1162 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1163 ErrorCode::KeysetExpired => Self::ExpiredKeyset,
1164 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1166 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1167 ErrorCode::MintingDisabled => Self::MintingDisabled,
1168 ErrorCode::LightningError => Self::PaymentFailed,
1169 ErrorCode::QuotePending => Self::PendingQuote,
1170 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1171 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1172 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1173 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1174 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1176 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1177 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1179 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1180 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1181 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1182 _ => Self::UnknownErrorResponse(err.to_string()),
1183 }
1184 }
1185}
1186
1187#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1189#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
1190pub enum ErrorCode {
1191 TokenNotVerified,
1194
1195 TokenAlreadySpent,
1198 TokenPending,
1200 BlindedMessageAlreadySigned,
1202 OutputsPending,
1204 TransactionUnbalanced,
1206 AmountOutofLimitRange,
1208 DuplicateInputs,
1210 DuplicateOutputs,
1212 MultipleUnits,
1214 UnitMismatch,
1216 AmountlessInvoiceNotSupported,
1218 IncorrectQuoteAmount,
1220 UnsupportedUnit,
1222 MaxInputsExceeded,
1224 MaxOutputsExceeded,
1226 DuplicateQuoteIds,
1228 BatchSizeExceeded,
1230 KeysetNotFound,
1233 KeysetInactive,
1235 KeysetExpired,
1237
1238 QuoteNotPaid,
1241 TokensAlreadyIssued,
1243 MintingDisabled,
1245 LightningError,
1247 QuotePending,
1249 InvoiceAlreadyPaid,
1251 QuoteExpired,
1253 WitnessMissingOrInvalid,
1255 PubkeyRequired,
1257
1258 ClearAuthRequired,
1261 ClearAuthFailed,
1263
1264 BlindAuthRequired,
1267 BlindAuthFailed,
1269 BatMintMaxExceeded,
1271 BatRateLimitExceeded,
1273
1274 ConcurrentUpdate,
1276
1277 Unknown(u16),
1279}
1280
1281impl ErrorCode {
1282 pub fn from_code(code: u16) -> Self {
1284 match code {
1285 10001 => Self::TokenNotVerified,
1287 11001 => Self::TokenAlreadySpent,
1289 11002 => Self::TokenPending,
1290 11003 => Self::BlindedMessageAlreadySigned,
1291 11004 => Self::OutputsPending,
1292 11005 => Self::TransactionUnbalanced,
1293 11006 => Self::AmountOutofLimitRange,
1294 11007 => Self::DuplicateInputs,
1295 11008 => Self::DuplicateOutputs,
1296 11009 => Self::MultipleUnits,
1297 11010 => Self::UnitMismatch,
1298 11011 => Self::AmountlessInvoiceNotSupported,
1299 11012 => Self::IncorrectQuoteAmount,
1300 11013 => Self::UnsupportedUnit,
1301 11014 => Self::MaxInputsExceeded,
1302 11015 => Self::MaxOutputsExceeded,
1303 11016 => Self::DuplicateQuoteIds,
1304 11017 => Self::BatchSizeExceeded,
1305 12001 => Self::KeysetNotFound,
1307 12002 => Self::KeysetInactive,
1308 12003 => Self::KeysetExpired,
1309 20001 => Self::QuoteNotPaid,
1311 20002 => Self::TokensAlreadyIssued,
1312 20003 => Self::MintingDisabled,
1313 20004 => Self::LightningError,
1314 20005 => Self::QuotePending,
1315 20006 => Self::InvoiceAlreadyPaid,
1316 20007 => Self::QuoteExpired,
1317 20008 => Self::WitnessMissingOrInvalid,
1318 20009 => Self::PubkeyRequired,
1319 30001 => Self::ClearAuthRequired,
1321 30002 => Self::ClearAuthFailed,
1322 31001 => Self::BlindAuthRequired,
1324 31002 => Self::BlindAuthFailed,
1325 31003 => Self::BatMintMaxExceeded,
1326 31004 => Self::BatRateLimitExceeded,
1327 _ => Self::Unknown(code),
1328 }
1329 }
1330
1331 pub fn to_code(&self) -> u16 {
1333 match self {
1334 Self::TokenNotVerified => 10001,
1336 Self::TokenAlreadySpent => 11001,
1338 Self::TokenPending => 11002,
1339 Self::BlindedMessageAlreadySigned => 11003,
1340 Self::OutputsPending => 11004,
1341 Self::TransactionUnbalanced => 11005,
1342 Self::AmountOutofLimitRange => 11006,
1343 Self::DuplicateInputs => 11007,
1344 Self::DuplicateOutputs => 11008,
1345 Self::MultipleUnits => 11009,
1346 Self::UnitMismatch => 11010,
1347 Self::AmountlessInvoiceNotSupported => 11011,
1348 Self::IncorrectQuoteAmount => 11012,
1349 Self::UnsupportedUnit => 11013,
1350 Self::MaxInputsExceeded => 11014,
1351 Self::MaxOutputsExceeded => 11015,
1352 Self::DuplicateQuoteIds => 11016,
1353 Self::BatchSizeExceeded => 11017,
1354 Self::KeysetNotFound => 12001,
1356 Self::KeysetInactive => 12002,
1357 Self::KeysetExpired => 12003,
1358 Self::QuoteNotPaid => 20001,
1360 Self::TokensAlreadyIssued => 20002,
1361 Self::MintingDisabled => 20003,
1362 Self::LightningError => 20004,
1363 Self::QuotePending => 20005,
1364 Self::InvoiceAlreadyPaid => 20006,
1365 Self::QuoteExpired => 20007,
1366 Self::WitnessMissingOrInvalid => 20008,
1367 Self::PubkeyRequired => 20009,
1368 Self::ClearAuthRequired => 30001,
1370 Self::ClearAuthFailed => 30002,
1371 Self::BlindAuthRequired => 31001,
1373 Self::BlindAuthFailed => 31002,
1374 Self::BatMintMaxExceeded => 31003,
1375 Self::BatRateLimitExceeded => 31004,
1376 Self::ConcurrentUpdate => 50000,
1377 Self::Unknown(code) => *code,
1378 }
1379 }
1380}
1381
1382impl Serialize for ErrorCode {
1383 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1384 where
1385 S: Serializer,
1386 {
1387 serializer.serialize_u16(self.to_code())
1388 }
1389}
1390
1391impl<'de> Deserialize<'de> for ErrorCode {
1392 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1393 where
1394 D: Deserializer<'de>,
1395 {
1396 let code = u16::deserialize(deserializer)?;
1397
1398 Ok(ErrorCode::from_code(code))
1399 }
1400}
1401
1402impl fmt::Display for ErrorCode {
1403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1404 write!(f, "{}", self.to_code())
1405 }
1406}