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(#[from] 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
505impl From<ErrorResponse> for Error {
506 fn from(err: ErrorResponse) -> Error {
507 match err.code {
508 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
509 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
510 ErrorCode::QuotePending => Self::PendingQuote,
511 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
512 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
513 ErrorCode::KeysetInactive => Self::InactiveKeyset,
514 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
515 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
516 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
517 ErrorCode::MintingDisabled => Self::MintingDisabled,
518 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
519 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
520 ErrorCode::LightningError => Self::PaymentFailed,
521 ErrorCode::AmountOutofLimitRange => {
522 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
523 }
524 ErrorCode::TokenPending => Self::TokenPending,
525 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
526 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
527 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
528 ErrorCode::MultipleUnits => Self::MultipleUnits,
529 ErrorCode::UnitMismatch => Self::UnitMismatch,
530 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
531 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
532 _ => Self::UnknownErrorResponse(err.to_string()),
533 }
534 }
535}
536
537#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
539#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
540pub enum ErrorCode {
541 TokenAlreadySpent,
543 TokenPending,
545 QuoteNotPaid,
547 QuoteExpired,
549 QuotePending,
551 KeysetNotFound,
553 KeysetInactive,
555 BlindedMessageAlreadySigned,
557 UnsupportedUnit,
559 TokensAlreadyIssued,
561 MintingDisabled,
563 InvoiceAlreadyPaid,
565 TokenNotVerified,
567 LightningError,
569 TransactionUnbalanced,
571 AmountOutofLimitRange,
573 WitnessMissingOrInvalid,
575 DuplicateInputs,
577 DuplicateOutputs,
579 MultipleUnits,
581 UnitMismatch,
583 ClearAuthRequired,
585 ClearAuthFailed,
587 BlindAuthRequired,
589 BlindAuthFailed,
591 Unknown(u16),
593}
594
595impl ErrorCode {
596 pub fn from_code(code: u16) -> Self {
598 match code {
599 10002 => Self::BlindedMessageAlreadySigned,
600 10003 => Self::TokenNotVerified,
601 11001 => Self::TokenAlreadySpent,
602 11002 => Self::TransactionUnbalanced,
603 11005 => Self::UnsupportedUnit,
604 11006 => Self::AmountOutofLimitRange,
605 11007 => Self::DuplicateInputs,
606 11008 => Self::DuplicateOutputs,
607 11009 => Self::MultipleUnits,
608 11010 => Self::UnitMismatch,
609 11012 => Self::TokenPending,
610 12001 => Self::KeysetNotFound,
611 12002 => Self::KeysetInactive,
612 20000 => Self::LightningError,
613 20001 => Self::QuoteNotPaid,
614 20002 => Self::TokensAlreadyIssued,
615 20003 => Self::MintingDisabled,
616 20005 => Self::QuotePending,
617 20006 => Self::InvoiceAlreadyPaid,
618 20007 => Self::QuoteExpired,
619 20008 => Self::WitnessMissingOrInvalid,
620 30001 => Self::ClearAuthRequired,
621 30002 => Self::ClearAuthFailed,
622 31001 => Self::BlindAuthRequired,
623 31002 => Self::BlindAuthFailed,
624 _ => Self::Unknown(code),
625 }
626 }
627
628 pub fn to_code(&self) -> u16 {
630 match self {
631 Self::BlindedMessageAlreadySigned => 10002,
632 Self::TokenNotVerified => 10003,
633 Self::TokenAlreadySpent => 11001,
634 Self::TransactionUnbalanced => 11002,
635 Self::UnsupportedUnit => 11005,
636 Self::AmountOutofLimitRange => 11006,
637 Self::DuplicateInputs => 11007,
638 Self::DuplicateOutputs => 11008,
639 Self::MultipleUnits => 11009,
640 Self::UnitMismatch => 11010,
641 Self::TokenPending => 11012,
642 Self::KeysetNotFound => 12001,
643 Self::KeysetInactive => 12002,
644 Self::LightningError => 20000,
645 Self::QuoteNotPaid => 20001,
646 Self::TokensAlreadyIssued => 20002,
647 Self::MintingDisabled => 20003,
648 Self::QuotePending => 20005,
649 Self::InvoiceAlreadyPaid => 20006,
650 Self::QuoteExpired => 20007,
651 Self::WitnessMissingOrInvalid => 20008,
652 Self::ClearAuthRequired => 30001,
653 Self::ClearAuthFailed => 30002,
654 Self::BlindAuthRequired => 31001,
655 Self::BlindAuthFailed => 31002,
656 Self::Unknown(code) => *code,
657 }
658 }
659}
660
661impl Serialize for ErrorCode {
662 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
663 where
664 S: Serializer,
665 {
666 serializer.serialize_u16(self.to_code())
667 }
668}
669
670impl<'de> Deserialize<'de> for ErrorCode {
671 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
672 where
673 D: Deserializer<'de>,
674 {
675 let code = u16::deserialize(deserializer)?;
676
677 Ok(ErrorCode::from_code(code))
678 }
679}
680
681impl fmt::Display for ErrorCode {
682 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683 write!(f, "{}", self.to_code())
684 }
685}