1use std::array::TryFromSliceError;
4use std::fmt;
5
6use cashu::{CurrencyUnit, PaymentMethod};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_json::Value;
9use thiserror::Error;
10
11use crate::nuts::Id;
12use crate::util::hex;
13#[cfg(feature = "wallet")]
14use crate::wallet::WalletKey;
15use crate::Amount;
16
17#[derive(Debug, Error)]
19pub enum Error {
20 #[error("No Key for Amount")]
22 AmountKey,
23 #[error("Keyset id not known: `{0}`")]
25 KeysetUnknown(Id),
26 #[error("Unit unsupported")]
28 UnsupportedUnit,
29 #[error("Payment failed")]
31 PaymentFailed,
32 #[error("Payment pending")]
34 PaymentPending,
35 #[error("Request already paid")]
37 RequestAlreadyPaid,
38 #[error("Invalid payment request")]
40 InvalidPaymentRequest,
41 #[error("Invoice Amount undefined")]
43 InvoiceAmountUndefined,
44 #[error("Split Values must be less then or equal to amount")]
46 SplitValuesGreater,
47 #[error("Amount Overflow")]
49 AmountOverflow,
50 #[error("Cannot issue more than amount paid")]
52 OverIssue,
53 #[error("Signature missing or invalid")]
55 SignatureMissingOrInvalid,
56 #[error("Amount Less Invoice is not allowed")]
58 AmountLessNotAllowed,
59 #[error("Multi-Part Internal Melt Quotes are not supported")]
61 InternalMultiPartMeltQuote,
62 #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
64 MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
65 #[error("Clear Auth Required")]
67 ClearAuthRequired,
68 #[error("Blind Auth Required")]
70 BlindAuthRequired,
71 #[error("Clear Auth Failed")]
73 ClearAuthFailed,
74 #[error("Blind Auth Failed")]
76 BlindAuthFailed,
77 #[error("Auth settings undefined")]
79 AuthSettingsUndefined,
80 #[error("Mint time outside of tolerance")]
82 MintTimeExceedsTolerance,
83 #[error("Insufficient blind auth tokens, must reauth")]
85 InsufficientBlindAuthTokens,
86 #[error("Auth localstore undefined")]
88 AuthLocalstoreUndefined,
89 #[error("Wallet cat not set")]
91 CatNotSet,
92 #[error("Could not get mint info")]
94 CouldNotGetMintInfo,
95 #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
97 AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
98 #[error("Payment id seen for mint")]
100 DuplicatePaymentId,
101 #[error("Pubkey required")]
103 PubkeyRequired,
104 #[error("Invalid payment method")]
106 InvalidPaymentMethod,
107 #[error("Amount undefined")]
109 AmountUndefined,
110 #[error("Payment method unsupported")]
112 UnsupportedPaymentMethod,
113 #[error("Payment method required")]
115 PaymentMethodRequired,
116 #[error("Could not parse bolt12")]
118 Bolt12parse,
119 #[error("Could not parse invoice")]
121 InvalidInvoice,
122
123 #[error("Failed to parse BIP353 address: {0}")]
125 Bip353Parse(String),
126
127 #[error("Operation timeout")]
129 Timeout,
130
131 #[error("Failed to resolve BIP353 address: {0}")]
133 Bip353Resolve(String),
134 #[error("No Lightning offer found in BIP353 payment instructions")]
136 Bip353NoLightningOffer,
137
138 #[error("Failed to parse Lightning address: {0}")]
140 LightningAddressParse(String),
141 #[error("Failed to request invoice from Lightning address service: {0}")]
143 LightningAddressRequest(String),
144
145 #[error("Internal send error: {0}")]
147 SendError(String),
148
149 #[error("Internal receive error: {0}")]
151 RecvError(String),
152
153 #[error("Minting is disabled")]
156 MintingDisabled,
157 #[error("Unknown quote")]
159 UnknownQuote,
160 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
162 ExpiredQuote(u64, u64),
163 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
165 AmountOutofLimitRange(Amount, Amount, Amount),
166 #[error("Quote not paid")]
168 UnpaidQuote,
169 #[error("Quote pending")]
171 PendingQuote,
172 #[error("Quote already issued")]
174 IssuedQuote,
175 #[error("Quote is already paid")]
177 PaidQuote,
178 #[error("Payment state is unknown")]
180 UnknownPaymentState,
181 #[error("Melting is disabled")]
183 MeltingDisabled,
184 #[error("Unknown Keyset")]
186 UnknownKeySet,
187 #[error("Blinded Message is already signed")]
189 BlindedMessageAlreadySigned,
190 #[error("Inactive Keyset")]
192 InactiveKeyset,
193 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
195 TransactionUnbalanced(u64, u64, u64),
196 #[error("Duplicate Inputs")]
198 DuplicateInputs,
199 #[error("Duplicate outputs")]
201 DuplicateOutputs,
202 #[error("Maximum inputs exceeded: {actual} provided, max {max}")]
204 MaxInputsExceeded {
205 actual: usize,
207 max: usize,
209 },
210 #[error("Maximum outputs exceeded: {actual} provided, max {max}")]
212 MaxOutputsExceeded {
213 actual: usize,
215 max: usize,
217 },
218 #[error("Proof content too large: {actual} bytes, max {max}")]
220 ProofContentTooLarge {
221 actual: usize,
223 max: usize,
225 },
226 #[error("Request field '{field}' too large: {actual} bytes, max {max}")]
228 RequestFieldTooLarge {
229 field: String,
231 actual: usize,
233 max: usize,
235 },
236 #[error("Cannot have multiple units")]
238 MultipleUnits,
239 #[error("Input unit must match output")]
241 UnitMismatch,
242 #[error("Sig all cannot be used in melt")]
244 SigAllUsedInMelt,
245 #[error("Token Already Spent")]
247 TokenAlreadySpent,
248 #[error("Token Pending")]
250 TokenPending,
251 #[error("Internal Error")]
253 Internal,
254 #[error("Oidc client not set")]
256 OidcNotSet,
257
258 #[error("P2PK condition not met `{0}`")]
261 P2PKConditionsNotMet(String),
262 #[error("Duplicate signature from same pubkey in P2PK")]
264 DuplicateSignatureError,
265 #[error("Spending condition locktime not provided")]
267 LocktimeNotProvided,
268 #[error("Invalid spending conditions: `{0}`")]
270 InvalidSpendConditions(String),
271 #[error("Incorrect wallet: `{0}`")]
273 IncorrectWallet(String),
274 #[error("Unknown wallet: `{0}`")]
276 #[cfg(feature = "wallet")]
277 UnknownWallet(WalletKey),
278 #[error("Max fee exceeded")]
280 MaxFeeExceeded,
281 #[error("Url path segments could not be joined")]
283 UrlPathSegments,
284 #[error("Unknown error response: `{0}`")]
286 UnknownErrorResponse(String),
287 #[error("Could not verify DLEQ proof")]
289 CouldNotVerifyDleq,
290 #[error("Dleq proof not provided for signature")]
292 DleqProofNotProvided,
293 #[error("Token does not match wallet mint")]
296 IncorrectMint,
297 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
299 MultiMintTokenNotSupported,
300 #[error("Preimage not provided")]
302 PreimageNotProvided,
303
304 #[error("Unknown mint: {mint_url}")]
306 UnknownMint {
307 mint_url: String,
309 },
310 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
312 TransferTimeout {
313 source_mint: String,
315 target_mint: String,
317 amount: Amount,
319 },
320 #[error("Insufficient funds")]
322 InsufficientFunds,
323 #[error("Unexpected proof state")]
325 UnexpectedProofState,
326 #[error("No active keyset")]
328 NoActiveKeyset,
329 #[error("Incorrect quote amount")]
331 IncorrectQuoteAmount,
332 #[error("Invoice Description not supported")]
334 InvoiceDescriptionUnsupported,
335 #[error("Invalid transaction direction")]
337 InvalidTransactionDirection,
338 #[error("Invalid transaction id")]
340 InvalidTransactionId,
341 #[error("Transaction not found")]
343 TransactionNotFound,
344 #[error("Invalid operation kind")]
346 InvalidOperationKind,
347 #[error("Invalid operation state")]
349 InvalidOperationState,
350 #[error("Operation not found")]
352 OperationNotFound,
353 #[error("Invalid KV store key or namespace: {0}")]
355 KVStoreInvalidKey(String),
356 #[error("Concurrent update detected")]
358 ConcurrentUpdate,
359 #[error("Invalid mint response: {0}")]
361 InvalidMintResponse(String),
362 #[error("Subscription error: {0}")]
364 SubscriptionError(String),
365 #[error("`{0}`")]
367 Custom(String),
368
369 #[error(transparent)]
372 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
373 #[error(transparent)]
375 Bip32(#[from] bitcoin::bip32::Error),
376 #[error(transparent)]
378 ParseInt(#[from] std::num::ParseIntError),
379 #[error(transparent)]
381 UrlParseError(#[from] url::ParseError),
382 #[error(transparent)]
384 Utf8ParseError(#[from] std::string::FromUtf8Error),
385 #[error(transparent)]
387 SerdeJsonError(#[from] serde_json::Error),
388 #[error(transparent)]
390 Base64Error(#[from] bitcoin::base64::DecodeError),
391 #[error(transparent)]
393 HexError(#[from] hex::Error),
394 #[error("Http transport error {0:?}: {1}")]
396 HttpError(Option<u16>, String),
397 #[cfg(feature = "mint")]
399 #[error(transparent)]
400 Uuid(#[from] uuid::Error),
401 #[error(transparent)]
404 CashuUrl(#[from] crate::mint_url::Error),
405 #[error(transparent)]
407 Secret(#[from] crate::secret::Error),
408 #[error(transparent)]
410 AmountError(#[from] crate::amount::Error),
411 #[error(transparent)]
413 DHKE(#[from] crate::dhke::Error),
414 #[error(transparent)]
416 NUT00(#[from] crate::nuts::nut00::Error),
417 #[error(transparent)]
419 NUT01(#[from] crate::nuts::nut01::Error),
420 #[error(transparent)]
422 NUT02(#[from] crate::nuts::nut02::Error),
423 #[error(transparent)]
425 NUT03(#[from] crate::nuts::nut03::Error),
426 #[error(transparent)]
428 NUT04(#[from] crate::nuts::nut04::Error),
429 #[error(transparent)]
431 NUT05(#[from] crate::nuts::nut05::Error),
432 #[error(transparent)]
434 NUT11(#[from] crate::nuts::nut11::Error),
435 #[error(transparent)]
437 NUT12(#[from] crate::nuts::nut12::Error),
438 #[error(transparent)]
440 #[cfg(feature = "wallet")]
441 NUT13(#[from] crate::nuts::nut13::Error),
442 #[error(transparent)]
444 NUT14(#[from] crate::nuts::nut14::Error),
445 #[error(transparent)]
447 NUT18(#[from] crate::nuts::nut18::Error),
448 #[error(transparent)]
450 NUT20(#[from] crate::nuts::nut20::Error),
451 #[error(transparent)]
453 NUT21(#[from] crate::nuts::nut21::Error),
454 #[error(transparent)]
456 NUT22(#[from] crate::nuts::nut22::Error),
457 #[error(transparent)]
459 NUT23(#[from] crate::nuts::nut23::Error),
460 #[error(transparent)]
462 #[cfg(feature = "mint")]
463 QuoteId(#[from] crate::quote_id::QuoteIdError),
464 #[error(transparent)]
466 TryFromSliceError(#[from] TryFromSliceError),
467 #[error(transparent)]
469 Database(crate::database::Error),
470 #[error(transparent)]
472 #[cfg(feature = "mint")]
473 Payment(#[from] crate::payment::Error),
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn test_is_definitive_failure() {
482 assert!(Error::AmountOverflow.is_definitive_failure());
484 assert!(Error::TokenAlreadySpent.is_definitive_failure());
485 assert!(Error::MintingDisabled.is_definitive_failure());
486
487 assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
489 assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
490 assert!(
491 Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
492 );
493
494 assert!(!Error::Timeout.is_definitive_failure());
496 assert!(!Error::Internal.is_definitive_failure());
497 assert!(!Error::ConcurrentUpdate.is_definitive_failure());
498
499 assert!(
501 !Error::HttpError(Some(500), "Internal Server Error".to_string())
502 .is_definitive_failure()
503 );
504 assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
505 assert!(
506 !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
507 );
508
509 assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
511 }
512}
513
514impl Error {
515 pub fn is_definitive_failure(&self) -> bool {
524 match self {
525 Self::AmountKey
527 | Self::KeysetUnknown(_)
528 | Self::UnsupportedUnit
529 | Self::InvoiceAmountUndefined
530 | Self::SplitValuesGreater
531 | Self::AmountOverflow
532 | Self::OverIssue
533 | Self::SignatureMissingOrInvalid
534 | Self::AmountLessNotAllowed
535 | Self::InternalMultiPartMeltQuote
536 | Self::MppUnitMethodNotSupported(_, _)
537 | Self::AmountlessInvoiceNotSupported(_, _)
538 | Self::DuplicatePaymentId
539 | Self::PubkeyRequired
540 | Self::InvalidPaymentMethod
541 | Self::UnsupportedPaymentMethod
542 | Self::InvalidInvoice
543 | Self::MintingDisabled
544 | Self::UnknownQuote
545 | Self::ExpiredQuote(_, _)
546 | Self::AmountOutofLimitRange(_, _, _)
547 | Self::UnpaidQuote
548 | Self::PendingQuote
549 | Self::IssuedQuote
550 | Self::PaidQuote
551 | Self::MeltingDisabled
552 | Self::UnknownKeySet
553 | Self::BlindedMessageAlreadySigned
554 | Self::InactiveKeyset
555 | Self::TransactionUnbalanced(_, _, _)
556 | Self::DuplicateInputs
557 | Self::DuplicateOutputs
558 | Self::MultipleUnits
559 | Self::UnitMismatch
560 | Self::SigAllUsedInMelt
561 | Self::TokenAlreadySpent
562 | Self::TokenPending
563 | Self::P2PKConditionsNotMet(_)
564 | Self::DuplicateSignatureError
565 | Self::LocktimeNotProvided
566 | Self::InvalidSpendConditions(_)
567 | Self::IncorrectWallet(_)
568 | Self::MaxFeeExceeded
569 | Self::DleqProofNotProvided
570 | Self::IncorrectMint
571 | Self::MultiMintTokenNotSupported
572 | Self::PreimageNotProvided
573 | Self::UnknownMint { .. }
574 | Self::UnexpectedProofState
575 | Self::NoActiveKeyset
576 | Self::IncorrectQuoteAmount
577 | Self::InvoiceDescriptionUnsupported
578 | Self::InvalidTransactionDirection
579 | Self::InvalidTransactionId
580 | Self::InvalidOperationKind
581 | Self::InvalidOperationState
582 | Self::OperationNotFound
583 | Self::KVStoreInvalidKey(_) => true,
584
585 Self::HttpError(Some(status), _) => {
587 (400..500).contains(status)
590 }
591
592 Self::Timeout
594 | Self::Internal
595 | Self::UnknownPaymentState
596 | Self::CouldNotGetMintInfo
597 | Self::UnknownErrorResponse(_)
598 | Self::InvalidMintResponse(_)
599 | Self::ConcurrentUpdate
600 | Self::SendError(_)
601 | Self::RecvError(_)
602 | Self::TransferTimeout { .. } => false,
603
604 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
608 | Self::Custom(_) => false,
609
610 Self::ClearAuthRequired
612 | Self::BlindAuthRequired
613 | Self::ClearAuthFailed
614 | Self::BlindAuthFailed
615 | Self::InsufficientBlindAuthTokens
616 | Self::AuthSettingsUndefined
617 | Self::AuthLocalstoreUndefined
618 | Self::OidcNotSet => true,
619
620 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
624 Self::UrlParseError(_) => true,
625 Self::Utf8ParseError(_) => true,
626 Self::Base64Error(_) => true,
627 Self::HexError(_) => true,
628 #[cfg(feature = "mint")]
629 Self::Uuid(_) => true,
630 Self::CashuUrl(_) => true,
631 Self::Secret(_) => true,
632 Self::AmountError(_) => true,
633 Self::DHKE(_) => true, Self::NUT00(_) => true,
635 Self::NUT01(_) => true,
636 Self::NUT02(_) => true,
637 Self::NUT03(_) => true,
638 Self::NUT04(_) => true,
639 Self::NUT05(_) => true,
640 Self::NUT11(_) => true,
641 Self::NUT12(_) => true,
642 #[cfg(feature = "wallet")]
643 Self::NUT13(_) => true,
644 Self::NUT14(_) => true,
645 Self::NUT18(_) => true,
646 Self::NUT20(_) => true,
647 Self::NUT21(_) => true,
648 Self::NUT22(_) => true,
649 Self::NUT23(_) => true,
650 #[cfg(feature = "mint")]
651 Self::QuoteId(_) => true,
652 Self::TryFromSliceError(_) => true,
653 #[cfg(feature = "mint")]
654 Self::Payment(_) => false, _ => false,
658 }
659 }
660}
661
662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
666#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
667pub struct ErrorResponse {
668 pub code: ErrorCode,
670 #[serde(default)]
672 pub detail: String,
673}
674
675impl fmt::Display for ErrorResponse {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 write!(f, "code: {}, detail: {}", self.code, self.detail)
678 }
679}
680
681impl ErrorResponse {
682 pub fn new(code: ErrorCode, detail: String) -> Self {
684 Self { code, detail }
685 }
686
687 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
689 let value: Value = serde_json::from_str(json)?;
690
691 Self::from_value(value)
692 }
693
694 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
696 match serde_json::from_value::<ErrorResponse>(value.clone()) {
697 Ok(res) => Ok(res),
698 Err(_) => Ok(Self {
699 code: ErrorCode::Unknown(999),
700 detail: value.to_string(),
701 }),
702 }
703 }
704}
705
706fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
709 ErrorCode::WitnessMissingOrInvalid
711}
712
713impl From<Error> for ErrorResponse {
714 fn from(err: Error) -> ErrorResponse {
715 match err {
716 Error::TokenAlreadySpent => ErrorResponse {
717 code: ErrorCode::TokenAlreadySpent,
718 detail: err.to_string(),
719 },
720 Error::UnsupportedUnit => ErrorResponse {
721 code: ErrorCode::UnsupportedUnit,
722 detail: err.to_string(),
723 },
724 Error::PaymentFailed => ErrorResponse {
725 code: ErrorCode::LightningError,
726 detail: err.to_string(),
727 },
728 Error::RequestAlreadyPaid => ErrorResponse {
729 code: ErrorCode::InvoiceAlreadyPaid,
730 detail: "Invoice already paid.".to_string(),
731 },
732 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
733 ErrorResponse {
734 code: ErrorCode::TransactionUnbalanced,
735 detail: format!(
736 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
737 ),
738 }
739 }
740 Error::MintingDisabled => ErrorResponse {
741 code: ErrorCode::MintingDisabled,
742 detail: err.to_string(),
743 },
744 Error::BlindedMessageAlreadySigned => ErrorResponse {
745 code: ErrorCode::BlindedMessageAlreadySigned,
746 detail: err.to_string(),
747 },
748 Error::InsufficientFunds => ErrorResponse {
749 code: ErrorCode::TransactionUnbalanced,
750 detail: err.to_string(),
751 },
752 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
753 code: ErrorCode::AmountOutofLimitRange,
754 detail: err.to_string(),
755 },
756 Error::ExpiredQuote(_, _) => ErrorResponse {
757 code: ErrorCode::QuoteExpired,
758 detail: err.to_string(),
759 },
760 Error::PendingQuote => ErrorResponse {
761 code: ErrorCode::QuotePending,
762 detail: err.to_string(),
763 },
764 Error::TokenPending => ErrorResponse {
765 code: ErrorCode::TokenPending,
766 detail: err.to_string(),
767 },
768 Error::ClearAuthRequired => ErrorResponse {
769 code: ErrorCode::ClearAuthRequired,
770 detail: Error::ClearAuthRequired.to_string(),
771 },
772 Error::ClearAuthFailed => ErrorResponse {
773 code: ErrorCode::ClearAuthFailed,
774 detail: Error::ClearAuthFailed.to_string(),
775 },
776 Error::BlindAuthRequired => ErrorResponse {
777 code: ErrorCode::BlindAuthRequired,
778 detail: Error::BlindAuthRequired.to_string(),
779 },
780 Error::BlindAuthFailed => ErrorResponse {
781 code: ErrorCode::BlindAuthFailed,
782 detail: Error::BlindAuthFailed.to_string(),
783 },
784 Error::NUT20(err) => ErrorResponse {
785 code: ErrorCode::WitnessMissingOrInvalid,
786 detail: err.to_string(),
787 },
788 Error::DuplicateInputs => ErrorResponse {
789 code: ErrorCode::DuplicateInputs,
790 detail: err.to_string(),
791 },
792 Error::DuplicateOutputs => ErrorResponse {
793 code: ErrorCode::DuplicateOutputs,
794 detail: err.to_string(),
795 },
796 Error::MultipleUnits => ErrorResponse {
797 code: ErrorCode::MultipleUnits,
798 detail: err.to_string(),
799 },
800 Error::UnitMismatch => ErrorResponse {
801 code: ErrorCode::UnitMismatch,
802 detail: err.to_string(),
803 },
804 Error::UnpaidQuote => ErrorResponse {
805 code: ErrorCode::QuoteNotPaid,
806 detail: Error::UnpaidQuote.to_string(),
807 },
808 Error::NUT11(err) => {
809 let code = map_nut11_error(&err);
810 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
811 Some("P2PK signatures are required but not provided".to_string())
812 } else {
813 None
814 };
815 ErrorResponse {
816 code,
817 detail: match extra {
818 Some(extra) => format!("{err}. {extra}"),
819 None => err.to_string(),
820 },
821 }
822 },
823 Error::DuplicateSignatureError => ErrorResponse {
824 code: ErrorCode::WitnessMissingOrInvalid,
825 detail: err.to_string(),
826 },
827 Error::IssuedQuote => ErrorResponse {
828 code: ErrorCode::TokensAlreadyIssued,
829 detail: err.to_string(),
830 },
831 Error::UnknownKeySet => ErrorResponse {
832 code: ErrorCode::KeysetNotFound,
833 detail: err.to_string(),
834 },
835 Error::InactiveKeyset => ErrorResponse {
836 code: ErrorCode::KeysetInactive,
837 detail: err.to_string(),
838 },
839 Error::AmountLessNotAllowed => ErrorResponse {
840 code: ErrorCode::AmountlessInvoiceNotSupported,
841 detail: err.to_string(),
842 },
843 Error::IncorrectQuoteAmount => ErrorResponse {
844 code: ErrorCode::IncorrectQuoteAmount,
845 detail: err.to_string(),
846 },
847 Error::PubkeyRequired => ErrorResponse {
848 code: ErrorCode::PubkeyRequired,
849 detail: err.to_string(),
850 },
851 Error::PaidQuote => ErrorResponse {
852 code: ErrorCode::InvoiceAlreadyPaid,
853 detail: err.to_string(),
854 },
855 Error::DuplicatePaymentId => ErrorResponse {
856 code: ErrorCode::InvoiceAlreadyPaid,
857 detail: err.to_string(),
858 },
859 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
861 code: ErrorCode::InvoiceAlreadyPaid,
862 detail: "Invoice already paid or pending".to_string(),
863 },
864
865 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
867 code: ErrorCode::TokenNotVerified,
868 detail: err.to_string(),
869 },
870 Error::DHKE(_) => ErrorResponse {
871 code: ErrorCode::Unknown(50000),
872 detail: err.to_string(),
873 },
874
875 Error::CouldNotVerifyDleq => ErrorResponse {
877 code: ErrorCode::TokenNotVerified,
878 detail: err.to_string(),
879 },
880 Error::SignatureMissingOrInvalid => ErrorResponse {
881 code: ErrorCode::WitnessMissingOrInvalid,
882 detail: err.to_string(),
883 },
884 Error::SigAllUsedInMelt => ErrorResponse {
885 code: ErrorCode::WitnessMissingOrInvalid,
886 detail: err.to_string(),
887 },
888
889 Error::AmountKey => ErrorResponse {
891 code: ErrorCode::KeysetNotFound,
892 detail: err.to_string(),
893 },
894 Error::KeysetUnknown(_) => ErrorResponse {
895 code: ErrorCode::KeysetNotFound,
896 detail: err.to_string(),
897 },
898 Error::NoActiveKeyset => ErrorResponse {
899 code: ErrorCode::KeysetInactive,
900 detail: err.to_string(),
901 },
902
903 Error::UnknownQuote => ErrorResponse {
905 code: ErrorCode::Unknown(50000),
906 detail: err.to_string(),
907 },
908 Error::MeltingDisabled => ErrorResponse {
909 code: ErrorCode::MintingDisabled,
910 detail: err.to_string(),
911 },
912 Error::PaymentPending => ErrorResponse {
913 code: ErrorCode::QuotePending,
914 detail: err.to_string(),
915 },
916 Error::UnknownPaymentState => ErrorResponse {
917 code: ErrorCode::Unknown(50000),
918 detail: err.to_string(),
919 },
920
921 Error::SplitValuesGreater => ErrorResponse {
923 code: ErrorCode::TransactionUnbalanced,
924 detail: err.to_string(),
925 },
926 Error::AmountOverflow => ErrorResponse {
927 code: ErrorCode::TransactionUnbalanced,
928 detail: err.to_string(),
929 },
930 Error::OverIssue => ErrorResponse {
931 code: ErrorCode::TransactionUnbalanced,
932 detail: err.to_string(),
933 },
934
935 Error::InvalidPaymentRequest => ErrorResponse {
937 code: ErrorCode::Unknown(50000),
938 detail: err.to_string(),
939 },
940 Error::InvoiceAmountUndefined => ErrorResponse {
941 code: ErrorCode::AmountlessInvoiceNotSupported,
942 detail: err.to_string(),
943 },
944
945 Error::Internal => ErrorResponse {
947 code: ErrorCode::Unknown(50000),
948 detail: err.to_string(),
949 },
950 Error::Database(_) => ErrorResponse {
951 code: ErrorCode::Unknown(50000),
952 detail: err.to_string(),
953 },
954 Error::ConcurrentUpdate => ErrorResponse {
955 code: ErrorCode::ConcurrentUpdate,
956 detail: err.to_string(),
957 },
958 Error::MaxInputsExceeded { .. } => ErrorResponse {
959 code: ErrorCode::MaxInputsExceeded,
960 detail: err.to_string()
961 },
962 Error::MaxOutputsExceeded { .. } => ErrorResponse {
963 code: ErrorCode::MaxOutputsExceeded,
964 detail: err.to_string()
965 },
966 _ => ErrorResponse {
968 code: ErrorCode::Unknown(50000),
969 detail: err.to_string(),
970 },
971 }
972 }
973}
974
975#[cfg(feature = "mint")]
976impl From<crate::database::Error> for Error {
977 fn from(db_error: crate::database::Error) -> Self {
978 match db_error {
979 crate::database::Error::InvalidStateTransition(state) => match state {
980 crate::state::Error::Pending => Self::TokenPending,
981 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
982 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
983 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
984 },
985 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
986 db_error => Self::Database(db_error),
987 }
988 }
989}
990
991#[cfg(not(feature = "mint"))]
992impl From<crate::database::Error> for Error {
993 fn from(db_error: crate::database::Error) -> Self {
994 match db_error {
995 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
996 db_error => Self::Database(db_error),
997 }
998 }
999}
1000
1001impl From<ErrorResponse> for Error {
1002 fn from(err: ErrorResponse) -> Error {
1003 match err.code {
1004 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1006 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1008 ErrorCode::TokenPending => Self::TokenPending,
1009 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1010 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1012 ErrorCode::AmountOutofLimitRange => {
1013 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1014 }
1015 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1016 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1017 ErrorCode::MultipleUnits => Self::MultipleUnits,
1018 ErrorCode::UnitMismatch => Self::UnitMismatch,
1019 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1020 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1021 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1022 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1024 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1025 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1027 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1028 ErrorCode::MintingDisabled => Self::MintingDisabled,
1029 ErrorCode::LightningError => Self::PaymentFailed,
1030 ErrorCode::QuotePending => Self::PendingQuote,
1031 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1032 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1033 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1034 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1035 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1037 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1038 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1040 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1041 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1042 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1043 _ => Self::UnknownErrorResponse(err.to_string()),
1044 }
1045 }
1046}
1047
1048#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1050#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
1051pub enum ErrorCode {
1052 TokenNotVerified,
1055
1056 TokenAlreadySpent,
1059 TokenPending,
1061 BlindedMessageAlreadySigned,
1063 OutputsPending,
1065 TransactionUnbalanced,
1067 AmountOutofLimitRange,
1069 DuplicateInputs,
1071 DuplicateOutputs,
1073 MultipleUnits,
1075 UnitMismatch,
1077 AmountlessInvoiceNotSupported,
1079 IncorrectQuoteAmount,
1081 UnsupportedUnit,
1083 MaxInputsExceeded,
1085 MaxOutputsExceeded,
1087 KeysetNotFound,
1090 KeysetInactive,
1092
1093 QuoteNotPaid,
1096 TokensAlreadyIssued,
1098 MintingDisabled,
1100 LightningError,
1102 QuotePending,
1104 InvoiceAlreadyPaid,
1106 QuoteExpired,
1108 WitnessMissingOrInvalid,
1110 PubkeyRequired,
1112
1113 ClearAuthRequired,
1116 ClearAuthFailed,
1118
1119 BlindAuthRequired,
1122 BlindAuthFailed,
1124 BatMintMaxExceeded,
1126 BatRateLimitExceeded,
1128
1129 ConcurrentUpdate,
1131
1132 Unknown(u16),
1134}
1135
1136impl ErrorCode {
1137 pub fn from_code(code: u16) -> Self {
1139 match code {
1140 10001 => Self::TokenNotVerified,
1142 11001 => Self::TokenAlreadySpent,
1144 11002 => Self::TokenPending,
1145 11003 => Self::BlindedMessageAlreadySigned,
1146 11004 => Self::OutputsPending,
1147 11005 => Self::TransactionUnbalanced,
1148 11006 => Self::AmountOutofLimitRange,
1149 11007 => Self::DuplicateInputs,
1150 11008 => Self::DuplicateOutputs,
1151 11009 => Self::MultipleUnits,
1152 11010 => Self::UnitMismatch,
1153 11011 => Self::AmountlessInvoiceNotSupported,
1154 11012 => Self::IncorrectQuoteAmount,
1155 11013 => Self::UnsupportedUnit,
1156 11014 => Self::MaxInputsExceeded,
1157 11015 => Self::MaxOutputsExceeded,
1158 12001 => Self::KeysetNotFound,
1160 12002 => Self::KeysetInactive,
1161 20001 => Self::QuoteNotPaid,
1163 20002 => Self::TokensAlreadyIssued,
1164 20003 => Self::MintingDisabled,
1165 20004 => Self::LightningError,
1166 20005 => Self::QuotePending,
1167 20006 => Self::InvoiceAlreadyPaid,
1168 20007 => Self::QuoteExpired,
1169 20008 => Self::WitnessMissingOrInvalid,
1170 20009 => Self::PubkeyRequired,
1171 30001 => Self::ClearAuthRequired,
1173 30002 => Self::ClearAuthFailed,
1174 31001 => Self::BlindAuthRequired,
1176 31002 => Self::BlindAuthFailed,
1177 31003 => Self::BatMintMaxExceeded,
1178 31004 => Self::BatRateLimitExceeded,
1179 _ => Self::Unknown(code),
1180 }
1181 }
1182
1183 pub fn to_code(&self) -> u16 {
1185 match self {
1186 Self::TokenNotVerified => 10001,
1188 Self::TokenAlreadySpent => 11001,
1190 Self::TokenPending => 11002,
1191 Self::BlindedMessageAlreadySigned => 11003,
1192 Self::OutputsPending => 11004,
1193 Self::TransactionUnbalanced => 11005,
1194 Self::AmountOutofLimitRange => 11006,
1195 Self::DuplicateInputs => 11007,
1196 Self::DuplicateOutputs => 11008,
1197 Self::MultipleUnits => 11009,
1198 Self::UnitMismatch => 11010,
1199 Self::AmountlessInvoiceNotSupported => 11011,
1200 Self::IncorrectQuoteAmount => 11012,
1201 Self::UnsupportedUnit => 11013,
1202 Self::MaxInputsExceeded => 11014,
1203 Self::MaxOutputsExceeded => 11015,
1204 Self::KeysetNotFound => 12001,
1206 Self::KeysetInactive => 12002,
1207 Self::QuoteNotPaid => 20001,
1209 Self::TokensAlreadyIssued => 20002,
1210 Self::MintingDisabled => 20003,
1211 Self::LightningError => 20004,
1212 Self::QuotePending => 20005,
1213 Self::InvoiceAlreadyPaid => 20006,
1214 Self::QuoteExpired => 20007,
1215 Self::WitnessMissingOrInvalid => 20008,
1216 Self::PubkeyRequired => 20009,
1217 Self::ClearAuthRequired => 30001,
1219 Self::ClearAuthFailed => 30002,
1220 Self::BlindAuthRequired => 31001,
1222 Self::BlindAuthFailed => 31002,
1223 Self::BatMintMaxExceeded => 31003,
1224 Self::BatRateLimitExceeded => 31004,
1225 Self::ConcurrentUpdate => 50000,
1226 Self::Unknown(code) => *code,
1227 }
1228 }
1229}
1230
1231impl Serialize for ErrorCode {
1232 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1233 where
1234 S: Serializer,
1235 {
1236 serializer.serialize_u16(self.to_code())
1237 }
1238}
1239
1240impl<'de> Deserialize<'de> for ErrorCode {
1241 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1242 where
1243 D: Deserializer<'de>,
1244 {
1245 let code = u16::deserialize(deserializer)?;
1246
1247 Ok(ErrorCode::from_code(code))
1248 }
1249}
1250
1251impl fmt::Display for ErrorCode {
1252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1253 write!(f, "{}", self.to_code())
1254 }
1255}