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("Signature missing or invalid")]
52 SignatureMissingOrInvalid,
53 #[error("Amount Less Invoice is not allowed")]
55 AmountLessNotAllowed,
56 #[error("Multi-Part Internal Melt Quotes are not supported")]
58 InternalMultiPartMeltQuote,
59 #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
61 MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
62 #[error("Clear Auth Required")]
64 ClearAuthRequired,
65 #[error("Blind Auth Required")]
67 BlindAuthRequired,
68 #[error("Clear Auth Failed")]
70 ClearAuthFailed,
71 #[error("Blind Auth Failed")]
73 BlindAuthFailed,
74 #[error("Auth settings undefined")]
76 AuthSettingsUndefined,
77 #[error("Mint time outside of tolerance")]
79 MintTimeExceedsTolerance,
80 #[error("Insufficient blind auth tokens, must reauth")]
82 InsufficientBlindAuthTokens,
83 #[error("Auth localstore undefined")]
85 AuthLocalstoreUndefined,
86 #[error("Wallet cat not set")]
88 CatNotSet,
89 #[error("Could not get mint info")]
91 CouldNotGetMintInfo,
92 #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
94 AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
95 #[error("Payment id seen for mint")]
97 DuplicatePaymentId,
98 #[error("Pubkey required")]
100 PubkeyRequired,
101 #[error("Invalid payment method")]
103 InvalidPaymentMethod,
104 #[error("Amount undefined")]
106 AmountUndefined,
107 #[error("Payment method unsupported")]
109 UnsupportedPaymentMethod,
110 #[error("Could not parse bolt12")]
112 Bolt12parse,
113
114 #[error("Failed to parse BIP353 address: {0}")]
116 Bip353Parse(String),
117
118 #[error("Operation timeout")]
120 Timeout,
121
122 #[error("Failed to resolve BIP353 address: {0}")]
124 Bip353Resolve(String),
125 #[error("No Lightning offer found in BIP353 payment instructions")]
127 Bip353NoLightningOffer,
128
129 #[error("Internal send error: {0}")]
131 SendError(String),
132
133 #[error("Internal receive error: {0}")]
135 RecvError(String),
136
137 #[error("Minting is disabled")]
140 MintingDisabled,
141 #[error("Unknown quote")]
143 UnknownQuote,
144 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
146 ExpiredQuote(u64, u64),
147 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
149 AmountOutofLimitRange(Amount, Amount, Amount),
150 #[error("Quote not paid")]
152 UnpaidQuote,
153 #[error("Quote pending")]
155 PendingQuote,
156 #[error("Quote already issued")]
158 IssuedQuote,
159 #[error("Quote is already paid")]
161 PaidQuote,
162 #[error("Payment state is unknown")]
164 UnknownPaymentState,
165 #[error("Minting is disabled")]
167 MeltingDisabled,
168 #[error("Unknown Keyset")]
170 UnknownKeySet,
171 #[error("Blinded Message is already signed")]
173 BlindedMessageAlreadySigned,
174 #[error("Inactive Keyset")]
176 InactiveKeyset,
177 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
179 TransactionUnbalanced(u64, u64, u64),
180 #[error("Duplicate Inputs")]
182 DuplicateInputs,
183 #[error("Duplicate outputs")]
185 DuplicateOutputs,
186 #[error("Cannot have multiple units")]
188 MultipleUnits,
189 #[error("Input unit must match output")]
191 UnitMismatch,
192 #[error("Sig all cannot be used in melt")]
194 SigAllUsedInMelt,
195 #[error("Token Already Spent")]
197 TokenAlreadySpent,
198 #[error("Token Pending")]
200 TokenPending,
201 #[error("Internal Error")]
203 Internal,
204 #[error("Oidc client not set")]
206 OidcNotSet,
207
208 #[error("P2PK condition not met `{0}`")]
211 P2PKConditionsNotMet(String),
212 #[error("Spending condition locktime not provided")]
214 LocktimeNotProvided,
215 #[error("Invalid spending conditions: `{0}`")]
217 InvalidSpendConditions(String),
218 #[error("Incorrect wallet: `{0}`")]
220 IncorrectWallet(String),
221 #[error("Unknown wallet: `{0}`")]
223 #[cfg(feature = "wallet")]
224 UnknownWallet(WalletKey),
225 #[error("Max fee exceeded")]
227 MaxFeeExceeded,
228 #[error("Url path segments could not be joined")]
230 UrlPathSegments,
231 #[error("Unknown error response: `{0}`")]
233 UnknownErrorResponse(String),
234 #[error("Could not verify DLEQ proof")]
236 CouldNotVerifyDleq,
237 #[error("Dleq proof not provided for signature")]
239 DleqProofNotProvided,
240 #[error("Token does not match wallet mint")]
243 IncorrectMint,
244 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
246 MultiMintTokenNotSupported,
247 #[error("Preimage not provided")]
249 PreimageNotProvided,
250 #[error("Insufficient funds")]
252 InsufficientFunds,
253 #[error("Unexpected proof state")]
255 UnexpectedProofState,
256 #[error("No active keyset")]
258 NoActiveKeyset,
259 #[error("Incorrect quote amount")]
261 IncorrectQuoteAmount,
262 #[error("Invoice Description not supported")]
264 InvoiceDescriptionUnsupported,
265 #[error("Invalid transaction direction")]
267 InvalidTransactionDirection,
268 #[error("Invalid transaction id")]
270 InvalidTransactionId,
271 #[error("Transaction not found")]
273 TransactionNotFound,
274 #[error("`{0}`")]
276 Custom(String),
277
278 #[error(transparent)]
281 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
282 #[error(transparent)]
284 Bip32(#[from] bitcoin::bip32::Error),
285 #[error(transparent)]
287 ParseInt(#[from] std::num::ParseIntError),
288 #[error(transparent)]
290 UrlParseError(#[from] url::ParseError),
291 #[error(transparent)]
293 Utf8ParseError(#[from] std::string::FromUtf8Error),
294 #[error(transparent)]
296 SerdeJsonError(#[from] serde_json::Error),
297 #[error(transparent)]
299 Base64Error(#[from] bitcoin::base64::DecodeError),
300 #[error(transparent)]
302 HexError(#[from] hex::Error),
303 #[error("Http transport error {0:?}: {1}")]
305 HttpError(Option<u16>, String),
306 #[cfg(feature = "wallet")]
307 #[error(transparent)]
310 CashuUrl(#[from] crate::mint_url::Error),
311 #[error(transparent)]
313 Secret(#[from] crate::secret::Error),
314 #[error(transparent)]
316 AmountError(#[from] crate::amount::Error),
317 #[error(transparent)]
319 DHKE(#[from] crate::dhke::Error),
320 #[error(transparent)]
322 NUT00(#[from] crate::nuts::nut00::Error),
323 #[error(transparent)]
325 NUT01(#[from] crate::nuts::nut01::Error),
326 #[error(transparent)]
328 NUT02(#[from] crate::nuts::nut02::Error),
329 #[error(transparent)]
331 NUT03(#[from] crate::nuts::nut03::Error),
332 #[error(transparent)]
334 NUT04(#[from] crate::nuts::nut04::Error),
335 #[error(transparent)]
337 NUT05(#[from] crate::nuts::nut05::Error),
338 #[error(transparent)]
340 NUT11(#[from] crate::nuts::nut11::Error),
341 #[error(transparent)]
343 NUT12(#[from] crate::nuts::nut12::Error),
344 #[error(transparent)]
346 #[cfg(feature = "wallet")]
347 NUT13(#[from] crate::nuts::nut13::Error),
348 #[error(transparent)]
350 NUT14(#[from] crate::nuts::nut14::Error),
351 #[error(transparent)]
353 NUT18(#[from] crate::nuts::nut18::Error),
354 #[error(transparent)]
356 NUT20(#[from] crate::nuts::nut20::Error),
357 #[error(transparent)]
359 NUT21(#[from] crate::nuts::nut21::Error),
360 #[error(transparent)]
362 NUT22(#[from] crate::nuts::nut22::Error),
363 #[error(transparent)]
365 NUT23(#[from] crate::nuts::nut23::Error),
366 #[error(transparent)]
368 TryFromSliceError(#[from] TryFromSliceError),
369 #[error(transparent)]
371 Database(crate::database::Error),
372 #[error(transparent)]
374 #[cfg(feature = "mint")]
375 Payment(#[from] crate::payment::Error),
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
382#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
383pub struct ErrorResponse {
384 pub code: ErrorCode,
386 pub error: Option<String>,
388 pub detail: Option<String>,
390}
391
392impl fmt::Display for ErrorResponse {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 write!(
395 f,
396 "code: {}, error: {}, detail: {}",
397 self.code,
398 self.error.clone().unwrap_or_default(),
399 self.detail.clone().unwrap_or_default()
400 )
401 }
402}
403
404impl ErrorResponse {
405 pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
407 Self {
408 code,
409 error,
410 detail,
411 }
412 }
413
414 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
416 let value: Value = serde_json::from_str(json)?;
417
418 Self::from_value(value)
419 }
420
421 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
423 match serde_json::from_value::<ErrorResponse>(value.clone()) {
424 Ok(res) => Ok(res),
425 Err(_) => Ok(Self {
426 code: ErrorCode::Unknown(999),
427 error: Some(value.to_string()),
428 detail: None,
429 }),
430 }
431 }
432}
433
434impl From<Error> for ErrorResponse {
435 fn from(err: Error) -> ErrorResponse {
436 match err {
437 Error::TokenAlreadySpent => ErrorResponse {
438 code: ErrorCode::TokenAlreadySpent,
439 error: Some(err.to_string()),
440 detail: None,
441 },
442 Error::UnsupportedUnit => ErrorResponse {
443 code: ErrorCode::UnsupportedUnit,
444 error: Some(err.to_string()),
445 detail: None,
446 },
447 Error::PaymentFailed => ErrorResponse {
448 code: ErrorCode::LightningError,
449 error: Some(err.to_string()),
450 detail: None,
451 },
452 Error::RequestAlreadyPaid => ErrorResponse {
453 code: ErrorCode::InvoiceAlreadyPaid,
454 error: Some("Invoice already paid.".to_string()),
455 detail: None,
456 },
457 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
458 ErrorResponse {
459 code: ErrorCode::TransactionUnbalanced,
460 error: Some(format!(
461 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}",
462 )),
463 detail: Some("Transaction inputs should equal outputs less fee".to_string()),
464 }
465 }
466 Error::MintingDisabled => ErrorResponse {
467 code: ErrorCode::MintingDisabled,
468 error: Some(err.to_string()),
469 detail: None,
470 },
471 Error::BlindedMessageAlreadySigned => ErrorResponse {
472 code: ErrorCode::BlindedMessageAlreadySigned,
473 error: Some(err.to_string()),
474 detail: None,
475 },
476 Error::InsufficientFunds => ErrorResponse {
477 code: ErrorCode::TransactionUnbalanced,
478 error: Some(err.to_string()),
479 detail: None,
480 },
481 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
482 code: ErrorCode::AmountOutofLimitRange,
483 error: Some(err.to_string()),
484 detail: None,
485 },
486 Error::ExpiredQuote(_, _) => ErrorResponse {
487 code: ErrorCode::QuoteExpired,
488 error: Some(err.to_string()),
489 detail: None,
490 },
491 Error::PendingQuote => ErrorResponse {
492 code: ErrorCode::QuotePending,
493 error: Some(err.to_string()),
494 detail: None,
495 },
496 Error::TokenPending => ErrorResponse {
497 code: ErrorCode::TokenPending,
498 error: Some(err.to_string()),
499 detail: None,
500 },
501 Error::ClearAuthRequired => ErrorResponse {
502 code: ErrorCode::ClearAuthRequired,
503 error: None,
504 detail: None,
505 },
506 Error::ClearAuthFailed => ErrorResponse {
507 code: ErrorCode::ClearAuthFailed,
508 error: None,
509 detail: None,
510 },
511 Error::BlindAuthRequired => ErrorResponse {
512 code: ErrorCode::BlindAuthRequired,
513 error: None,
514 detail: None,
515 },
516 Error::BlindAuthFailed => ErrorResponse {
517 code: ErrorCode::BlindAuthFailed,
518 error: None,
519 detail: None,
520 },
521 Error::NUT20(err) => ErrorResponse {
522 code: ErrorCode::WitnessMissingOrInvalid,
523 error: Some(err.to_string()),
524 detail: None,
525 },
526 Error::DuplicateInputs => ErrorResponse {
527 code: ErrorCode::DuplicateInputs,
528 error: Some(err.to_string()),
529 detail: None,
530 },
531 Error::DuplicateOutputs => ErrorResponse {
532 code: ErrorCode::DuplicateOutputs,
533 error: Some(err.to_string()),
534 detail: None,
535 },
536 Error::MultipleUnits => ErrorResponse {
537 code: ErrorCode::MultipleUnits,
538 error: Some(err.to_string()),
539 detail: None,
540 },
541 Error::UnitMismatch => ErrorResponse {
542 code: ErrorCode::UnitMismatch,
543 error: Some(err.to_string()),
544 detail: None,
545 },
546 Error::UnpaidQuote => ErrorResponse {
547 code: ErrorCode::QuoteNotPaid,
548 error: Some(err.to_string()),
549 detail: None
550 },
551 _ => ErrorResponse {
552 code: ErrorCode::Unknown(9999),
553 error: Some(err.to_string()),
554 detail: None,
555 },
556 }
557 }
558}
559
560#[cfg(feature = "mint")]
561impl From<crate::database::Error> for Error {
562 fn from(db_error: crate::database::Error) -> Self {
563 match db_error {
564 crate::database::Error::InvalidStateTransition(state) => match state {
565 crate::state::Error::Pending => Self::TokenPending,
566 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
567 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
568 },
569 db_error => Self::Database(db_error),
570 }
571 }
572}
573
574#[cfg(not(feature = "mint"))]
575impl From<crate::database::Error> for Error {
576 fn from(db_error: crate::database::Error) -> Self {
577 Self::Database(db_error)
578 }
579}
580
581impl From<ErrorResponse> for Error {
582 fn from(err: ErrorResponse) -> Error {
583 match err.code {
584 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
585 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
586 ErrorCode::QuotePending => Self::PendingQuote,
587 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
588 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
589 ErrorCode::KeysetInactive => Self::InactiveKeyset,
590 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
591 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
592 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
593 ErrorCode::MintingDisabled => Self::MintingDisabled,
594 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
595 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
596 ErrorCode::LightningError => Self::PaymentFailed,
597 ErrorCode::AmountOutofLimitRange => {
598 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
599 }
600 ErrorCode::TokenPending => Self::TokenPending,
601 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
602 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
603 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
604 ErrorCode::MultipleUnits => Self::MultipleUnits,
605 ErrorCode::UnitMismatch => Self::UnitMismatch,
606 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
607 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
608 _ => Self::UnknownErrorResponse(err.to_string()),
609 }
610 }
611}
612
613#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
615#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
616pub enum ErrorCode {
617 TokenAlreadySpent,
619 TokenPending,
621 QuoteNotPaid,
623 QuoteExpired,
625 QuotePending,
627 KeysetNotFound,
629 KeysetInactive,
631 BlindedMessageAlreadySigned,
633 UnsupportedUnit,
635 TokensAlreadyIssued,
637 MintingDisabled,
639 InvoiceAlreadyPaid,
641 TokenNotVerified,
643 LightningError,
645 TransactionUnbalanced,
647 AmountOutofLimitRange,
649 WitnessMissingOrInvalid,
651 DuplicateInputs,
653 DuplicateOutputs,
655 MultipleUnits,
657 UnitMismatch,
659 ClearAuthRequired,
661 ClearAuthFailed,
663 BlindAuthRequired,
665 BlindAuthFailed,
667 Unknown(u16),
669}
670
671impl ErrorCode {
672 pub fn from_code(code: u16) -> Self {
674 match code {
675 10002 => Self::BlindedMessageAlreadySigned,
676 10003 => Self::TokenNotVerified,
677 11001 => Self::TokenAlreadySpent,
678 11002 => Self::TransactionUnbalanced,
679 11005 => Self::UnsupportedUnit,
680 11006 => Self::AmountOutofLimitRange,
681 11007 => Self::DuplicateInputs,
682 11008 => Self::DuplicateOutputs,
683 11009 => Self::MultipleUnits,
684 11010 => Self::UnitMismatch,
685 11012 => Self::TokenPending,
686 12001 => Self::KeysetNotFound,
687 12002 => Self::KeysetInactive,
688 20000 => Self::LightningError,
689 20001 => Self::QuoteNotPaid,
690 20002 => Self::TokensAlreadyIssued,
691 20003 => Self::MintingDisabled,
692 20005 => Self::QuotePending,
693 20006 => Self::InvoiceAlreadyPaid,
694 20007 => Self::QuoteExpired,
695 20008 => Self::WitnessMissingOrInvalid,
696 30001 => Self::ClearAuthRequired,
697 30002 => Self::ClearAuthFailed,
698 31001 => Self::BlindAuthRequired,
699 31002 => Self::BlindAuthFailed,
700 _ => Self::Unknown(code),
701 }
702 }
703
704 pub fn to_code(&self) -> u16 {
706 match self {
707 Self::BlindedMessageAlreadySigned => 10002,
708 Self::TokenNotVerified => 10003,
709 Self::TokenAlreadySpent => 11001,
710 Self::TransactionUnbalanced => 11002,
711 Self::UnsupportedUnit => 11005,
712 Self::AmountOutofLimitRange => 11006,
713 Self::DuplicateInputs => 11007,
714 Self::DuplicateOutputs => 11008,
715 Self::MultipleUnits => 11009,
716 Self::UnitMismatch => 11010,
717 Self::TokenPending => 11012,
718 Self::KeysetNotFound => 12001,
719 Self::KeysetInactive => 12002,
720 Self::LightningError => 20000,
721 Self::QuoteNotPaid => 20001,
722 Self::TokensAlreadyIssued => 20002,
723 Self::MintingDisabled => 20003,
724 Self::QuotePending => 20005,
725 Self::InvoiceAlreadyPaid => 20006,
726 Self::QuoteExpired => 20007,
727 Self::WitnessMissingOrInvalid => 20008,
728 Self::ClearAuthRequired => 30001,
729 Self::ClearAuthFailed => 30002,
730 Self::BlindAuthRequired => 31001,
731 Self::BlindAuthFailed => 31002,
732 Self::Unknown(code) => *code,
733 }
734 }
735}
736
737impl Serialize for ErrorCode {
738 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
739 where
740 S: Serializer,
741 {
742 serializer.serialize_u16(self.to_code())
743 }
744}
745
746impl<'de> Deserialize<'de> for ErrorCode {
747 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
748 where
749 D: Deserializer<'de>,
750 {
751 let code = u16::deserialize(deserializer)?;
752
753 Ok(ErrorCode::from_code(code))
754 }
755}
756
757impl fmt::Display for ErrorCode {
758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759 write!(f, "{}", self.to_code())
760 }
761}