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: {}, Outputs: {}, expected_fee: {}",
411 inputs_total, outputs_total, fee_expected,
412 )),
413 detail: Some("Transaction inputs should equal outputs less fee".to_string()),
414 }
415 }
416 Error::MintingDisabled => ErrorResponse {
417 code: ErrorCode::MintingDisabled,
418 error: Some(err.to_string()),
419 detail: None,
420 },
421 Error::BlindedMessageAlreadySigned => ErrorResponse {
422 code: ErrorCode::BlindedMessageAlreadySigned,
423 error: Some(err.to_string()),
424 detail: None,
425 },
426 Error::InsufficientFunds => ErrorResponse {
427 code: ErrorCode::TransactionUnbalanced,
428 error: Some(err.to_string()),
429 detail: None,
430 },
431 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
432 code: ErrorCode::AmountOutofLimitRange,
433 error: Some(err.to_string()),
434 detail: None,
435 },
436 Error::ExpiredQuote(_, _) => ErrorResponse {
437 code: ErrorCode::QuoteExpired,
438 error: Some(err.to_string()),
439 detail: None,
440 },
441 Error::PendingQuote => ErrorResponse {
442 code: ErrorCode::QuotePending,
443 error: Some(err.to_string()),
444 detail: None,
445 },
446 Error::TokenPending => ErrorResponse {
447 code: ErrorCode::TokenPending,
448 error: Some(err.to_string()),
449 detail: None,
450 },
451 Error::ClearAuthRequired => ErrorResponse {
452 code: ErrorCode::ClearAuthRequired,
453 error: None,
454 detail: None,
455 },
456 Error::ClearAuthFailed => ErrorResponse {
457 code: ErrorCode::ClearAuthFailed,
458 error: None,
459 detail: None,
460 },
461 Error::BlindAuthRequired => ErrorResponse {
462 code: ErrorCode::BlindAuthRequired,
463 error: None,
464 detail: None,
465 },
466 Error::BlindAuthFailed => ErrorResponse {
467 code: ErrorCode::BlindAuthFailed,
468 error: None,
469 detail: None,
470 },
471 Error::NUT20(err) => ErrorResponse {
472 code: ErrorCode::WitnessMissingOrInvalid,
473 error: Some(err.to_string()),
474 detail: None,
475 },
476 Error::DuplicateInputs => ErrorResponse {
477 code: ErrorCode::DuplicateInputs,
478 error: Some(err.to_string()),
479 detail: None,
480 },
481 Error::DuplicateOutputs => ErrorResponse {
482 code: ErrorCode::DuplicateOutputs,
483 error: Some(err.to_string()),
484 detail: None,
485 },
486 Error::MultipleUnits => ErrorResponse {
487 code: ErrorCode::MultipleUnits,
488 error: Some(err.to_string()),
489 detail: None,
490 },
491 Error::UnitMismatch => ErrorResponse {
492 code: ErrorCode::UnitMismatch,
493 error: Some(err.to_string()),
494 detail: None,
495 },
496 _ => ErrorResponse {
497 code: ErrorCode::Unknown(9999),
498 error: Some(err.to_string()),
499 detail: None,
500 },
501 }
502 }
503}
504
505#[cfg(feature = "mint")]
506impl From<crate::database::Error> for Error {
507 fn from(db_error: crate::database::Error) -> Self {
508 match db_error {
509 crate::database::Error::InvalidStateTransition(state) => match state {
510 crate::state::Error::Pending => Self::TokenPending,
511 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
512 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
513 },
514 db_error => Self::Database(db_error),
515 }
516 }
517}
518
519#[cfg(not(feature = "mint"))]
520impl From<crate::database::Error> for Error {
521 fn from(db_error: crate::database::Error) -> Self {
522 Self::Database(db_error)
523 }
524}
525
526impl From<ErrorResponse> for Error {
527 fn from(err: ErrorResponse) -> Error {
528 match err.code {
529 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
530 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
531 ErrorCode::QuotePending => Self::PendingQuote,
532 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
533 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
534 ErrorCode::KeysetInactive => Self::InactiveKeyset,
535 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
536 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
537 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
538 ErrorCode::MintingDisabled => Self::MintingDisabled,
539 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
540 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
541 ErrorCode::LightningError => Self::PaymentFailed,
542 ErrorCode::AmountOutofLimitRange => {
543 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
544 }
545 ErrorCode::TokenPending => Self::TokenPending,
546 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
547 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
548 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
549 ErrorCode::MultipleUnits => Self::MultipleUnits,
550 ErrorCode::UnitMismatch => Self::UnitMismatch,
551 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
552 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
553 _ => Self::UnknownErrorResponse(err.to_string()),
554 }
555 }
556}
557
558#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
560#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
561pub enum ErrorCode {
562 TokenAlreadySpent,
564 TokenPending,
566 QuoteNotPaid,
568 QuoteExpired,
570 QuotePending,
572 KeysetNotFound,
574 KeysetInactive,
576 BlindedMessageAlreadySigned,
578 UnsupportedUnit,
580 TokensAlreadyIssued,
582 MintingDisabled,
584 InvoiceAlreadyPaid,
586 TokenNotVerified,
588 LightningError,
590 TransactionUnbalanced,
592 AmountOutofLimitRange,
594 WitnessMissingOrInvalid,
596 DuplicateInputs,
598 DuplicateOutputs,
600 MultipleUnits,
602 UnitMismatch,
604 ClearAuthRequired,
606 ClearAuthFailed,
608 BlindAuthRequired,
610 BlindAuthFailed,
612 Unknown(u16),
614}
615
616impl ErrorCode {
617 pub fn from_code(code: u16) -> Self {
619 match code {
620 10002 => Self::BlindedMessageAlreadySigned,
621 10003 => Self::TokenNotVerified,
622 11001 => Self::TokenAlreadySpent,
623 11002 => Self::TransactionUnbalanced,
624 11005 => Self::UnsupportedUnit,
625 11006 => Self::AmountOutofLimitRange,
626 11007 => Self::DuplicateInputs,
627 11008 => Self::DuplicateOutputs,
628 11009 => Self::MultipleUnits,
629 11010 => Self::UnitMismatch,
630 11012 => Self::TokenPending,
631 12001 => Self::KeysetNotFound,
632 12002 => Self::KeysetInactive,
633 20000 => Self::LightningError,
634 20001 => Self::QuoteNotPaid,
635 20002 => Self::TokensAlreadyIssued,
636 20003 => Self::MintingDisabled,
637 20005 => Self::QuotePending,
638 20006 => Self::InvoiceAlreadyPaid,
639 20007 => Self::QuoteExpired,
640 20008 => Self::WitnessMissingOrInvalid,
641 30001 => Self::ClearAuthRequired,
642 30002 => Self::ClearAuthFailed,
643 31001 => Self::BlindAuthRequired,
644 31002 => Self::BlindAuthFailed,
645 _ => Self::Unknown(code),
646 }
647 }
648
649 pub fn to_code(&self) -> u16 {
651 match self {
652 Self::BlindedMessageAlreadySigned => 10002,
653 Self::TokenNotVerified => 10003,
654 Self::TokenAlreadySpent => 11001,
655 Self::TransactionUnbalanced => 11002,
656 Self::UnsupportedUnit => 11005,
657 Self::AmountOutofLimitRange => 11006,
658 Self::DuplicateInputs => 11007,
659 Self::DuplicateOutputs => 11008,
660 Self::MultipleUnits => 11009,
661 Self::UnitMismatch => 11010,
662 Self::TokenPending => 11012,
663 Self::KeysetNotFound => 12001,
664 Self::KeysetInactive => 12002,
665 Self::LightningError => 20000,
666 Self::QuoteNotPaid => 20001,
667 Self::TokensAlreadyIssued => 20002,
668 Self::MintingDisabled => 20003,
669 Self::QuotePending => 20005,
670 Self::InvoiceAlreadyPaid => 20006,
671 Self::QuoteExpired => 20007,
672 Self::WitnessMissingOrInvalid => 20008,
673 Self::ClearAuthRequired => 30001,
674 Self::ClearAuthFailed => 30002,
675 Self::BlindAuthRequired => 31001,
676 Self::BlindAuthFailed => 31002,
677 Self::Unknown(code) => *code,
678 }
679 }
680}
681
682impl Serialize for ErrorCode {
683 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
684 where
685 S: Serializer,
686 {
687 serializer.serialize_u16(self.to_code())
688 }
689}
690
691impl<'de> Deserialize<'de> for ErrorCode {
692 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
693 where
694 D: Deserializer<'de>,
695 {
696 let code = u16::deserialize(deserializer)?;
697
698 Ok(ErrorCode::from_code(code))
699 }
700}
701
702impl fmt::Display for ErrorCode {
703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704 write!(f, "{}", self.to_code())
705 }
706}