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 BOLT12 offer found in BIP353 payment instructions")]
136 Bip353NoBolt12Offer,
137
138 #[error("Failed to parse BIP321 payment instruction: {0}")]
140 Bip321Parse(String),
141 #[error("Failed to encode BIP321 payment request: {0}")]
143 Bip321Encode(String),
144
145 #[error("Failed to parse Lightning address: {0}")]
147 LightningAddressParse(String),
148 #[error("Failed to request invoice from Lightning address service: {0}")]
150 LightningAddressRequest(String),
151
152 #[error("Internal send error: {0}")]
154 SendError(String),
155
156 #[error("Internal receive error: {0}")]
158 RecvError(String),
159
160 #[error("Minting is disabled")]
163 MintingDisabled,
164 #[error("Unknown quote")]
166 UnknownQuote,
167 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
169 ExpiredQuote(u64, u64),
170 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
172 AmountOutofLimitRange(Amount, Amount, Amount),
173 #[error("Quote not paid")]
175 UnpaidQuote,
176 #[error("Quote pending")]
178 PendingQuote,
179 #[error("Quote already issued")]
181 IssuedQuote,
182 #[error("Quote is already paid")]
184 PaidQuote,
185 #[error("Payment state is unknown")]
187 UnknownPaymentState,
188 #[error("Melting is disabled")]
190 MeltingDisabled,
191 #[error("Unknown Keyset")]
193 UnknownKeySet,
194 #[error("Blinded Message is already signed")]
196 BlindedMessageAlreadySigned,
197 #[error("Inactive Keyset")]
199 InactiveKeyset,
200 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
202 TransactionUnbalanced(u64, u64, u64),
203 #[error("Duplicate Inputs")]
205 DuplicateInputs,
206 #[error("Duplicate outputs")]
208 DuplicateOutputs,
209 #[error("Maximum inputs exceeded: {actual} provided, max {max}")]
211 MaxInputsExceeded {
212 actual: usize,
214 max: usize,
216 },
217 #[error("Maximum outputs exceeded: {actual} provided, max {max}")]
219 MaxOutputsExceeded {
220 actual: usize,
222 max: usize,
224 },
225 #[error("Proof content too large: {actual} bytes, max {max}")]
227 ProofContentTooLarge {
228 actual: usize,
230 max: usize,
232 },
233 #[error("Request field '{field}' too large: {actual} bytes, max {max}")]
235 RequestFieldTooLarge {
236 field: String,
238 actual: usize,
240 max: usize,
242 },
243 #[error("Cannot have multiple units")]
245 MultipleUnits,
246 #[error("Input unit must match output")]
248 UnitMismatch,
249 #[error("Sig all cannot be used in melt")]
251 SigAllUsedInMelt,
252 #[error("Token Already Spent")]
254 TokenAlreadySpent,
255 #[error("Token Pending")]
257 TokenPending,
258 #[error("Internal Error")]
260 Internal,
261 #[error("Oidc client not set")]
263 OidcNotSet,
264 #[error("Unit string picked collided: `{0}`")]
266 UnitStringCollision(CurrencyUnit),
267 #[error("P2PK condition not met `{0}`")]
270 P2PKConditionsNotMet(String),
271 #[error("Duplicate signature from same pubkey in P2PK")]
273 DuplicateSignatureError,
274 #[error("Spending condition locktime not provided")]
276 LocktimeNotProvided,
277 #[error("Invalid spending conditions: `{0}`")]
279 InvalidSpendConditions(String),
280 #[error("Incorrect wallet: `{0}`")]
282 IncorrectWallet(String),
283 #[error("Unknown wallet: `{0}`")]
285 #[cfg(feature = "wallet")]
286 UnknownWallet(WalletKey),
287 #[error("Max fee exceeded")]
289 MaxFeeExceeded,
290 #[error("Url path segments could not be joined")]
292 UrlPathSegments,
293 #[error("Unknown error response: `{0}`")]
295 UnknownErrorResponse(String),
296 #[error("Could not verify DLEQ proof")]
298 CouldNotVerifyDleq,
299 #[error("Dleq proof not provided for signature")]
301 DleqProofNotProvided,
302 #[error("Token does not match wallet mint")]
305 IncorrectMint,
306 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
308 MultiMintTokenNotSupported,
309 #[error("Preimage not provided")]
311 PreimageNotProvided,
312
313 #[error("Unknown mint: {mint_url}")]
315 UnknownMint {
316 mint_url: String,
318 },
319 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
321 TransferTimeout {
322 source_mint: String,
324 target_mint: String,
326 amount: Amount,
328 },
329 #[error("Insufficient funds")]
331 InsufficientFunds,
332 #[error("Unexpected proof state")]
334 UnexpectedProofState,
335 #[error("No active keyset")]
337 NoActiveKeyset,
338 #[error("Incorrect quote amount")]
340 IncorrectQuoteAmount,
341 #[error("Invoice Description not supported")]
343 InvoiceDescriptionUnsupported,
344 #[error("Invalid transaction direction")]
346 InvalidTransactionDirection,
347 #[error("Invalid transaction id")]
349 InvalidTransactionId,
350 #[error("Transaction not found")]
352 TransactionNotFound,
353 #[error("Invalid operation kind")]
355 InvalidOperationKind,
356 #[error("Invalid operation state")]
358 InvalidOperationState,
359 #[error("Operation not found")]
361 OperationNotFound,
362 #[error("Invalid KV store key or namespace: {0}")]
364 KVStoreInvalidKey(String),
365 #[error("Concurrent update detected")]
367 ConcurrentUpdate,
368 #[error("Invalid mint response: {0}")]
370 InvalidMintResponse(String),
371 #[error("Subscription error: {0}")]
373 SubscriptionError(String),
374 #[error("`{0}`")]
376 Custom(String),
377
378 #[error(transparent)]
381 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
382 #[error(transparent)]
384 Bip32(#[from] bitcoin::bip32::Error),
385 #[error(transparent)]
387 ParseInt(#[from] std::num::ParseIntError),
388 #[error(transparent)]
390 UrlParseError(#[from] url::ParseError),
391 #[error(transparent)]
393 Utf8ParseError(#[from] std::string::FromUtf8Error),
394 #[error(transparent)]
396 SerdeJsonError(#[from] serde_json::Error),
397 #[error(transparent)]
399 Base64Error(#[from] bitcoin::base64::DecodeError),
400 #[error(transparent)]
402 HexError(#[from] hex::Error),
403 #[error("Http transport error {0:?}: {1}")]
405 HttpError(Option<u16>, String),
406 #[cfg(feature = "mint")]
408 #[error(transparent)]
409 Uuid(#[from] uuid::Error),
410 #[error(transparent)]
413 CashuUrl(#[from] crate::mint_url::Error),
414 #[error(transparent)]
416 Secret(#[from] crate::secret::Error),
417 #[error(transparent)]
419 AmountError(#[from] crate::amount::Error),
420 #[error(transparent)]
422 DHKE(#[from] crate::dhke::Error),
423 #[error(transparent)]
425 NUT00(#[from] crate::nuts::nut00::Error),
426 #[error(transparent)]
428 NUT01(#[from] crate::nuts::nut01::Error),
429 #[error(transparent)]
431 NUT02(#[from] crate::nuts::nut02::Error),
432 #[error(transparent)]
434 NUT03(#[from] crate::nuts::nut03::Error),
435 #[error(transparent)]
437 NUT04(#[from] crate::nuts::nut04::Error),
438 #[error(transparent)]
440 NUT05(#[from] crate::nuts::nut05::Error),
441 #[error(transparent)]
443 NUT10(crate::nuts::nut10::Error),
444 #[error(transparent)]
446 NUT11(#[from] crate::nuts::nut11::Error),
447 #[error(transparent)]
449 NUT12(#[from] crate::nuts::nut12::Error),
450 #[error(transparent)]
452 #[cfg(feature = "wallet")]
453 NUT13(#[from] crate::nuts::nut13::Error),
454 #[error(transparent)]
456 NUT14(#[from] crate::nuts::nut14::Error),
457 #[error(transparent)]
459 NUT18(#[from] crate::nuts::nut18::Error),
460 #[error(transparent)]
462 NUT20(#[from] crate::nuts::nut20::Error),
463 #[error(transparent)]
465 NUT21(#[from] crate::nuts::nut21::Error),
466 #[error(transparent)]
468 NUT22(#[from] crate::nuts::nut22::Error),
469 #[error(transparent)]
471 NUT23(#[from] crate::nuts::nut23::Error),
472 #[error(transparent)]
474 #[cfg(feature = "mint")]
475 QuoteId(#[from] crate::quote_id::QuoteIdError),
476 #[error(transparent)]
478 TryFromSliceError(#[from] TryFromSliceError),
479 #[error(transparent)]
481 Database(crate::database::Error),
482 #[error(transparent)]
484 #[cfg(feature = "mint")]
485 Payment(#[from] crate::payment::Error),
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_is_definitive_failure() {
494 assert!(Error::AmountOverflow.is_definitive_failure());
496 assert!(Error::TokenAlreadySpent.is_definitive_failure());
497 assert!(Error::MintingDisabled.is_definitive_failure());
498
499 assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
501 assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
502 assert!(
503 Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
504 );
505
506 assert!(!Error::Timeout.is_definitive_failure());
508 assert!(!Error::Internal.is_definitive_failure());
509 assert!(!Error::ConcurrentUpdate.is_definitive_failure());
510
511 assert!(
513 !Error::HttpError(Some(500), "Internal Server Error".to_string())
514 .is_definitive_failure()
515 );
516 assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
517 assert!(
518 !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
519 );
520
521 assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
523 }
524}
525
526impl Error {
527 pub fn is_definitive_failure(&self) -> bool {
536 match self {
537 Self::AmountKey
539 | Self::KeysetUnknown(_)
540 | Self::UnsupportedUnit
541 | Self::InvoiceAmountUndefined
542 | Self::SplitValuesGreater
543 | Self::AmountOverflow
544 | Self::OverIssue
545 | Self::SignatureMissingOrInvalid
546 | Self::AmountLessNotAllowed
547 | Self::InternalMultiPartMeltQuote
548 | Self::MppUnitMethodNotSupported(_, _)
549 | Self::AmountlessInvoiceNotSupported(_, _)
550 | Self::DuplicatePaymentId
551 | Self::PubkeyRequired
552 | Self::InvalidPaymentMethod
553 | Self::UnsupportedPaymentMethod
554 | Self::InvalidInvoice
555 | Self::MintingDisabled
556 | Self::UnknownQuote
557 | Self::ExpiredQuote(_, _)
558 | Self::AmountOutofLimitRange(_, _, _)
559 | Self::UnpaidQuote
560 | Self::PendingQuote
561 | Self::IssuedQuote
562 | Self::PaidQuote
563 | Self::MeltingDisabled
564 | Self::UnknownKeySet
565 | Self::BlindedMessageAlreadySigned
566 | Self::InactiveKeyset
567 | Self::TransactionUnbalanced(_, _, _)
568 | Self::DuplicateInputs
569 | Self::DuplicateOutputs
570 | Self::MultipleUnits
571 | Self::UnitMismatch
572 | Self::SigAllUsedInMelt
573 | Self::TokenAlreadySpent
574 | Self::TokenPending
575 | Self::P2PKConditionsNotMet(_)
576 | Self::DuplicateSignatureError
577 | Self::LocktimeNotProvided
578 | Self::InvalidSpendConditions(_)
579 | Self::IncorrectWallet(_)
580 | Self::MaxFeeExceeded
581 | Self::DleqProofNotProvided
582 | Self::IncorrectMint
583 | Self::MultiMintTokenNotSupported
584 | Self::PreimageNotProvided
585 | Self::UnknownMint { .. }
586 | Self::UnexpectedProofState
587 | Self::NoActiveKeyset
588 | Self::IncorrectQuoteAmount
589 | Self::InvoiceDescriptionUnsupported
590 | Self::InvalidTransactionDirection
591 | Self::InvalidTransactionId
592 | Self::InvalidOperationKind
593 | Self::InvalidOperationState
594 | Self::OperationNotFound
595 | Self::KVStoreInvalidKey(_)
596 | Self::Bip353Parse(_)
597 | Self::Bip353NoBolt12Offer
598 | Self::Bip321Parse(_)
599 | Self::Bip321Encode(_)
600 | Self::LightningAddressParse(_) => true,
601
602 Self::HttpError(Some(status), _) => {
604 (400..500).contains(status)
607 }
608
609 Self::Timeout
611 | Self::Internal
612 | Self::UnknownPaymentState
613 | Self::CouldNotGetMintInfo
614 | Self::UnknownErrorResponse(_)
615 | Self::InvalidMintResponse(_)
616 | Self::ConcurrentUpdate
617 | Self::SendError(_)
618 | Self::RecvError(_)
619 | Self::TransferTimeout { .. }
620 | Self::Bip353Resolve(_)
621 | Self::LightningAddressRequest(_) => false,
622
623 Self::HttpError(None, _) | Self::SerdeJsonError(_) | Self::Database(_)
627 | Self::Custom(_) => false,
628
629 Self::ClearAuthRequired
631 | Self::BlindAuthRequired
632 | Self::ClearAuthFailed
633 | Self::BlindAuthFailed
634 | Self::InsufficientBlindAuthTokens
635 | Self::AuthSettingsUndefined
636 | Self::AuthLocalstoreUndefined
637 | Self::OidcNotSet => true,
638
639 Self::Invoice(_) => true, Self::Bip32(_) => true, Self::ParseInt(_) => true,
643 Self::UrlParseError(_) => true,
644 Self::Utf8ParseError(_) => true,
645 Self::Base64Error(_) => true,
646 Self::HexError(_) => true,
647 #[cfg(feature = "mint")]
648 Self::Uuid(_) => true,
649 Self::CashuUrl(_) => true,
650 Self::Secret(_) => true,
651 Self::AmountError(_) => true,
652 Self::DHKE(_) => true, Self::NUT00(_) => true,
654 Self::NUT01(_) => true,
655 Self::NUT02(_) => true,
656 Self::NUT03(_) => true,
657 Self::NUT04(_) => true,
658 Self::NUT05(_) => true,
659 Self::NUT11(_) => true,
660 Self::NUT12(_) => true,
661 #[cfg(feature = "wallet")]
662 Self::NUT13(_) => true,
663 Self::NUT14(_) => true,
664 Self::NUT18(_) => true,
665 Self::NUT20(_) => true,
666 Self::NUT21(_) => true,
667 Self::NUT22(_) => true,
668 Self::NUT23(_) => true,
669 #[cfg(feature = "mint")]
670 Self::QuoteId(_) => true,
671 Self::TryFromSliceError(_) => true,
672 #[cfg(feature = "mint")]
673 Self::Payment(_) => false, _ => false,
677 }
678 }
679}
680
681impl From<crate::nuts::nut10::Error> for Error {
682 fn from(err: crate::nuts::nut10::Error) -> Self {
683 match err {
684 crate::nuts::nut10::Error::NUT11(nut11_err) => Self::NUT11(nut11_err),
685 crate::nuts::nut10::Error::NUT14(nut14_err) => Self::NUT14(nut14_err),
686 other => Self::NUT10(other),
687 }
688 }
689}
690
691#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
695#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
696pub struct ErrorResponse {
697 pub code: ErrorCode,
699 #[serde(default)]
701 pub detail: String,
702}
703
704impl fmt::Display for ErrorResponse {
705 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
706 write!(f, "code: {}, detail: {}", self.code, self.detail)
707 }
708}
709
710impl ErrorResponse {
711 pub fn new(code: ErrorCode, detail: String) -> Self {
713 Self { code, detail }
714 }
715
716 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
718 let value: Value = serde_json::from_str(json)?;
719
720 Self::from_value(value)
721 }
722
723 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
725 match serde_json::from_value::<ErrorResponse>(value.clone()) {
726 Ok(res) => Ok(res),
727 Err(_) => Ok(Self {
728 code: ErrorCode::Unknown(999),
729 detail: value.to_string(),
730 }),
731 }
732 }
733}
734
735fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
738 ErrorCode::WitnessMissingOrInvalid
740}
741
742impl From<Error> for ErrorResponse {
743 fn from(err: Error) -> ErrorResponse {
744 match err {
745 Error::TokenAlreadySpent => ErrorResponse {
746 code: ErrorCode::TokenAlreadySpent,
747 detail: err.to_string(),
748 },
749 Error::UnsupportedUnit => ErrorResponse {
750 code: ErrorCode::UnsupportedUnit,
751 detail: err.to_string(),
752 },
753 Error::PaymentFailed => ErrorResponse {
754 code: ErrorCode::LightningError,
755 detail: err.to_string(),
756 },
757 Error::RequestAlreadyPaid => ErrorResponse {
758 code: ErrorCode::InvoiceAlreadyPaid,
759 detail: "Invoice already paid.".to_string(),
760 },
761 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
762 ErrorResponse {
763 code: ErrorCode::TransactionUnbalanced,
764 detail: format!(
765 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
766 ),
767 }
768 }
769 Error::MintingDisabled => ErrorResponse {
770 code: ErrorCode::MintingDisabled,
771 detail: err.to_string(),
772 },
773 Error::BlindedMessageAlreadySigned => ErrorResponse {
774 code: ErrorCode::BlindedMessageAlreadySigned,
775 detail: err.to_string(),
776 },
777 Error::InsufficientFunds => ErrorResponse {
778 code: ErrorCode::TransactionUnbalanced,
779 detail: err.to_string(),
780 },
781 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
782 code: ErrorCode::AmountOutofLimitRange,
783 detail: err.to_string(),
784 },
785 Error::ExpiredQuote(_, _) => ErrorResponse {
786 code: ErrorCode::QuoteExpired,
787 detail: err.to_string(),
788 },
789 Error::PendingQuote => ErrorResponse {
790 code: ErrorCode::QuotePending,
791 detail: err.to_string(),
792 },
793 Error::TokenPending => ErrorResponse {
794 code: ErrorCode::TokenPending,
795 detail: err.to_string(),
796 },
797 Error::ClearAuthRequired => ErrorResponse {
798 code: ErrorCode::ClearAuthRequired,
799 detail: Error::ClearAuthRequired.to_string(),
800 },
801 Error::ClearAuthFailed => ErrorResponse {
802 code: ErrorCode::ClearAuthFailed,
803 detail: Error::ClearAuthFailed.to_string(),
804 },
805 Error::BlindAuthRequired => ErrorResponse {
806 code: ErrorCode::BlindAuthRequired,
807 detail: Error::BlindAuthRequired.to_string(),
808 },
809 Error::BlindAuthFailed => ErrorResponse {
810 code: ErrorCode::BlindAuthFailed,
811 detail: Error::BlindAuthFailed.to_string(),
812 },
813 Error::NUT20(err) => ErrorResponse {
814 code: ErrorCode::WitnessMissingOrInvalid,
815 detail: err.to_string(),
816 },
817 Error::DuplicateInputs => ErrorResponse {
818 code: ErrorCode::DuplicateInputs,
819 detail: err.to_string(),
820 },
821 Error::DuplicateOutputs => ErrorResponse {
822 code: ErrorCode::DuplicateOutputs,
823 detail: err.to_string(),
824 },
825 Error::MultipleUnits => ErrorResponse {
826 code: ErrorCode::MultipleUnits,
827 detail: err.to_string(),
828 },
829 Error::UnitMismatch => ErrorResponse {
830 code: ErrorCode::UnitMismatch,
831 detail: err.to_string(),
832 },
833 Error::UnpaidQuote => ErrorResponse {
834 code: ErrorCode::QuoteNotPaid,
835 detail: Error::UnpaidQuote.to_string(),
836 },
837 Error::NUT11(err) => {
838 let code = map_nut11_error(&err);
839 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
840 Some("P2PK signatures are required but not provided".to_string())
841 } else {
842 None
843 };
844 ErrorResponse {
845 code,
846 detail: match extra {
847 Some(extra) => format!("{err}. {extra}"),
848 None => err.to_string(),
849 },
850 }
851 },
852 Error::DuplicateSignatureError => ErrorResponse {
853 code: ErrorCode::WitnessMissingOrInvalid,
854 detail: err.to_string(),
855 },
856 Error::IssuedQuote => ErrorResponse {
857 code: ErrorCode::TokensAlreadyIssued,
858 detail: err.to_string(),
859 },
860 Error::UnknownKeySet => ErrorResponse {
861 code: ErrorCode::KeysetNotFound,
862 detail: err.to_string(),
863 },
864 Error::InactiveKeyset => ErrorResponse {
865 code: ErrorCode::KeysetInactive,
866 detail: err.to_string(),
867 },
868 Error::AmountLessNotAllowed => ErrorResponse {
869 code: ErrorCode::AmountlessInvoiceNotSupported,
870 detail: err.to_string(),
871 },
872 Error::IncorrectQuoteAmount => ErrorResponse {
873 code: ErrorCode::IncorrectQuoteAmount,
874 detail: err.to_string(),
875 },
876 Error::PubkeyRequired => ErrorResponse {
877 code: ErrorCode::PubkeyRequired,
878 detail: err.to_string(),
879 },
880 Error::PaidQuote => ErrorResponse {
881 code: ErrorCode::InvoiceAlreadyPaid,
882 detail: err.to_string(),
883 },
884 Error::DuplicatePaymentId => ErrorResponse {
885 code: ErrorCode::InvoiceAlreadyPaid,
886 detail: err.to_string(),
887 },
888 Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
890 code: ErrorCode::InvoiceAlreadyPaid,
891 detail: "Invoice already paid or pending".to_string(),
892 },
893
894 Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
896 code: ErrorCode::TokenNotVerified,
897 detail: err.to_string(),
898 },
899 Error::DHKE(_) => ErrorResponse {
900 code: ErrorCode::Unknown(50000),
901 detail: err.to_string(),
902 },
903
904 Error::CouldNotVerifyDleq => ErrorResponse {
906 code: ErrorCode::TokenNotVerified,
907 detail: err.to_string(),
908 },
909 Error::SignatureMissingOrInvalid => ErrorResponse {
910 code: ErrorCode::WitnessMissingOrInvalid,
911 detail: err.to_string(),
912 },
913 Error::SigAllUsedInMelt => ErrorResponse {
914 code: ErrorCode::WitnessMissingOrInvalid,
915 detail: err.to_string(),
916 },
917
918 Error::AmountKey => ErrorResponse {
920 code: ErrorCode::KeysetNotFound,
921 detail: err.to_string(),
922 },
923 Error::KeysetUnknown(_) => ErrorResponse {
924 code: ErrorCode::KeysetNotFound,
925 detail: err.to_string(),
926 },
927 Error::NoActiveKeyset => ErrorResponse {
928 code: ErrorCode::KeysetInactive,
929 detail: err.to_string(),
930 },
931
932 Error::UnknownQuote => ErrorResponse {
934 code: ErrorCode::Unknown(50000),
935 detail: err.to_string(),
936 },
937 Error::MeltingDisabled => ErrorResponse {
938 code: ErrorCode::MintingDisabled,
939 detail: err.to_string(),
940 },
941 Error::PaymentPending => ErrorResponse {
942 code: ErrorCode::QuotePending,
943 detail: err.to_string(),
944 },
945 Error::UnknownPaymentState => ErrorResponse {
946 code: ErrorCode::Unknown(50000),
947 detail: err.to_string(),
948 },
949
950 Error::SplitValuesGreater => ErrorResponse {
952 code: ErrorCode::TransactionUnbalanced,
953 detail: err.to_string(),
954 },
955 Error::AmountOverflow => ErrorResponse {
956 code: ErrorCode::TransactionUnbalanced,
957 detail: err.to_string(),
958 },
959 Error::OverIssue => ErrorResponse {
960 code: ErrorCode::TransactionUnbalanced,
961 detail: err.to_string(),
962 },
963
964 Error::InvalidPaymentRequest => ErrorResponse {
966 code: ErrorCode::Unknown(50000),
967 detail: err.to_string(),
968 },
969 Error::InvoiceAmountUndefined => ErrorResponse {
970 code: ErrorCode::AmountlessInvoiceNotSupported,
971 detail: err.to_string(),
972 },
973
974 Error::Internal => ErrorResponse {
976 code: ErrorCode::Unknown(50000),
977 detail: err.to_string(),
978 },
979 Error::Database(_) => ErrorResponse {
980 code: ErrorCode::Unknown(50000),
981 detail: err.to_string(),
982 },
983 Error::ConcurrentUpdate => ErrorResponse {
984 code: ErrorCode::ConcurrentUpdate,
985 detail: err.to_string(),
986 },
987 Error::MaxInputsExceeded { .. } => ErrorResponse {
988 code: ErrorCode::MaxInputsExceeded,
989 detail: err.to_string()
990 },
991 Error::MaxOutputsExceeded { .. } => ErrorResponse {
992 code: ErrorCode::MaxOutputsExceeded,
993 detail: err.to_string()
994 },
995 _ => ErrorResponse {
997 code: ErrorCode::Unknown(50000),
998 detail: err.to_string(),
999 },
1000 }
1001 }
1002}
1003
1004#[cfg(feature = "mint")]
1005impl From<crate::database::Error> for Error {
1006 fn from(db_error: crate::database::Error) -> Self {
1007 match db_error {
1008 crate::database::Error::InvalidStateTransition(state) => match state {
1009 crate::state::Error::Pending => Self::TokenPending,
1010 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
1011 crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
1012 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
1013 },
1014 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1015 db_error => Self::Database(db_error),
1016 }
1017 }
1018}
1019
1020#[cfg(not(feature = "mint"))]
1021impl From<crate::database::Error> for Error {
1022 fn from(db_error: crate::database::Error) -> Self {
1023 match db_error {
1024 crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
1025 db_error => Self::Database(db_error),
1026 }
1027 }
1028}
1029
1030impl From<ErrorResponse> for Error {
1031 fn from(err: ErrorResponse) -> Error {
1032 match err.code {
1033 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1035 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1037 ErrorCode::TokenPending => Self::TokenPending,
1038 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1039 ErrorCode::OutputsPending => Self::TokenPending, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1041 ErrorCode::AmountOutofLimitRange => {
1042 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1043 }
1044 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1045 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1046 ErrorCode::MultipleUnits => Self::MultipleUnits,
1047 ErrorCode::UnitMismatch => Self::UnitMismatch,
1048 ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1049 ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1050 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1051 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1053 ErrorCode::KeysetInactive => Self::InactiveKeyset,
1054 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1056 ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1057 ErrorCode::MintingDisabled => Self::MintingDisabled,
1058 ErrorCode::LightningError => Self::PaymentFailed,
1059 ErrorCode::QuotePending => Self::PendingQuote,
1060 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1061 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1062 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1063 ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1064 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1066 ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1067 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1069 ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1070 ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1071 ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1072 _ => Self::UnknownErrorResponse(err.to_string()),
1073 }
1074 }
1075}
1076
1077#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1079#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
1080pub enum ErrorCode {
1081 TokenNotVerified,
1084
1085 TokenAlreadySpent,
1088 TokenPending,
1090 BlindedMessageAlreadySigned,
1092 OutputsPending,
1094 TransactionUnbalanced,
1096 AmountOutofLimitRange,
1098 DuplicateInputs,
1100 DuplicateOutputs,
1102 MultipleUnits,
1104 UnitMismatch,
1106 AmountlessInvoiceNotSupported,
1108 IncorrectQuoteAmount,
1110 UnsupportedUnit,
1112 MaxInputsExceeded,
1114 MaxOutputsExceeded,
1116 KeysetNotFound,
1119 KeysetInactive,
1121
1122 QuoteNotPaid,
1125 TokensAlreadyIssued,
1127 MintingDisabled,
1129 LightningError,
1131 QuotePending,
1133 InvoiceAlreadyPaid,
1135 QuoteExpired,
1137 WitnessMissingOrInvalid,
1139 PubkeyRequired,
1141
1142 ClearAuthRequired,
1145 ClearAuthFailed,
1147
1148 BlindAuthRequired,
1151 BlindAuthFailed,
1153 BatMintMaxExceeded,
1155 BatRateLimitExceeded,
1157
1158 ConcurrentUpdate,
1160
1161 Unknown(u16),
1163}
1164
1165impl ErrorCode {
1166 pub fn from_code(code: u16) -> Self {
1168 match code {
1169 10001 => Self::TokenNotVerified,
1171 11001 => Self::TokenAlreadySpent,
1173 11002 => Self::TokenPending,
1174 11003 => Self::BlindedMessageAlreadySigned,
1175 11004 => Self::OutputsPending,
1176 11005 => Self::TransactionUnbalanced,
1177 11006 => Self::AmountOutofLimitRange,
1178 11007 => Self::DuplicateInputs,
1179 11008 => Self::DuplicateOutputs,
1180 11009 => Self::MultipleUnits,
1181 11010 => Self::UnitMismatch,
1182 11011 => Self::AmountlessInvoiceNotSupported,
1183 11012 => Self::IncorrectQuoteAmount,
1184 11013 => Self::UnsupportedUnit,
1185 11014 => Self::MaxInputsExceeded,
1186 11015 => Self::MaxOutputsExceeded,
1187 12001 => Self::KeysetNotFound,
1189 12002 => Self::KeysetInactive,
1190 20001 => Self::QuoteNotPaid,
1192 20002 => Self::TokensAlreadyIssued,
1193 20003 => Self::MintingDisabled,
1194 20004 => Self::LightningError,
1195 20005 => Self::QuotePending,
1196 20006 => Self::InvoiceAlreadyPaid,
1197 20007 => Self::QuoteExpired,
1198 20008 => Self::WitnessMissingOrInvalid,
1199 20009 => Self::PubkeyRequired,
1200 30001 => Self::ClearAuthRequired,
1202 30002 => Self::ClearAuthFailed,
1203 31001 => Self::BlindAuthRequired,
1205 31002 => Self::BlindAuthFailed,
1206 31003 => Self::BatMintMaxExceeded,
1207 31004 => Self::BatRateLimitExceeded,
1208 _ => Self::Unknown(code),
1209 }
1210 }
1211
1212 pub fn to_code(&self) -> u16 {
1214 match self {
1215 Self::TokenNotVerified => 10001,
1217 Self::TokenAlreadySpent => 11001,
1219 Self::TokenPending => 11002,
1220 Self::BlindedMessageAlreadySigned => 11003,
1221 Self::OutputsPending => 11004,
1222 Self::TransactionUnbalanced => 11005,
1223 Self::AmountOutofLimitRange => 11006,
1224 Self::DuplicateInputs => 11007,
1225 Self::DuplicateOutputs => 11008,
1226 Self::MultipleUnits => 11009,
1227 Self::UnitMismatch => 11010,
1228 Self::AmountlessInvoiceNotSupported => 11011,
1229 Self::IncorrectQuoteAmount => 11012,
1230 Self::UnsupportedUnit => 11013,
1231 Self::MaxInputsExceeded => 11014,
1232 Self::MaxOutputsExceeded => 11015,
1233 Self::KeysetNotFound => 12001,
1235 Self::KeysetInactive => 12002,
1236 Self::QuoteNotPaid => 20001,
1238 Self::TokensAlreadyIssued => 20002,
1239 Self::MintingDisabled => 20003,
1240 Self::LightningError => 20004,
1241 Self::QuotePending => 20005,
1242 Self::InvoiceAlreadyPaid => 20006,
1243 Self::QuoteExpired => 20007,
1244 Self::WitnessMissingOrInvalid => 20008,
1245 Self::PubkeyRequired => 20009,
1246 Self::ClearAuthRequired => 30001,
1248 Self::ClearAuthFailed => 30002,
1249 Self::BlindAuthRequired => 31001,
1251 Self::BlindAuthFailed => 31002,
1252 Self::BatMintMaxExceeded => 31003,
1253 Self::BatRateLimitExceeded => 31004,
1254 Self::ConcurrentUpdate => 50000,
1255 Self::Unknown(code) => *code,
1256 }
1257 }
1258}
1259
1260impl Serialize for ErrorCode {
1261 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1262 where
1263 S: Serializer,
1264 {
1265 serializer.serialize_u16(self.to_code())
1266 }
1267}
1268
1269impl<'de> Deserialize<'de> for ErrorCode {
1270 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1271 where
1272 D: Deserializer<'de>,
1273 {
1274 let code = u16::deserialize(deserializer)?;
1275
1276 Ok(ErrorCode::from_code(code))
1277 }
1278}
1279
1280impl fmt::Display for ErrorCode {
1281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1282 write!(f, "{}", self.to_code())
1283 }
1284}