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("Cannot have multiple units")]
220 MultipleUnits,
221 #[error("Input unit must match output")]
223 UnitMismatch,
224 #[error("Sig all cannot be used in melt")]
226 SigAllUsedInMelt,
227 #[error("Token Already Spent")]
229 TokenAlreadySpent,
230 #[error("Token Pending")]
232 TokenPending,
233 #[error("Internal Error")]
235 Internal,
236 #[error("Oidc client not set")]
238 OidcNotSet,
239
240 #[error("P2PK condition not met `{0}`")]
243 P2PKConditionsNotMet(String),
244 #[error("Duplicate signature from same pubkey in P2PK")]
246 DuplicateSignatureError,
247 #[error("Spending condition locktime not provided")]
249 LocktimeNotProvided,
250 #[error("Invalid spending conditions: `{0}`")]
252 InvalidSpendConditions(String),
253 #[error("Incorrect wallet: `{0}`")]
255 IncorrectWallet(String),
256 #[error("Unknown wallet: `{0}`")]
258 #[cfg(feature = "wallet")]
259 UnknownWallet(WalletKey),
260 #[error("Max fee exceeded")]
262 MaxFeeExceeded,
263 #[error("Url path segments could not be joined")]
265 UrlPathSegments,
266 #[error("Unknown error response: `{0}`")]
268 UnknownErrorResponse(String),
269 #[error("Could not verify DLEQ proof")]
271 CouldNotVerifyDleq,
272 #[error("Dleq proof not provided for signature")]
274 DleqProofNotProvided,
275 #[error("Token does not match wallet mint")]
278 IncorrectMint,
279 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
281 MultiMintTokenNotSupported,
282 #[error("Preimage not provided")]
284 PreimageNotProvided,
285
286 #[error("Unknown mint: {mint_url}")]
288 UnknownMint {
289 mint_url: String,
291 },
292 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
294 TransferTimeout {
295 source_mint: String,
297 target_mint: String,
299 amount: Amount,
301 },
302 #[error("Insufficient funds")]
304 InsufficientFunds,
305 #[error("Unexpected proof state")]
307 UnexpectedProofState,
308 #[error("No active keyset")]
310 NoActiveKeyset,
311 #[error("Incorrect quote amount")]
313 IncorrectQuoteAmount,
314 #[error("Invoice Description not supported")]
316 InvoiceDescriptionUnsupported,
317 #[error("Invalid transaction direction")]
319 InvalidTransactionDirection,
320 #[error("Invalid transaction id")]
322 InvalidTransactionId,
323 #[error("Transaction not found")]
325 TransactionNotFound,
326 #[error("Invalid operation kind")]
328 InvalidOperationKind,
329 #[error("Invalid operation state")]
331 InvalidOperationState,
332 #[error("Operation not found")]
334 OperationNotFound,
335 #[error("Invalid KV store key or namespace: {0}")]
337 KVStoreInvalidKey(String),
338 #[error("Concurrent update detected")]
340 ConcurrentUpdate,
341 #[error("Invalid mint response: {0}")]
343 InvalidMintResponse(String),
344 #[error("Subscription error: {0}")]
346 SubscriptionError(String),
347 #[error("`{0}`")]
349 Custom(String),
350
351 #[error(transparent)]
354 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
355 #[error(transparent)]
357 Bip32(#[from] bitcoin::bip32::Error),
358 #[error(transparent)]
360 ParseInt(#[from] std::num::ParseIntError),
361 #[error(transparent)]
363 UrlParseError(#[from] url::ParseError),
364 #[error(transparent)]
366 Utf8ParseError(#[from] std::string::FromUtf8Error),
367 #[error(transparent)]
369 SerdeJsonError(#[from] serde_json::Error),
370 #[error(transparent)]
372 Base64Error(#[from] bitcoin::base64::DecodeError),
373 #[error(transparent)]
375 HexError(#[from] hex::Error),
376 #[error("Http transport error {0:?}: {1}")]
378 HttpError(Option<u16>, String),
379 #[cfg(feature = "mint")]
381 #[error(transparent)]
382 Uuid(#[from] uuid::Error),
383 #[error(transparent)]
386 CashuUrl(#[from] crate::mint_url::Error),
387 #[error(transparent)]
389 Secret(#[from] crate::secret::Error),
390 #[error(transparent)]
392 AmountError(#[from] crate::amount::Error),
393 #[error(transparent)]
395 DHKE(#[from] crate::dhke::Error),
396 #[error(transparent)]
398 NUT00(#[from] crate::nuts::nut00::Error),
399 #[error(transparent)]
401 NUT01(#[from] crate::nuts::nut01::Error),
402 #[error(transparent)]
404 NUT02(#[from] crate::nuts::nut02::Error),
405 #[error(transparent)]
407 NUT03(#[from] crate::nuts::nut03::Error),
408 #[error(transparent)]
410 NUT04(#[from] crate::nuts::nut04::Error),
411 #[error(transparent)]
413 NUT05(#[from] crate::nuts::nut05::Error),
414 #[error(transparent)]
416 NUT11(#[from] crate::nuts::nut11::Error),
417 #[error(transparent)]
419 NUT12(#[from] crate::nuts::nut12::Error),
420 #[error(transparent)]
422 #[cfg(feature = "wallet")]
423 NUT13(#[from] crate::nuts::nut13::Error),
424 #[error(transparent)]
426 NUT14(#[from] crate::nuts::nut14::Error),
427 #[error(transparent)]
429 NUT18(#[from] crate::nuts::nut18::Error),
430 #[error(transparent)]
432 NUT20(#[from] crate::nuts::nut20::Error),
433 #[error(transparent)]
435 NUT21(#[from] crate::nuts::nut21::Error),
436 #[error(transparent)]
438 NUT22(#[from] crate::nuts::nut22::Error),
439 #[error(transparent)]
441 NUT23(#[from] crate::nuts::nut23::Error),
442 #[error(transparent)]
444 #[cfg(feature = "mint")]
445 QuoteId(#[from] crate::quote_id::QuoteIdError),
446 #[error(transparent)]
448 TryFromSliceError(#[from] TryFromSliceError),
449 #[error(transparent)]
451 Database(crate::database::Error),
452 #[error(transparent)]
454 #[cfg(feature = "mint")]
455 Payment(#[from] crate::payment::Error),
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn test_is_definitive_failure() {
464 assert!(Error::AmountOverflow.is_definitive_failure());
466 assert!(Error::TokenAlreadySpent.is_definitive_failure());
467 assert!(Error::MintingDisabled.is_definitive_failure());
468
469 assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
471 assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
472 assert!(
473 Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
474 );
475
476 assert!(!Error::Timeout.is_definitive_failure());
478 assert!(!Error::Internal.is_definitive_failure());
479 assert!(!Error::ConcurrentUpdate.is_definitive_failure());
480
481 assert!(
483 !Error::HttpError(Some(500), "Internal Server Error".to_string())
484 .is_definitive_failure()
485 );
486 assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
487 assert!(
488 !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
489 );
490
491 assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
493 }
494}
495
496impl Error {
497 pub fn is_definitive_failure(&self) -> bool {
506 match self {
507 Self::AmountKey
509 | Self::KeysetUnknown(_)
510 | Self::UnsupportedUnit
511 | Self::InvoiceAmountUndefined
512 | Self::SplitValuesGreater
513 | Self::AmountOverflow
514 | Self::OverIssue
515 | Self::SignatureMissingOrInvalid
516 | Self::AmountLessNotAllowed
517 | Self::InternalMultiPartMeltQuote
518 | Self::MppUnitMethodNotSupported(_, _)
519 | Self::AmountlessInvoiceNotSupported(_, _)
520 | Self::DuplicatePaymentId
521 | Self::PubkeyRequired
522 | Self::InvalidPaymentMethod
523 | Self::UnsupportedPaymentMethod
524 | Self::InvalidInvoice
525 | Self::MintingDisabled
526 | Self::UnknownQuote
527 | Self::ExpiredQuote(_, _)
528 | Self::AmountOutofLimitRange(_, _, _)
529 | Self::UnpaidQuote
530 | Self::PendingQuote
531 | Self::IssuedQuote
532 | Self::PaidQuote
533 | Self::MeltingDisabled
534 | Self::UnknownKeySet
535 | Self::BlindedMessageAlreadySigned
536 | Self::InactiveKeyset
537 | Self::TransactionUnbalanced(_, _, _)
538 | Self::DuplicateInputs
539 | Self::DuplicateOutputs
540 | Self::MultipleUnits
541 | Self::UnitMismatch
542 | Self::SigAllUsedInMelt
543 | Self::TokenAlreadySpent
544 | Self::TokenPending
545 | Self::P2PKConditionsNotMet(_)
546 | Self::DuplicateSignatureError
547 | Self::LocktimeNotProvided
548 | Self::InvalidSpendConditions(_)
549 | Self::IncorrectWallet(_)
550 | Self::MaxFeeExceeded
551 | Self::DleqProofNotProvided
552 | Self::IncorrectMint
553 | Self::MultiMintTokenNotSupported
554 | Self::PreimageNotProvided
555 | Self::UnknownMint { .. }
556 | Self::UnexpectedProofState
557 | Self::NoActiveKeyset
558 | Self::IncorrectQuoteAmount
559 | Self::InvoiceDescriptionUnsupported
560 | Self::InvalidTransactionDirection
561 | Self::InvalidTransactionId
562 | Self::InvalidOperationKind
563 | Self::InvalidOperationState
564 | Self::OperationNotFound
565 | Self::KVStoreInvalidKey(_) => true,
566
567 Self::HttpError(Some(status), _) => {
569 (400..500).contains(status)
572 }
573
574 Self::Timeout
576 | Self::Internal
577 | Self::UnknownPaymentState
578 | Self::CouldNotGetMintInfo
579 | Self::UnknownErrorResponse(_)
580 | Self::InvalidMintResponse(_)
581 | Self::ConcurrentUpdate
582 | Self::SendError(_)
583 | Self::RecvError(_)
584 | Self::TransferTimeout { .. } => false,
585
586 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
590 | Self::Custom(_) => false,
591
592 Self::ClearAuthRequired
594 | Self::BlindAuthRequired
595 | Self::ClearAuthFailed
596 | Self::BlindAuthFailed
597 | Self::InsufficientBlindAuthTokens
598 | Self::AuthSettingsUndefined
599 | Self::AuthLocalstoreUndefined
600 | Self::OidcNotSet => true,
601
602 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
606 Self::UrlParseError(_) => true,
607 Self::Utf8ParseError(_) => true,
608 Self::Base64Error(_) => true,
609 Self::HexError(_) => true,
610 #[cfg(feature = "mint")]
611 Self::Uuid(_) => true,
612 Self::CashuUrl(_) => true,
613 Self::Secret(_) => true,
614 Self::AmountError(_) => true,
615 Self::DHKE(_) => true, Self::NUT00(_) => true,
617 Self::NUT01(_) => true,
618 Self::NUT02(_) => true,
619 Self::NUT03(_) => true,
620 Self::NUT04(_) => true,
621 Self::NUT05(_) => true,
622 Self::NUT11(_) => true,
623 Self::NUT12(_) => true,
624 #[cfg(feature = "wallet")]
625 Self::NUT13(_) => true,
626 Self::NUT14(_) => true,
627 Self::NUT18(_) => true,
628 Self::NUT20(_) => true,
629 Self::NUT21(_) => true,
630 Self::NUT22(_) => true,
631 Self::NUT23(_) => true,
632 #[cfg(feature = "mint")]
633 Self::QuoteId(_) => true,
634 Self::TryFromSliceError(_) => true,
635 #[cfg(feature = "mint")]
636 Self::Payment(_) => false, _ => false,
640 }
641 }
642}
643
644#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
648#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
649pub struct ErrorResponse {
650 pub code: ErrorCode,
652 #[serde(default)]
654 pub detail: String,
655}
656
657impl fmt::Display for ErrorResponse {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659 write!(f, "code: {}, detail: {}", self.code, self.detail)
660 }
661}
662
663impl ErrorResponse {
664 pub fn new(code: ErrorCode, detail: String) -> Self {
666 Self { code, detail }
667 }
668
669 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
671 let value: Value = serde_json::from_str(json)?;
672
673 Self::from_value(value)
674 }
675
676 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
678 match serde_json::from_value::<ErrorResponse>(value.clone()) {
679 Ok(res) => Ok(res),
680 Err(_) => Ok(Self {
681 code: ErrorCode::Unknown(999),
682 detail: value.to_string(),
683 }),
684 }
685 }
686}
687
688fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
691 ErrorCode::WitnessMissingOrInvalid
693}
694
695impl From<Error> for ErrorResponse {
696 fn from(err: Error) -> ErrorResponse {
697 match err {
698 Error::TokenAlreadySpent => ErrorResponse {
699 code: ErrorCode::TokenAlreadySpent,
700 detail: err.to_string(),
701 },
702 Error::UnsupportedUnit => ErrorResponse {
703 code: ErrorCode::UnsupportedUnit,
704 detail: err.to_string(),
705 },
706 Error::PaymentFailed => ErrorResponse {
707 code: ErrorCode::LightningError,
708 detail: err.to_string(),
709 },
710 Error::RequestAlreadyPaid => ErrorResponse {
711 code: ErrorCode::InvoiceAlreadyPaid,
712 detail: "Invoice already paid.".to_string(),
713 },
714 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
715 ErrorResponse {
716 code: ErrorCode::TransactionUnbalanced,
717 detail: format!(
718 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
719 ),
720 }
721 }
722 Error::MintingDisabled => ErrorResponse {
723 code: ErrorCode::MintingDisabled,
724 detail: err.to_string(),
725 },
726 Error::BlindedMessageAlreadySigned => ErrorResponse {
727 code: ErrorCode::BlindedMessageAlreadySigned,
728 detail: err.to_string(),
729 },
730 Error::InsufficientFunds => ErrorResponse {
731 code: ErrorCode::TransactionUnbalanced,
732 detail: err.to_string(),
733 },
734 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
735 code: ErrorCode::AmountOutofLimitRange,
736 detail: err.to_string(),
737 },
738 Error::ExpiredQuote(_, _) => ErrorResponse {
739 code: ErrorCode::QuoteExpired,
740 detail: err.to_string(),
741 },
742 Error::PendingQuote => ErrorResponse {
743 code: ErrorCode::QuotePending,
744 detail: err.to_string(),
745 },
746 Error::TokenPending => ErrorResponse {
747 code: ErrorCode::TokenPending,
748 detail: err.to_string(),
749 },
750 Error::ClearAuthRequired => ErrorResponse {
751 code: ErrorCode::ClearAuthRequired,
752 detail: Error::ClearAuthRequired.to_string(),
753 },
754 Error::ClearAuthFailed => ErrorResponse {
755 code: ErrorCode::ClearAuthFailed,
756 detail: Error::ClearAuthFailed.to_string(),
757 },
758 Error::BlindAuthRequired => ErrorResponse {
759 code: ErrorCode::BlindAuthRequired,
760 detail: Error::BlindAuthRequired.to_string(),
761 },
762 Error::BlindAuthFailed => ErrorResponse {
763 code: ErrorCode::BlindAuthFailed,
764 detail: Error::BlindAuthFailed.to_string(),
765 },
766 Error::NUT20(err) => ErrorResponse {
767 code: ErrorCode::WitnessMissingOrInvalid,
768 detail: err.to_string(),
769 },
770 Error::DuplicateInputs => ErrorResponse {
771 code: ErrorCode::DuplicateInputs,
772 detail: err.to_string(),
773 },
774 Error::DuplicateOutputs => ErrorResponse {
775 code: ErrorCode::DuplicateOutputs,
776 detail: err.to_string(),
777 },
778 Error::MultipleUnits => ErrorResponse {
779 code: ErrorCode::MultipleUnits,
780 detail: err.to_string(),
781 },
782 Error::UnitMismatch => ErrorResponse {
783 code: ErrorCode::UnitMismatch,
784 detail: err.to_string(),
785 },
786 Error::UnpaidQuote => ErrorResponse {
787 code: ErrorCode::QuoteNotPaid,
788 detail: Error::UnpaidQuote.to_string(),
789 },
790 Error::NUT11(err) => {
791 let code = map_nut11_error(&err);
792 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
793 Some("P2PK signatures are required but not provided".to_string())
794 } else {
795 None
796 };
797 ErrorResponse {
798 code,
799 detail: match extra {
800 Some(extra) => format!("{err}. {extra}"),
801 None => err.to_string(),
802 },
803 }
804 },
805 Error::DuplicateSignatureError => ErrorResponse {
806 code: ErrorCode::WitnessMissingOrInvalid,
807 detail: err.to_string(),
808 },
809 Error::IssuedQuote => ErrorResponse {
810 code: ErrorCode::TokensAlreadyIssued,
811 detail: err.to_string(),
812 },
813 Error::UnknownKeySet => ErrorResponse {
814 code: ErrorCode::KeysetNotFound,
815 detail: err.to_string(),
816 },
817 Error::InactiveKeyset => ErrorResponse {
818 code: ErrorCode::KeysetInactive,
819 detail: err.to_string(),
820 },
821 Error::AmountLessNotAllowed => ErrorResponse {
822 code: ErrorCode::AmountlessInvoiceNotSupported,
823 detail: err.to_string(),
824 },
825 Error::IncorrectQuoteAmount => ErrorResponse {
826 code: ErrorCode::IncorrectQuoteAmount,
827 detail: err.to_string(),
828 },
829 Error::PubkeyRequired => ErrorResponse {
830 code: ErrorCode::PubkeyRequired,
831 detail: err.to_string(),
832 },
833 Error::PaidQuote => ErrorResponse {
834 code: ErrorCode::InvoiceAlreadyPaid,
835 detail: err.to_string(),
836 },
837 Error::DuplicatePaymentId => ErrorResponse {
838 code: ErrorCode::InvoiceAlreadyPaid,
839 detail: err.to_string(),
840 },
841 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
843 code: ErrorCode::InvoiceAlreadyPaid,
844 detail: "Invoice already paid or pending".to_string(),
845 },
846
847 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
849 code: ErrorCode::TokenNotVerified,
850 detail: err.to_string(),
851 },
852 Error::DHKE(_) => ErrorResponse {
853 code: ErrorCode::Unknown(50000),
854 detail: err.to_string(),
855 },
856
857 Error::CouldNotVerifyDleq => ErrorResponse {
859 code: ErrorCode::TokenNotVerified,
860 detail: err.to_string(),
861 },
862 Error::SignatureMissingOrInvalid => ErrorResponse {
863 code: ErrorCode::WitnessMissingOrInvalid,
864 detail: err.to_string(),
865 },
866 Error::SigAllUsedInMelt => ErrorResponse {
867 code: ErrorCode::WitnessMissingOrInvalid,
868 detail: err.to_string(),
869 },
870
871 Error::AmountKey => ErrorResponse {
873 code: ErrorCode::KeysetNotFound,
874 detail: err.to_string(),
875 },
876 Error::KeysetUnknown(_) => ErrorResponse {
877 code: ErrorCode::KeysetNotFound,
878 detail: err.to_string(),
879 },
880 Error::NoActiveKeyset => ErrorResponse {
881 code: ErrorCode::KeysetInactive,
882 detail: err.to_string(),
883 },
884
885 Error::UnknownQuote => ErrorResponse {
887 code: ErrorCode::Unknown(50000),
888 detail: err.to_string(),
889 },
890 Error::MeltingDisabled => ErrorResponse {
891 code: ErrorCode::MintingDisabled,
892 detail: err.to_string(),
893 },
894 Error::PaymentPending => ErrorResponse {
895 code: ErrorCode::QuotePending,
896 detail: err.to_string(),
897 },
898 Error::UnknownPaymentState => ErrorResponse {
899 code: ErrorCode::Unknown(50000),
900 detail: err.to_string(),
901 },
902
903 Error::SplitValuesGreater => ErrorResponse {
905 code: ErrorCode::TransactionUnbalanced,
906 detail: err.to_string(),
907 },
908 Error::AmountOverflow => ErrorResponse {
909 code: ErrorCode::TransactionUnbalanced,
910 detail: err.to_string(),
911 },
912 Error::OverIssue => ErrorResponse {
913 code: ErrorCode::TransactionUnbalanced,
914 detail: err.to_string(),
915 },
916
917 Error::InvalidPaymentRequest => ErrorResponse {
919 code: ErrorCode::Unknown(50000),
920 detail: err.to_string(),
921 },
922 Error::InvoiceAmountUndefined => ErrorResponse {
923 code: ErrorCode::AmountlessInvoiceNotSupported,
924 detail: err.to_string(),
925 },
926
927 Error::Internal => ErrorResponse {
929 code: ErrorCode::Unknown(50000),
930 detail: err.to_string(),
931 },
932 Error::Database(_) => ErrorResponse {
933 code: ErrorCode::Unknown(50000),
934 detail: err.to_string(),
935 },
936 Error::ConcurrentUpdate => ErrorResponse {
937 code: ErrorCode::ConcurrentUpdate,
938 detail: err.to_string(),
939 },
940 Error::MaxInputsExceeded { .. } => ErrorResponse {
941 code: ErrorCode::MaxInputsExceeded,
942 detail: err.to_string()
943 },
944 Error::MaxOutputsExceeded { .. } => ErrorResponse {
945 code: ErrorCode::MaxOutputsExceeded,
946 detail: err.to_string()
947 },
948 _ => ErrorResponse {
950 code: ErrorCode::Unknown(50000),
951 detail: err.to_string(),
952 },
953 }
954 }
955}
956
957#[cfg(feature = "mint")]
958impl From<crate::database::Error> for Error {
959 fn from(db_error: crate::database::Error) -> Self {
960 match db_error {
961 crate::database::Error::InvalidStateTransition(state) => match state {
962 crate::state::Error::Pending => Self::TokenPending,
963 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
964 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
965 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
966 },
967 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
968 db_error => Self::Database(db_error),
969 }
970 }
971}
972
973#[cfg(not(feature = "mint"))]
974impl From<crate::database::Error> for Error {
975 fn from(db_error: crate::database::Error) -> Self {
976 match db_error {
977 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
978 db_error => Self::Database(db_error),
979 }
980 }
981}
982
983impl From<ErrorResponse> for Error {
984 fn from(err: ErrorResponse) -> Error {
985 match err.code {
986 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
988 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
990 ErrorCode::TokenPending => Self::TokenPending,
991 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
992 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
994 ErrorCode::AmountOutofLimitRange => {
995 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
996 }
997 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
998 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
999 ErrorCode::MultipleUnits => Self::MultipleUnits,
1000 ErrorCode::UnitMismatch => Self::UnitMismatch,
1001 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1002 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1003 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1004 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1006 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1007 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1009 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1010 ErrorCode::MintingDisabled => Self::MintingDisabled,
1011 ErrorCode::LightningError => Self::PaymentFailed,
1012 ErrorCode::QuotePending => Self::PendingQuote,
1013 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1014 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1015 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1016 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1017 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1019 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1020 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1022 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1023 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1024 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1025 _ => Self::UnknownErrorResponse(err.to_string()),
1026 }
1027 }
1028}
1029
1030#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1032#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
1033pub enum ErrorCode {
1034 TokenNotVerified,
1037
1038 TokenAlreadySpent,
1041 TokenPending,
1043 BlindedMessageAlreadySigned,
1045 OutputsPending,
1047 TransactionUnbalanced,
1049 AmountOutofLimitRange,
1051 DuplicateInputs,
1053 DuplicateOutputs,
1055 MultipleUnits,
1057 UnitMismatch,
1059 AmountlessInvoiceNotSupported,
1061 IncorrectQuoteAmount,
1063 UnsupportedUnit,
1065 MaxInputsExceeded,
1067 MaxOutputsExceeded,
1069
1070 KeysetNotFound,
1073 KeysetInactive,
1075
1076 QuoteNotPaid,
1079 TokensAlreadyIssued,
1081 MintingDisabled,
1083 LightningError,
1085 QuotePending,
1087 InvoiceAlreadyPaid,
1089 QuoteExpired,
1091 WitnessMissingOrInvalid,
1093 PubkeyRequired,
1095
1096 ClearAuthRequired,
1099 ClearAuthFailed,
1101
1102 BlindAuthRequired,
1105 BlindAuthFailed,
1107 BatMintMaxExceeded,
1109 BatRateLimitExceeded,
1111
1112 ConcurrentUpdate,
1114
1115 Unknown(u16),
1117}
1118
1119impl ErrorCode {
1120 pub fn from_code(code: u16) -> Self {
1122 match code {
1123 10001 => Self::TokenNotVerified,
1125 11001 => Self::TokenAlreadySpent,
1127 11002 => Self::TokenPending,
1128 11003 => Self::BlindedMessageAlreadySigned,
1129 11004 => Self::OutputsPending,
1130 11005 => Self::TransactionUnbalanced,
1131 11006 => Self::AmountOutofLimitRange,
1132 11007 => Self::DuplicateInputs,
1133 11008 => Self::DuplicateOutputs,
1134 11009 => Self::MultipleUnits,
1135 11010 => Self::UnitMismatch,
1136 11011 => Self::AmountlessInvoiceNotSupported,
1137 11012 => Self::IncorrectQuoteAmount,
1138 11013 => Self::UnsupportedUnit,
1139 11014 => Self::MaxInputsExceeded,
1140 11015 => Self::MaxOutputsExceeded,
1141 12001 => Self::KeysetNotFound,
1143 12002 => Self::KeysetInactive,
1144 20001 => Self::QuoteNotPaid,
1146 20002 => Self::TokensAlreadyIssued,
1147 20003 => Self::MintingDisabled,
1148 20004 => Self::LightningError,
1149 20005 => Self::QuotePending,
1150 20006 => Self::InvoiceAlreadyPaid,
1151 20007 => Self::QuoteExpired,
1152 20008 => Self::WitnessMissingOrInvalid,
1153 20009 => Self::PubkeyRequired,
1154 30001 => Self::ClearAuthRequired,
1156 30002 => Self::ClearAuthFailed,
1157 31001 => Self::BlindAuthRequired,
1159 31002 => Self::BlindAuthFailed,
1160 31003 => Self::BatMintMaxExceeded,
1161 31004 => Self::BatRateLimitExceeded,
1162 _ => Self::Unknown(code),
1163 }
1164 }
1165
1166 pub fn to_code(&self) -> u16 {
1168 match self {
1169 Self::TokenNotVerified => 10001,
1171 Self::TokenAlreadySpent => 11001,
1173 Self::TokenPending => 11002,
1174 Self::BlindedMessageAlreadySigned => 11003,
1175 Self::OutputsPending => 11004,
1176 Self::TransactionUnbalanced => 11005,
1177 Self::AmountOutofLimitRange => 11006,
1178 Self::DuplicateInputs => 11007,
1179 Self::DuplicateOutputs => 11008,
1180 Self::MultipleUnits => 11009,
1181 Self::UnitMismatch => 11010,
1182 Self::AmountlessInvoiceNotSupported => 11011,
1183 Self::IncorrectQuoteAmount => 11012,
1184 Self::UnsupportedUnit => 11013,
1185 Self::MaxInputsExceeded => 11014,
1186 Self::MaxOutputsExceeded => 11015,
1187 Self::KeysetNotFound => 12001,
1189 Self::KeysetInactive => 12002,
1190 Self::QuoteNotPaid => 20001,
1192 Self::TokensAlreadyIssued => 20002,
1193 Self::MintingDisabled => 20003,
1194 Self::LightningError => 20004,
1195 Self::QuotePending => 20005,
1196 Self::InvoiceAlreadyPaid => 20006,
1197 Self::QuoteExpired => 20007,
1198 Self::WitnessMissingOrInvalid => 20008,
1199 Self::PubkeyRequired => 20009,
1200 Self::ClearAuthRequired => 30001,
1202 Self::ClearAuthFailed => 30002,
1203 Self::BlindAuthRequired => 31001,
1205 Self::BlindAuthFailed => 31002,
1206 Self::BatMintMaxExceeded => 31003,
1207 Self::BatRateLimitExceeded => 31004,
1208 Self::ConcurrentUpdate => 50000,
1209 Self::Unknown(code) => *code,
1210 }
1211 }
1212}
1213
1214impl Serialize for ErrorCode {
1215 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1216 where
1217 S: Serializer,
1218 {
1219 serializer.serialize_u16(self.to_code())
1220 }
1221}
1222
1223impl<'de> Deserialize<'de> for ErrorCode {
1224 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1225 where
1226 D: Deserializer<'de>,
1227 {
1228 let code = u16::deserialize(deserializer)?;
1229
1230 Ok(ErrorCode::from_code(code))
1231 }
1232}
1233
1234impl fmt::Display for ErrorCode {
1235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1236 write!(f, "{}", self.to_code())
1237 }
1238}