1use std::fmt;
4
5use cashu::{CurrencyUnit, PaymentMethod};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use serde_json::Value;
8use thiserror::Error;
9
10use crate::nuts::Id;
11use crate::util::hex;
12#[cfg(feature = "wallet")]
13use crate::wallet::WalletKey;
14use crate::Amount;
15
16#[derive(Debug, Error)]
18pub enum Error {
19 #[error("No Key for Amount")]
21 AmountKey,
22 #[error("Keyset id not known: `{0}`")]
24 KeysetUnknown(Id),
25 #[error("Unit unsupported")]
27 UnsupportedUnit,
28 #[error("Payment failed")]
30 PaymentFailed,
31 #[error("Payment pending")]
33 PaymentPending,
34 #[error("Request already paid")]
36 RequestAlreadyPaid,
37 #[error("Invalid payment request")]
39 InvalidPaymentRequest,
40 #[error("Invoice Amount undefined")]
42 InvoiceAmountUndefined,
43 #[error("Split Values must be less then or equal to amount")]
45 SplitValuesGreater,
46 #[error("Amount Overflow")]
48 AmountOverflow,
49 #[error("Signature missing or invalid")]
51 SignatureMissingOrInvalid,
52 #[error("Amount Less Invoice is not allowed")]
54 AmountLessNotAllowed,
55 #[error("Multi-Part Internal Melt Quotes are not supported")]
57 InternalMultiPartMeltQuote,
58 #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
60 MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
61 #[error("Clear Auth Required")]
63 ClearAuthRequired,
64 #[error("Blind Auth Required")]
66 BlindAuthRequired,
67 #[error("Clear Auth Failed")]
69 ClearAuthFailed,
70 #[error("Blind Auth Failed")]
72 BlindAuthFailed,
73 #[error("Auth settings undefined")]
75 AuthSettingsUndefined,
76 #[error("Mint time outside of tolerance")]
78 MintTimeExceedsTolerance,
79 #[error("Insufficient blind auth tokens, must reauth")]
81 InsufficientBlindAuthTokens,
82 #[error("Auth localstore undefined")]
84 AuthLocalstoreUndefined,
85 #[error("Wallet cat not set")]
87 CatNotSet,
88 #[error("Could not get mint info")]
90 CouldNotGetMintInfo,
91 #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
93 AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
94
95 #[error("Minting is disabled")]
98 MintingDisabled,
99 #[error("Unknown quote")]
101 UnknownQuote,
102 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
104 ExpiredQuote(u64, u64),
105 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
107 AmountOutofLimitRange(Amount, Amount, Amount),
108 #[error("Quote not paid")]
110 UnpaidQuote,
111 #[error("Quote pending")]
113 PendingQuote,
114 #[error("Quote already issued")]
116 IssuedQuote,
117 #[error("Quote is already paid")]
119 PaidQuote,
120 #[error("Payment state is unknown")]
122 UnknownPaymentState,
123 #[error("Minting is disabled")]
125 MeltingDisabled,
126 #[error("Unknown Keyset")]
128 UnknownKeySet,
129 #[error("Blinded Message is already signed")]
131 BlindedMessageAlreadySigned,
132 #[error("Inactive Keyset")]
134 InactiveKeyset,
135 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
137 TransactionUnbalanced(u64, u64, u64),
138 #[error("Duplicate Inputs")]
140 DuplicateInputs,
141 #[error("Duplicate outputs")]
143 DuplicateOutputs,
144 #[error("Cannot have multiple units")]
146 MultipleUnits,
147 #[error("Input unit must match output")]
149 UnitMismatch,
150 #[error("Sig all cannot be used in melt")]
152 SigAllUsedInMelt,
153 #[error("Token Already Spent")]
155 TokenAlreadySpent,
156 #[error("Token Pending")]
158 TokenPending,
159 #[error("Internal Error")]
161 Internal,
162 #[error("Oidc client not set")]
164 OidcNotSet,
165
166 #[error("P2PK condition not met `{0}`")]
169 P2PKConditionsNotMet(String),
170 #[error("Spending condition locktime not provided")]
172 LocktimeNotProvided,
173 #[error("Invalid spending conditions: `{0}`")]
175 InvalidSpendConditions(String),
176 #[error("Incorrect wallet: `{0}`")]
178 IncorrectWallet(String),
179 #[error("Unknown wallet: `{0}`")]
181 #[cfg(feature = "wallet")]
182 UnknownWallet(WalletKey),
183 #[error("Max fee exceeded")]
185 MaxFeeExceeded,
186 #[error("Url path segments could not be joined")]
188 UrlPathSegments,
189 #[error("Unknown error response: `{0}`")]
191 UnknownErrorResponse(String),
192 #[error("Could not verify DLEQ proof")]
194 CouldNotVerifyDleq,
195 #[error("Dleq proof not provided for signature")]
197 DleqProofNotProvided,
198 #[error("Token does not match wallet mint")]
201 IncorrectMint,
202 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
204 MultiMintTokenNotSupported,
205 #[error("Preimage not provided")]
207 PreimageNotProvided,
208 #[error("Insufficient funds")]
210 InsufficientFunds,
211 #[error("Unexpected proof state")]
213 UnexpectedProofState,
214 #[error("No active keyset")]
216 NoActiveKeyset,
217 #[error("Incorrect quote amount")]
219 IncorrectQuoteAmount,
220 #[error("Invoice Description not supported")]
222 InvoiceDescriptionUnsupported,
223 #[error("Invalid transaction direction")]
225 InvalidTransactionDirection,
226 #[error("Invalid transaction id")]
228 InvalidTransactionId,
229 #[error("`{0}`")]
231 Custom(String),
232
233 #[error(transparent)]
236 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
237 #[error(transparent)]
239 Bip32(#[from] bitcoin::bip32::Error),
240 #[error(transparent)]
242 ParseInt(#[from] std::num::ParseIntError),
243 #[error(transparent)]
245 UrlParseError(#[from] url::ParseError),
246 #[error(transparent)]
248 Utf8ParseError(#[from] std::string::FromUtf8Error),
249 #[error(transparent)]
251 SerdeJsonError(#[from] serde_json::Error),
252 #[error(transparent)]
254 Base64Error(#[from] bitcoin::base64::DecodeError),
255 #[error(transparent)]
257 HexError(#[from] hex::Error),
258 #[error("Http transport error: {0}")]
260 HttpError(String),
261 #[cfg(feature = "wallet")]
262 #[error(transparent)]
265 CashuUrl(#[from] crate::mint_url::Error),
266 #[error(transparent)]
268 Secret(#[from] crate::secret::Error),
269 #[error(transparent)]
271 AmountError(#[from] crate::amount::Error),
272 #[error(transparent)]
274 DHKE(#[from] crate::dhke::Error),
275 #[error(transparent)]
277 NUT00(#[from] crate::nuts::nut00::Error),
278 #[error(transparent)]
280 NUT01(#[from] crate::nuts::nut01::Error),
281 #[error(transparent)]
283 NUT02(#[from] crate::nuts::nut02::Error),
284 #[error(transparent)]
286 NUT03(#[from] crate::nuts::nut03::Error),
287 #[error(transparent)]
289 NUT04(#[from] crate::nuts::nut04::Error),
290 #[error(transparent)]
292 NUT05(#[from] crate::nuts::nut05::Error),
293 #[error(transparent)]
295 NUT11(#[from] crate::nuts::nut11::Error),
296 #[error(transparent)]
298 NUT12(#[from] crate::nuts::nut12::Error),
299 #[error(transparent)]
301 #[cfg(feature = "wallet")]
302 NUT13(#[from] crate::nuts::nut13::Error),
303 #[error(transparent)]
305 NUT14(#[from] crate::nuts::nut14::Error),
306 #[error(transparent)]
308 NUT18(#[from] crate::nuts::nut18::Error),
309 #[error(transparent)]
311 NUT20(#[from] crate::nuts::nut20::Error),
312 #[error(transparent)]
314 NUT21(#[from] crate::nuts::nut21::Error),
315 #[error(transparent)]
317 NUT22(#[from] crate::nuts::nut22::Error),
318 #[error(transparent)]
320 Database(crate::database::Error),
321 #[error(transparent)]
323 #[cfg(feature = "mint")]
324 Payment(#[from] crate::payment::Error),
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
332pub struct ErrorResponse {
333 pub code: ErrorCode,
335 pub error: Option<String>,
337 pub detail: Option<String>,
339}
340
341impl fmt::Display for ErrorResponse {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 write!(
344 f,
345 "code: {}, error: {}, detail: {}",
346 self.code,
347 self.error.clone().unwrap_or_default(),
348 self.detail.clone().unwrap_or_default()
349 )
350 }
351}
352
353impl ErrorResponse {
354 pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
356 Self {
357 code,
358 error,
359 detail,
360 }
361 }
362
363 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
365 let value: Value = serde_json::from_str(json)?;
366
367 Self::from_value(value)
368 }
369
370 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
372 match serde_json::from_value::<ErrorResponse>(value.clone()) {
373 Ok(res) => Ok(res),
374 Err(_) => Ok(Self {
375 code: ErrorCode::Unknown(999),
376 error: Some(value.to_string()),
377 detail: None,
378 }),
379 }
380 }
381}
382
383impl From<Error> for ErrorResponse {
384 fn from(err: Error) -> ErrorResponse {
385 match err {
386 Error::TokenAlreadySpent => ErrorResponse {
387 code: ErrorCode::TokenAlreadySpent,
388 error: Some(err.to_string()),
389 detail: None,
390 },
391 Error::UnsupportedUnit => ErrorResponse {
392 code: ErrorCode::UnsupportedUnit,
393 error: Some(err.to_string()),
394 detail: None,
395 },
396 Error::PaymentFailed => ErrorResponse {
397 code: ErrorCode::LightningError,
398 error: Some(err.to_string()),
399 detail: None,
400 },
401 Error::RequestAlreadyPaid => ErrorResponse {
402 code: ErrorCode::InvoiceAlreadyPaid,
403 error: Some("Invoice already paid.".to_string()),
404 detail: None,
405 },
406 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
407 ErrorResponse {
408 code: ErrorCode::TransactionUnbalanced,
409 error: Some(format!(
410 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}",
411 )),
412 detail: Some("Transaction inputs should equal outputs less fee".to_string()),
413 }
414 }
415 Error::MintingDisabled => ErrorResponse {
416 code: ErrorCode::MintingDisabled,
417 error: Some(err.to_string()),
418 detail: None,
419 },
420 Error::BlindedMessageAlreadySigned => ErrorResponse {
421 code: ErrorCode::BlindedMessageAlreadySigned,
422 error: Some(err.to_string()),
423 detail: None,
424 },
425 Error::InsufficientFunds => ErrorResponse {
426 code: ErrorCode::TransactionUnbalanced,
427 error: Some(err.to_string()),
428 detail: None,
429 },
430 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
431 code: ErrorCode::AmountOutofLimitRange,
432 error: Some(err.to_string()),
433 detail: None,
434 },
435 Error::ExpiredQuote(_, _) => ErrorResponse {
436 code: ErrorCode::QuoteExpired,
437 error: Some(err.to_string()),
438 detail: None,
439 },
440 Error::PendingQuote => ErrorResponse {
441 code: ErrorCode::QuotePending,
442 error: Some(err.to_string()),
443 detail: None,
444 },
445 Error::TokenPending => ErrorResponse {
446 code: ErrorCode::TokenPending,
447 error: Some(err.to_string()),
448 detail: None,
449 },
450 Error::ClearAuthRequired => ErrorResponse {
451 code: ErrorCode::ClearAuthRequired,
452 error: None,
453 detail: None,
454 },
455 Error::ClearAuthFailed => ErrorResponse {
456 code: ErrorCode::ClearAuthFailed,
457 error: None,
458 detail: None,
459 },
460 Error::BlindAuthRequired => ErrorResponse {
461 code: ErrorCode::BlindAuthRequired,
462 error: None,
463 detail: None,
464 },
465 Error::BlindAuthFailed => ErrorResponse {
466 code: ErrorCode::BlindAuthFailed,
467 error: None,
468 detail: None,
469 },
470 Error::NUT20(err) => ErrorResponse {
471 code: ErrorCode::WitnessMissingOrInvalid,
472 error: Some(err.to_string()),
473 detail: None,
474 },
475 Error::DuplicateInputs => ErrorResponse {
476 code: ErrorCode::DuplicateInputs,
477 error: Some(err.to_string()),
478 detail: None,
479 },
480 Error::DuplicateOutputs => ErrorResponse {
481 code: ErrorCode::DuplicateOutputs,
482 error: Some(err.to_string()),
483 detail: None,
484 },
485 Error::MultipleUnits => ErrorResponse {
486 code: ErrorCode::MultipleUnits,
487 error: Some(err.to_string()),
488 detail: None,
489 },
490 Error::UnitMismatch => ErrorResponse {
491 code: ErrorCode::UnitMismatch,
492 error: Some(err.to_string()),
493 detail: None,
494 },
495 _ => ErrorResponse {
496 code: ErrorCode::Unknown(9999),
497 error: Some(err.to_string()),
498 detail: None,
499 },
500 }
501 }
502}
503
504#[cfg(feature = "mint")]
505impl From<crate::database::Error> for Error {
506 fn from(db_error: crate::database::Error) -> Self {
507 match db_error {
508 crate::database::Error::InvalidStateTransition(state) => match state {
509 crate::state::Error::Pending => Self::TokenPending,
510 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
511 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
512 },
513 db_error => Self::Database(db_error),
514 }
515 }
516}
517
518#[cfg(not(feature = "mint"))]
519impl From<crate::database::Error> for Error {
520 fn from(db_error: crate::database::Error) -> Self {
521 Self::Database(db_error)
522 }
523}
524
525impl From<ErrorResponse> for Error {
526 fn from(err: ErrorResponse) -> Error {
527 match err.code {
528 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
529 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
530 ErrorCode::QuotePending => Self::PendingQuote,
531 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
532 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
533 ErrorCode::KeysetInactive => Self::InactiveKeyset,
534 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
535 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
536 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
537 ErrorCode::MintingDisabled => Self::MintingDisabled,
538 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
539 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
540 ErrorCode::LightningError => Self::PaymentFailed,
541 ErrorCode::AmountOutofLimitRange => {
542 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
543 }
544 ErrorCode::TokenPending => Self::TokenPending,
545 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
546 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
547 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
548 ErrorCode::MultipleUnits => Self::MultipleUnits,
549 ErrorCode::UnitMismatch => Self::UnitMismatch,
550 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
551 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
552 _ => Self::UnknownErrorResponse(err.to_string()),
553 }
554 }
555}
556
557#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
559#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
560pub enum ErrorCode {
561 TokenAlreadySpent,
563 TokenPending,
565 QuoteNotPaid,
567 QuoteExpired,
569 QuotePending,
571 KeysetNotFound,
573 KeysetInactive,
575 BlindedMessageAlreadySigned,
577 UnsupportedUnit,
579 TokensAlreadyIssued,
581 MintingDisabled,
583 InvoiceAlreadyPaid,
585 TokenNotVerified,
587 LightningError,
589 TransactionUnbalanced,
591 AmountOutofLimitRange,
593 WitnessMissingOrInvalid,
595 DuplicateInputs,
597 DuplicateOutputs,
599 MultipleUnits,
601 UnitMismatch,
603 ClearAuthRequired,
605 ClearAuthFailed,
607 BlindAuthRequired,
609 BlindAuthFailed,
611 Unknown(u16),
613}
614
615impl ErrorCode {
616 pub fn from_code(code: u16) -> Self {
618 match code {
619 10002 => Self::BlindedMessageAlreadySigned,
620 10003 => Self::TokenNotVerified,
621 11001 => Self::TokenAlreadySpent,
622 11002 => Self::TransactionUnbalanced,
623 11005 => Self::UnsupportedUnit,
624 11006 => Self::AmountOutofLimitRange,
625 11007 => Self::DuplicateInputs,
626 11008 => Self::DuplicateOutputs,
627 11009 => Self::MultipleUnits,
628 11010 => Self::UnitMismatch,
629 11012 => Self::TokenPending,
630 12001 => Self::KeysetNotFound,
631 12002 => Self::KeysetInactive,
632 20000 => Self::LightningError,
633 20001 => Self::QuoteNotPaid,
634 20002 => Self::TokensAlreadyIssued,
635 20003 => Self::MintingDisabled,
636 20005 => Self::QuotePending,
637 20006 => Self::InvoiceAlreadyPaid,
638 20007 => Self::QuoteExpired,
639 20008 => Self::WitnessMissingOrInvalid,
640 30001 => Self::ClearAuthRequired,
641 30002 => Self::ClearAuthFailed,
642 31001 => Self::BlindAuthRequired,
643 31002 => Self::BlindAuthFailed,
644 _ => Self::Unknown(code),
645 }
646 }
647
648 pub fn to_code(&self) -> u16 {
650 match self {
651 Self::BlindedMessageAlreadySigned => 10002,
652 Self::TokenNotVerified => 10003,
653 Self::TokenAlreadySpent => 11001,
654 Self::TransactionUnbalanced => 11002,
655 Self::UnsupportedUnit => 11005,
656 Self::AmountOutofLimitRange => 11006,
657 Self::DuplicateInputs => 11007,
658 Self::DuplicateOutputs => 11008,
659 Self::MultipleUnits => 11009,
660 Self::UnitMismatch => 11010,
661 Self::TokenPending => 11012,
662 Self::KeysetNotFound => 12001,
663 Self::KeysetInactive => 12002,
664 Self::LightningError => 20000,
665 Self::QuoteNotPaid => 20001,
666 Self::TokensAlreadyIssued => 20002,
667 Self::MintingDisabled => 20003,
668 Self::QuotePending => 20005,
669 Self::InvoiceAlreadyPaid => 20006,
670 Self::QuoteExpired => 20007,
671 Self::WitnessMissingOrInvalid => 20008,
672 Self::ClearAuthRequired => 30001,
673 Self::ClearAuthFailed => 30002,
674 Self::BlindAuthRequired => 31001,
675 Self::BlindAuthFailed => 31002,
676 Self::Unknown(code) => *code,
677 }
678 }
679}
680
681impl Serialize for ErrorCode {
682 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
683 where
684 S: Serializer,
685 {
686 serializer.serialize_u16(self.to_code())
687 }
688}
689
690impl<'de> Deserialize<'de> for ErrorCode {
691 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
692 where
693 D: Deserializer<'de>,
694 {
695 let code = u16::deserialize(deserializer)?;
696
697 Ok(ErrorCode::from_code(code))
698 }
699}
700
701impl fmt::Display for ErrorCode {
702 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
703 write!(f, "{}", self.to_code())
704 }
705}