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
92 #[error("Minting is disabled")]
95 MintingDisabled,
96 #[error("Unknown quote")]
98 UnknownQuote,
99 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
101 ExpiredQuote(u64, u64),
102 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
104 AmountOutofLimitRange(Amount, Amount, Amount),
105 #[error("Quote not paid")]
107 UnpaidQuote,
108 #[error("Quote pending")]
110 PendingQuote,
111 #[error("Quote already issued")]
113 IssuedQuote,
114 #[error("Quote is already paid")]
116 PaidQuote,
117 #[error("Payment state is unknown")]
119 UnknownPaymentState,
120 #[error("Minting is disabled")]
122 MeltingDisabled,
123 #[error("Unknown Keyset")]
125 UnknownKeySet,
126 #[error("Blinded Message is already signed")]
128 BlindedMessageAlreadySigned,
129 #[error("Inactive Keyset")]
131 InactiveKeyset,
132 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
134 TransactionUnbalanced(u64, u64, u64),
135 #[error("Duplicate Inputs")]
137 DuplicateInputs,
138 #[error("Duplicate outputs")]
140 DuplicateOutputs,
141 #[error("Cannot have multiple units")]
143 MultipleUnits,
144 #[error("Input unit must match output")]
146 UnitMismatch,
147 #[error("Sig all cannot be used in melt")]
149 SigAllUsedInMelt,
150 #[error("Token Already Spent")]
152 TokenAlreadySpent,
153 #[error("Token Pending")]
155 TokenPending,
156 #[error("Internal Error")]
158 Internal,
159 #[error("Oidc client not set")]
161 OidcNotSet,
162
163 #[error("P2PK condition not met `{0}`")]
166 P2PKConditionsNotMet(String),
167 #[error("Spending condition locktime not provided")]
169 LocktimeNotProvided,
170 #[error("Invalid spending conditions: `{0}`")]
172 InvalidSpendConditions(String),
173 #[error("Incorrect wallet: `{0}`")]
175 IncorrectWallet(String),
176 #[error("Unknown wallet: `{0}`")]
178 #[cfg(feature = "wallet")]
179 UnknownWallet(WalletKey),
180 #[error("Max fee exceeded")]
182 MaxFeeExceeded,
183 #[error("Url path segments could not be joined")]
185 UrlPathSegments,
186 #[error("Unknown error response: `{0}`")]
188 UnknownErrorResponse(String),
189 #[error("Could not verify DLEQ proof")]
191 CouldNotVerifyDleq,
192 #[error("Dleq proof not provided for signature")]
194 DleqProofNotProvided,
195 #[error("Token does not match wallet mint")]
198 IncorrectMint,
199 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
201 MultiMintTokenNotSupported,
202 #[error("Preimage not provided")]
204 PreimageNotProvided,
205 #[error("Insufficient funds")]
207 InsufficientFunds,
208 #[error("Unexpected proof state")]
210 UnexpectedProofState,
211 #[error("No active keyset")]
213 NoActiveKeyset,
214 #[error("Incorrect quote amount")]
216 IncorrectQuoteAmount,
217 #[error("Invoice Description not supported")]
219 InvoiceDescriptionUnsupported,
220 #[error("`{0}`")]
222 Custom(String),
223
224 #[error(transparent)]
227 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
228 #[error(transparent)]
230 Bip32(#[from] bitcoin::bip32::Error),
231 #[error(transparent)]
233 ParseInt(#[from] std::num::ParseIntError),
234 #[error(transparent)]
236 UrlParseError(#[from] url::ParseError),
237 #[error(transparent)]
239 Utf8ParseError(#[from] std::string::FromUtf8Error),
240 #[error(transparent)]
242 SerdeJsonError(#[from] serde_json::Error),
243 #[error(transparent)]
245 Base64Error(#[from] bitcoin::base64::DecodeError),
246 #[error(transparent)]
248 HexError(#[from] hex::Error),
249 #[error("Http transport error: {0}")]
251 HttpError(String),
252 #[cfg(feature = "wallet")]
253 #[error(transparent)]
256 CashuUrl(#[from] crate::mint_url::Error),
257 #[error(transparent)]
259 Secret(#[from] crate::secret::Error),
260 #[error(transparent)]
262 AmountError(#[from] crate::amount::Error),
263 #[error(transparent)]
265 DHKE(#[from] crate::dhke::Error),
266 #[error(transparent)]
268 NUT00(#[from] crate::nuts::nut00::Error),
269 #[error(transparent)]
271 NUT01(#[from] crate::nuts::nut01::Error),
272 #[error(transparent)]
274 NUT02(#[from] crate::nuts::nut02::Error),
275 #[error(transparent)]
277 NUT03(#[from] crate::nuts::nut03::Error),
278 #[error(transparent)]
280 NUT04(#[from] crate::nuts::nut04::Error),
281 #[error(transparent)]
283 NUT05(#[from] crate::nuts::nut05::Error),
284 #[error(transparent)]
286 NUT11(#[from] crate::nuts::nut11::Error),
287 #[error(transparent)]
289 NUT12(#[from] crate::nuts::nut12::Error),
290 #[error(transparent)]
292 #[cfg(feature = "wallet")]
293 NUT13(#[from] crate::nuts::nut13::Error),
294 #[error(transparent)]
296 NUT14(#[from] crate::nuts::nut14::Error),
297 #[error(transparent)]
299 NUT18(#[from] crate::nuts::nut18::Error),
300 #[error(transparent)]
302 NUT20(#[from] crate::nuts::nut20::Error),
303 #[error(transparent)]
305 NUT21(#[from] crate::nuts::nut21::Error),
306 #[error(transparent)]
308 NUT22(#[from] crate::nuts::nut22::Error),
309 #[error(transparent)]
311 Database(#[from] crate::database::Error),
312 #[error(transparent)]
314 #[cfg(feature = "mint")]
315 Payment(#[from] crate::payment::Error),
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
323pub struct ErrorResponse {
324 pub code: ErrorCode,
326 pub error: Option<String>,
328 pub detail: Option<String>,
330}
331
332impl fmt::Display for ErrorResponse {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 write!(
335 f,
336 "code: {}, error: {}, detail: {}",
337 self.code,
338 self.error.clone().unwrap_or_default(),
339 self.detail.clone().unwrap_or_default()
340 )
341 }
342}
343
344impl ErrorResponse {
345 pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
347 Self {
348 code,
349 error,
350 detail,
351 }
352 }
353
354 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
356 let value: Value = serde_json::from_str(json)?;
357
358 Self::from_value(value)
359 }
360
361 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
363 match serde_json::from_value::<ErrorResponse>(value.clone()) {
364 Ok(res) => Ok(res),
365 Err(_) => Ok(Self {
366 code: ErrorCode::Unknown(999),
367 error: Some(value.to_string()),
368 detail: None,
369 }),
370 }
371 }
372}
373
374impl From<Error> for ErrorResponse {
375 fn from(err: Error) -> ErrorResponse {
376 match err {
377 Error::TokenAlreadySpent => ErrorResponse {
378 code: ErrorCode::TokenAlreadySpent,
379 error: Some(err.to_string()),
380 detail: None,
381 },
382 Error::UnsupportedUnit => ErrorResponse {
383 code: ErrorCode::UnsupportedUnit,
384 error: Some(err.to_string()),
385 detail: None,
386 },
387 Error::PaymentFailed => ErrorResponse {
388 code: ErrorCode::LightningError,
389 error: Some(err.to_string()),
390 detail: None,
391 },
392 Error::RequestAlreadyPaid => ErrorResponse {
393 code: ErrorCode::InvoiceAlreadyPaid,
394 error: Some("Invoice already paid.".to_string()),
395 detail: None,
396 },
397 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
398 ErrorResponse {
399 code: ErrorCode::TransactionUnbalanced,
400 error: Some(format!(
401 "Inputs: {}, Outputs: {}, expected_fee: {}",
402 inputs_total, outputs_total, fee_expected,
403 )),
404 detail: Some("Transaction inputs should equal outputs less fee".to_string()),
405 }
406 }
407 Error::MintingDisabled => ErrorResponse {
408 code: ErrorCode::MintingDisabled,
409 error: Some(err.to_string()),
410 detail: None,
411 },
412 Error::BlindedMessageAlreadySigned => ErrorResponse {
413 code: ErrorCode::BlindedMessageAlreadySigned,
414 error: Some(err.to_string()),
415 detail: None,
416 },
417 Error::InsufficientFunds => ErrorResponse {
418 code: ErrorCode::TransactionUnbalanced,
419 error: Some(err.to_string()),
420 detail: None,
421 },
422 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
423 code: ErrorCode::AmountOutofLimitRange,
424 error: Some(err.to_string()),
425 detail: None,
426 },
427 Error::ExpiredQuote(_, _) => ErrorResponse {
428 code: ErrorCode::QuoteExpired,
429 error: Some(err.to_string()),
430 detail: None,
431 },
432 Error::PendingQuote => ErrorResponse {
433 code: ErrorCode::QuotePending,
434 error: Some(err.to_string()),
435 detail: None,
436 },
437 Error::TokenPending => ErrorResponse {
438 code: ErrorCode::TokenPending,
439 error: Some(err.to_string()),
440 detail: None,
441 },
442 Error::ClearAuthRequired => ErrorResponse {
443 code: ErrorCode::ClearAuthRequired,
444 error: None,
445 detail: None,
446 },
447 Error::ClearAuthFailed => ErrorResponse {
448 code: ErrorCode::ClearAuthFailed,
449 error: None,
450 detail: None,
451 },
452 Error::BlindAuthRequired => ErrorResponse {
453 code: ErrorCode::BlindAuthRequired,
454 error: None,
455 detail: None,
456 },
457 Error::BlindAuthFailed => ErrorResponse {
458 code: ErrorCode::BlindAuthFailed,
459 error: None,
460 detail: None,
461 },
462 Error::NUT20(err) => ErrorResponse {
463 code: ErrorCode::WitnessMissingOrInvalid,
464 error: Some(err.to_string()),
465 detail: None,
466 },
467 Error::DuplicateInputs => ErrorResponse {
468 code: ErrorCode::DuplicateInputs,
469 error: Some(err.to_string()),
470 detail: None,
471 },
472 Error::DuplicateOutputs => ErrorResponse {
473 code: ErrorCode::DuplicateOutputs,
474 error: Some(err.to_string()),
475 detail: None,
476 },
477 Error::MultipleUnits => ErrorResponse {
478 code: ErrorCode::MultipleUnits,
479 error: Some(err.to_string()),
480 detail: None,
481 },
482 Error::UnitMismatch => ErrorResponse {
483 code: ErrorCode::UnitMismatch,
484 error: Some(err.to_string()),
485 detail: None,
486 },
487 _ => ErrorResponse {
488 code: ErrorCode::Unknown(9999),
489 error: Some(err.to_string()),
490 detail: None,
491 },
492 }
493 }
494}
495
496impl From<ErrorResponse> for Error {
497 fn from(err: ErrorResponse) -> Error {
498 match err.code {
499 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
500 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
501 ErrorCode::QuotePending => Self::PendingQuote,
502 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
503 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
504 ErrorCode::KeysetInactive => Self::InactiveKeyset,
505 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
506 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
507 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
508 ErrorCode::MintingDisabled => Self::MintingDisabled,
509 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
510 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
511 ErrorCode::LightningError => Self::PaymentFailed,
512 ErrorCode::AmountOutofLimitRange => {
513 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
514 }
515 ErrorCode::TokenPending => Self::TokenPending,
516 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
517 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
518 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
519 ErrorCode::MultipleUnits => Self::MultipleUnits,
520 ErrorCode::UnitMismatch => Self::UnitMismatch,
521 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
522 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
523 _ => Self::UnknownErrorResponse(err.to_string()),
524 }
525 }
526}
527
528#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
530#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
531pub enum ErrorCode {
532 TokenAlreadySpent,
534 TokenPending,
536 QuoteNotPaid,
538 QuoteExpired,
540 QuotePending,
542 KeysetNotFound,
544 KeysetInactive,
546 BlindedMessageAlreadySigned,
548 UnsupportedUnit,
550 TokensAlreadyIssued,
552 MintingDisabled,
554 InvoiceAlreadyPaid,
556 TokenNotVerified,
558 LightningError,
560 TransactionUnbalanced,
562 AmountOutofLimitRange,
564 WitnessMissingOrInvalid,
566 DuplicateInputs,
568 DuplicateOutputs,
570 MultipleUnits,
572 UnitMismatch,
574 ClearAuthRequired,
576 ClearAuthFailed,
578 BlindAuthRequired,
580 BlindAuthFailed,
582 Unknown(u16),
584}
585
586impl ErrorCode {
587 pub fn from_code(code: u16) -> Self {
589 match code {
590 10002 => Self::BlindedMessageAlreadySigned,
591 10003 => Self::TokenNotVerified,
592 11001 => Self::TokenAlreadySpent,
593 11002 => Self::TransactionUnbalanced,
594 11005 => Self::UnsupportedUnit,
595 11006 => Self::AmountOutofLimitRange,
596 11007 => Self::DuplicateInputs,
597 11008 => Self::DuplicateOutputs,
598 11009 => Self::MultipleUnits,
599 11010 => Self::UnitMismatch,
600 11012 => Self::TokenPending,
601 12001 => Self::KeysetNotFound,
602 12002 => Self::KeysetInactive,
603 20000 => Self::LightningError,
604 20001 => Self::QuoteNotPaid,
605 20002 => Self::TokensAlreadyIssued,
606 20003 => Self::MintingDisabled,
607 20005 => Self::QuotePending,
608 20006 => Self::InvoiceAlreadyPaid,
609 20007 => Self::QuoteExpired,
610 20008 => Self::WitnessMissingOrInvalid,
611 30001 => Self::ClearAuthRequired,
612 30002 => Self::ClearAuthFailed,
613 31001 => Self::BlindAuthRequired,
614 31002 => Self::BlindAuthFailed,
615 _ => Self::Unknown(code),
616 }
617 }
618
619 pub fn to_code(&self) -> u16 {
621 match self {
622 Self::BlindedMessageAlreadySigned => 10002,
623 Self::TokenNotVerified => 10003,
624 Self::TokenAlreadySpent => 11001,
625 Self::TransactionUnbalanced => 11002,
626 Self::UnsupportedUnit => 11005,
627 Self::AmountOutofLimitRange => 11006,
628 Self::DuplicateInputs => 11007,
629 Self::DuplicateOutputs => 11008,
630 Self::MultipleUnits => 11009,
631 Self::UnitMismatch => 11010,
632 Self::TokenPending => 11012,
633 Self::KeysetNotFound => 12001,
634 Self::KeysetInactive => 12002,
635 Self::LightningError => 20000,
636 Self::QuoteNotPaid => 20001,
637 Self::TokensAlreadyIssued => 20002,
638 Self::MintingDisabled => 20003,
639 Self::QuotePending => 20005,
640 Self::InvoiceAlreadyPaid => 20006,
641 Self::QuoteExpired => 20007,
642 Self::WitnessMissingOrInvalid => 20008,
643 Self::ClearAuthRequired => 30001,
644 Self::ClearAuthFailed => 30002,
645 Self::BlindAuthRequired => 31001,
646 Self::BlindAuthFailed => 31002,
647 Self::Unknown(code) => *code,
648 }
649 }
650}
651
652impl Serialize for ErrorCode {
653 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
654 where
655 S: Serializer,
656 {
657 serializer.serialize_u16(self.to_code())
658 }
659}
660
661impl<'de> Deserialize<'de> for ErrorCode {
662 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
663 where
664 D: Deserializer<'de>,
665 {
666 let code = u16::deserialize(deserializer)?;
667
668 Ok(ErrorCode::from_code(code))
669 }
670}
671
672impl fmt::Display for ErrorCode {
673 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674 write!(f, "{}", self.to_code())
675 }
676}