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("Internal send error: {0}")]
97 SendError(String),
98
99 #[error("Internal receive error: {0}")]
101 RecvError(String),
102
103 #[error("Minting is disabled")]
106 MintingDisabled,
107 #[error("Unknown quote")]
109 UnknownQuote,
110 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
112 ExpiredQuote(u64, u64),
113 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
115 AmountOutofLimitRange(Amount, Amount, Amount),
116 #[error("Quote not paid")]
118 UnpaidQuote,
119 #[error("Quote pending")]
121 PendingQuote,
122 #[error("Quote already issued")]
124 IssuedQuote,
125 #[error("Quote is already paid")]
127 PaidQuote,
128 #[error("Payment state is unknown")]
130 UnknownPaymentState,
131 #[error("Minting is disabled")]
133 MeltingDisabled,
134 #[error("Unknown Keyset")]
136 UnknownKeySet,
137 #[error("Blinded Message is already signed")]
139 BlindedMessageAlreadySigned,
140 #[error("Inactive Keyset")]
142 InactiveKeyset,
143 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
145 TransactionUnbalanced(u64, u64, u64),
146 #[error("Duplicate Inputs")]
148 DuplicateInputs,
149 #[error("Duplicate outputs")]
151 DuplicateOutputs,
152 #[error("Cannot have multiple units")]
154 MultipleUnits,
155 #[error("Input unit must match output")]
157 UnitMismatch,
158 #[error("Sig all cannot be used in melt")]
160 SigAllUsedInMelt,
161 #[error("Token Already Spent")]
163 TokenAlreadySpent,
164 #[error("Token Pending")]
166 TokenPending,
167 #[error("Internal Error")]
169 Internal,
170 #[error("Oidc client not set")]
172 OidcNotSet,
173
174 #[error("P2PK condition not met `{0}`")]
177 P2PKConditionsNotMet(String),
178 #[error("Spending condition locktime not provided")]
180 LocktimeNotProvided,
181 #[error("Invalid spending conditions: `{0}`")]
183 InvalidSpendConditions(String),
184 #[error("Incorrect wallet: `{0}`")]
186 IncorrectWallet(String),
187 #[error("Unknown wallet: `{0}`")]
189 #[cfg(feature = "wallet")]
190 UnknownWallet(WalletKey),
191 #[error("Max fee exceeded")]
193 MaxFeeExceeded,
194 #[error("Url path segments could not be joined")]
196 UrlPathSegments,
197 #[error("Unknown error response: `{0}`")]
199 UnknownErrorResponse(String),
200 #[error("Could not verify DLEQ proof")]
202 CouldNotVerifyDleq,
203 #[error("Dleq proof not provided for signature")]
205 DleqProofNotProvided,
206 #[error("Token does not match wallet mint")]
209 IncorrectMint,
210 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
212 MultiMintTokenNotSupported,
213 #[error("Preimage not provided")]
215 PreimageNotProvided,
216 #[error("Insufficient funds")]
218 InsufficientFunds,
219 #[error("Unexpected proof state")]
221 UnexpectedProofState,
222 #[error("No active keyset")]
224 NoActiveKeyset,
225 #[error("Incorrect quote amount")]
227 IncorrectQuoteAmount,
228 #[error("Invoice Description not supported")]
230 InvoiceDescriptionUnsupported,
231 #[error("Invalid transaction direction")]
233 InvalidTransactionDirection,
234 #[error("Invalid transaction id")]
236 InvalidTransactionId,
237 #[error("Transaction not found")]
239 TransactionNotFound,
240 #[error("`{0}`")]
242 Custom(String),
243
244 #[error(transparent)]
247 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
248 #[error(transparent)]
250 Bip32(#[from] bitcoin::bip32::Error),
251 #[error(transparent)]
253 ParseInt(#[from] std::num::ParseIntError),
254 #[error(transparent)]
256 UrlParseError(#[from] url::ParseError),
257 #[error(transparent)]
259 Utf8ParseError(#[from] std::string::FromUtf8Error),
260 #[error(transparent)]
262 SerdeJsonError(#[from] serde_json::Error),
263 #[error(transparent)]
265 Base64Error(#[from] bitcoin::base64::DecodeError),
266 #[error(transparent)]
268 HexError(#[from] hex::Error),
269 #[error("Http transport error: {0}")]
271 HttpError(String),
272 #[cfg(feature = "wallet")]
273 #[error(transparent)]
276 CashuUrl(#[from] crate::mint_url::Error),
277 #[error(transparent)]
279 Secret(#[from] crate::secret::Error),
280 #[error(transparent)]
282 AmountError(#[from] crate::amount::Error),
283 #[error(transparent)]
285 DHKE(#[from] crate::dhke::Error),
286 #[error(transparent)]
288 NUT00(#[from] crate::nuts::nut00::Error),
289 #[error(transparent)]
291 NUT01(#[from] crate::nuts::nut01::Error),
292 #[error(transparent)]
294 NUT02(#[from] crate::nuts::nut02::Error),
295 #[error(transparent)]
297 NUT03(#[from] crate::nuts::nut03::Error),
298 #[error(transparent)]
300 NUT04(#[from] crate::nuts::nut04::Error),
301 #[error(transparent)]
303 NUT05(#[from] crate::nuts::nut05::Error),
304 #[error(transparent)]
306 NUT11(#[from] crate::nuts::nut11::Error),
307 #[error(transparent)]
309 NUT12(#[from] crate::nuts::nut12::Error),
310 #[error(transparent)]
312 #[cfg(feature = "wallet")]
313 NUT13(#[from] crate::nuts::nut13::Error),
314 #[error(transparent)]
316 NUT14(#[from] crate::nuts::nut14::Error),
317 #[error(transparent)]
319 NUT18(#[from] crate::nuts::nut18::Error),
320 #[error(transparent)]
322 NUT20(#[from] crate::nuts::nut20::Error),
323 #[error(transparent)]
325 NUT21(#[from] crate::nuts::nut21::Error),
326 #[error(transparent)]
328 NUT22(#[from] crate::nuts::nut22::Error),
329 #[error(transparent)]
331 NUT23(#[from] crate::nuts::nut23::Error),
332 #[error(transparent)]
334 Database(crate::database::Error),
335 #[error(transparent)]
337 #[cfg(feature = "mint")]
338 Payment(#[from] crate::payment::Error),
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
345#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
346pub struct ErrorResponse {
347 pub code: ErrorCode,
349 pub error: Option<String>,
351 pub detail: Option<String>,
353}
354
355impl fmt::Display for ErrorResponse {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 write!(
358 f,
359 "code: {}, error: {}, detail: {}",
360 self.code,
361 self.error.clone().unwrap_or_default(),
362 self.detail.clone().unwrap_or_default()
363 )
364 }
365}
366
367impl ErrorResponse {
368 pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
370 Self {
371 code,
372 error,
373 detail,
374 }
375 }
376
377 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
379 let value: Value = serde_json::from_str(json)?;
380
381 Self::from_value(value)
382 }
383
384 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
386 match serde_json::from_value::<ErrorResponse>(value.clone()) {
387 Ok(res) => Ok(res),
388 Err(_) => Ok(Self {
389 code: ErrorCode::Unknown(999),
390 error: Some(value.to_string()),
391 detail: None,
392 }),
393 }
394 }
395}
396
397impl From<Error> for ErrorResponse {
398 fn from(err: Error) -> ErrorResponse {
399 match err {
400 Error::TokenAlreadySpent => ErrorResponse {
401 code: ErrorCode::TokenAlreadySpent,
402 error: Some(err.to_string()),
403 detail: None,
404 },
405 Error::UnsupportedUnit => ErrorResponse {
406 code: ErrorCode::UnsupportedUnit,
407 error: Some(err.to_string()),
408 detail: None,
409 },
410 Error::PaymentFailed => ErrorResponse {
411 code: ErrorCode::LightningError,
412 error: Some(err.to_string()),
413 detail: None,
414 },
415 Error::RequestAlreadyPaid => ErrorResponse {
416 code: ErrorCode::InvoiceAlreadyPaid,
417 error: Some("Invoice already paid.".to_string()),
418 detail: None,
419 },
420 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
421 ErrorResponse {
422 code: ErrorCode::TransactionUnbalanced,
423 error: Some(format!(
424 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}",
425 )),
426 detail: Some("Transaction inputs should equal outputs less fee".to_string()),
427 }
428 }
429 Error::MintingDisabled => ErrorResponse {
430 code: ErrorCode::MintingDisabled,
431 error: Some(err.to_string()),
432 detail: None,
433 },
434 Error::BlindedMessageAlreadySigned => ErrorResponse {
435 code: ErrorCode::BlindedMessageAlreadySigned,
436 error: Some(err.to_string()),
437 detail: None,
438 },
439 Error::InsufficientFunds => ErrorResponse {
440 code: ErrorCode::TransactionUnbalanced,
441 error: Some(err.to_string()),
442 detail: None,
443 },
444 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
445 code: ErrorCode::AmountOutofLimitRange,
446 error: Some(err.to_string()),
447 detail: None,
448 },
449 Error::ExpiredQuote(_, _) => ErrorResponse {
450 code: ErrorCode::QuoteExpired,
451 error: Some(err.to_string()),
452 detail: None,
453 },
454 Error::PendingQuote => ErrorResponse {
455 code: ErrorCode::QuotePending,
456 error: Some(err.to_string()),
457 detail: None,
458 },
459 Error::TokenPending => ErrorResponse {
460 code: ErrorCode::TokenPending,
461 error: Some(err.to_string()),
462 detail: None,
463 },
464 Error::ClearAuthRequired => ErrorResponse {
465 code: ErrorCode::ClearAuthRequired,
466 error: None,
467 detail: None,
468 },
469 Error::ClearAuthFailed => ErrorResponse {
470 code: ErrorCode::ClearAuthFailed,
471 error: None,
472 detail: None,
473 },
474 Error::BlindAuthRequired => ErrorResponse {
475 code: ErrorCode::BlindAuthRequired,
476 error: None,
477 detail: None,
478 },
479 Error::BlindAuthFailed => ErrorResponse {
480 code: ErrorCode::BlindAuthFailed,
481 error: None,
482 detail: None,
483 },
484 Error::NUT20(err) => ErrorResponse {
485 code: ErrorCode::WitnessMissingOrInvalid,
486 error: Some(err.to_string()),
487 detail: None,
488 },
489 Error::DuplicateInputs => ErrorResponse {
490 code: ErrorCode::DuplicateInputs,
491 error: Some(err.to_string()),
492 detail: None,
493 },
494 Error::DuplicateOutputs => ErrorResponse {
495 code: ErrorCode::DuplicateOutputs,
496 error: Some(err.to_string()),
497 detail: None,
498 },
499 Error::MultipleUnits => ErrorResponse {
500 code: ErrorCode::MultipleUnits,
501 error: Some(err.to_string()),
502 detail: None,
503 },
504 Error::UnitMismatch => ErrorResponse {
505 code: ErrorCode::UnitMismatch,
506 error: Some(err.to_string()),
507 detail: None,
508 },
509 _ => ErrorResponse {
510 code: ErrorCode::Unknown(9999),
511 error: Some(err.to_string()),
512 detail: None,
513 },
514 }
515 }
516}
517
518#[cfg(feature = "mint")]
519impl From<crate::database::Error> for Error {
520 fn from(db_error: crate::database::Error) -> Self {
521 match db_error {
522 crate::database::Error::InvalidStateTransition(state) => match state {
523 crate::state::Error::Pending => Self::TokenPending,
524 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
525 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
526 },
527 db_error => Self::Database(db_error),
528 }
529 }
530}
531
532#[cfg(not(feature = "mint"))]
533impl From<crate::database::Error> for Error {
534 fn from(db_error: crate::database::Error) -> Self {
535 Self::Database(db_error)
536 }
537}
538
539impl From<ErrorResponse> for Error {
540 fn from(err: ErrorResponse) -> Error {
541 match err.code {
542 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
543 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
544 ErrorCode::QuotePending => Self::PendingQuote,
545 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
546 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
547 ErrorCode::KeysetInactive => Self::InactiveKeyset,
548 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
549 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
550 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
551 ErrorCode::MintingDisabled => Self::MintingDisabled,
552 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
553 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
554 ErrorCode::LightningError => Self::PaymentFailed,
555 ErrorCode::AmountOutofLimitRange => {
556 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
557 }
558 ErrorCode::TokenPending => Self::TokenPending,
559 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
560 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
561 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
562 ErrorCode::MultipleUnits => Self::MultipleUnits,
563 ErrorCode::UnitMismatch => Self::UnitMismatch,
564 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
565 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
566 _ => Self::UnknownErrorResponse(err.to_string()),
567 }
568 }
569}
570
571#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
573#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
574pub enum ErrorCode {
575 TokenAlreadySpent,
577 TokenPending,
579 QuoteNotPaid,
581 QuoteExpired,
583 QuotePending,
585 KeysetNotFound,
587 KeysetInactive,
589 BlindedMessageAlreadySigned,
591 UnsupportedUnit,
593 TokensAlreadyIssued,
595 MintingDisabled,
597 InvoiceAlreadyPaid,
599 TokenNotVerified,
601 LightningError,
603 TransactionUnbalanced,
605 AmountOutofLimitRange,
607 WitnessMissingOrInvalid,
609 DuplicateInputs,
611 DuplicateOutputs,
613 MultipleUnits,
615 UnitMismatch,
617 ClearAuthRequired,
619 ClearAuthFailed,
621 BlindAuthRequired,
623 BlindAuthFailed,
625 Unknown(u16),
627}
628
629impl ErrorCode {
630 pub fn from_code(code: u16) -> Self {
632 match code {
633 10002 => Self::BlindedMessageAlreadySigned,
634 10003 => Self::TokenNotVerified,
635 11001 => Self::TokenAlreadySpent,
636 11002 => Self::TransactionUnbalanced,
637 11005 => Self::UnsupportedUnit,
638 11006 => Self::AmountOutofLimitRange,
639 11007 => Self::DuplicateInputs,
640 11008 => Self::DuplicateOutputs,
641 11009 => Self::MultipleUnits,
642 11010 => Self::UnitMismatch,
643 11012 => Self::TokenPending,
644 12001 => Self::KeysetNotFound,
645 12002 => Self::KeysetInactive,
646 20000 => Self::LightningError,
647 20001 => Self::QuoteNotPaid,
648 20002 => Self::TokensAlreadyIssued,
649 20003 => Self::MintingDisabled,
650 20005 => Self::QuotePending,
651 20006 => Self::InvoiceAlreadyPaid,
652 20007 => Self::QuoteExpired,
653 20008 => Self::WitnessMissingOrInvalid,
654 30001 => Self::ClearAuthRequired,
655 30002 => Self::ClearAuthFailed,
656 31001 => Self::BlindAuthRequired,
657 31002 => Self::BlindAuthFailed,
658 _ => Self::Unknown(code),
659 }
660 }
661
662 pub fn to_code(&self) -> u16 {
664 match self {
665 Self::BlindedMessageAlreadySigned => 10002,
666 Self::TokenNotVerified => 10003,
667 Self::TokenAlreadySpent => 11001,
668 Self::TransactionUnbalanced => 11002,
669 Self::UnsupportedUnit => 11005,
670 Self::AmountOutofLimitRange => 11006,
671 Self::DuplicateInputs => 11007,
672 Self::DuplicateOutputs => 11008,
673 Self::MultipleUnits => 11009,
674 Self::UnitMismatch => 11010,
675 Self::TokenPending => 11012,
676 Self::KeysetNotFound => 12001,
677 Self::KeysetInactive => 12002,
678 Self::LightningError => 20000,
679 Self::QuoteNotPaid => 20001,
680 Self::TokensAlreadyIssued => 20002,
681 Self::MintingDisabled => 20003,
682 Self::QuotePending => 20005,
683 Self::InvoiceAlreadyPaid => 20006,
684 Self::QuoteExpired => 20007,
685 Self::WitnessMissingOrInvalid => 20008,
686 Self::ClearAuthRequired => 30001,
687 Self::ClearAuthFailed => 30002,
688 Self::BlindAuthRequired => 31001,
689 Self::BlindAuthFailed => 31002,
690 Self::Unknown(code) => *code,
691 }
692 }
693}
694
695impl Serialize for ErrorCode {
696 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
697 where
698 S: Serializer,
699 {
700 serializer.serialize_u16(self.to_code())
701 }
702}
703
704impl<'de> Deserialize<'de> for ErrorCode {
705 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
706 where
707 D: Deserializer<'de>,
708 {
709 let code = u16::deserialize(deserializer)?;
710
711 Ok(ErrorCode::from_code(code))
712 }
713}
714
715impl fmt::Display for ErrorCode {
716 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
717 write!(f, "{}", self.to_code())
718 }
719}