cdk_common/
error.rs

1//! Errors
2
3use std::array::TryFromSliceError;
4use std::fmt;
5
6use cashu::{CurrencyUnit, PaymentMethod};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_json::Value;
9use thiserror::Error;
10
11use crate::nuts::Id;
12use crate::util::hex;
13#[cfg(feature = "wallet")]
14use crate::wallet::WalletKey;
15use crate::Amount;
16
17/// CDK Error
18#[derive(Debug, Error)]
19pub enum Error {
20    /// Mint does not have a key for amount
21    #[error("No Key for Amount")]
22    AmountKey,
23    /// Keyset is not known
24    #[error("Keyset id not known: `{0}`")]
25    KeysetUnknown(Id),
26    /// Unsupported unit
27    #[error("Unit unsupported")]
28    UnsupportedUnit,
29    /// Payment failed
30    #[error("Payment failed")]
31    PaymentFailed,
32    /// Payment pending
33    #[error("Payment pending")]
34    PaymentPending,
35    /// Invoice already paid
36    #[error("Request already paid")]
37    RequestAlreadyPaid,
38    /// Invalid payment request
39    #[error("Invalid payment request")]
40    InvalidPaymentRequest,
41    /// Bolt11 invoice does not have amount
42    #[error("Invoice Amount undefined")]
43    InvoiceAmountUndefined,
44    /// Split Values must be less then or equal to amount
45    #[error("Split Values must be less then or equal to amount")]
46    SplitValuesGreater,
47    /// Amount overflow
48    #[error("Amount Overflow")]
49    AmountOverflow,
50    /// Witness missing or invalid
51    #[error("Signature missing or invalid")]
52    SignatureMissingOrInvalid,
53    /// Amountless Invoice Not supported
54    #[error("Amount Less Invoice is not allowed")]
55    AmountLessNotAllowed,
56    /// Multi-Part Internal Melt Quotes are not supported
57    #[error("Multi-Part Internal Melt Quotes are not supported")]
58    InternalMultiPartMeltQuote,
59    /// Multi-Part Payment not supported for unit and method
60    #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
61    MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
62    /// Clear Auth Required
63    #[error("Clear Auth Required")]
64    ClearAuthRequired,
65    /// Blind Auth Required
66    #[error("Blind Auth Required")]
67    BlindAuthRequired,
68    /// Clear Auth Failed
69    #[error("Clear Auth Failed")]
70    ClearAuthFailed,
71    /// Blind Auth Failed
72    #[error("Blind Auth Failed")]
73    BlindAuthFailed,
74    /// Auth settings undefined
75    #[error("Auth settings undefined")]
76    AuthSettingsUndefined,
77    /// Mint time outside of tolerance
78    #[error("Mint time outside of tolerance")]
79    MintTimeExceedsTolerance,
80    /// Insufficient blind auth tokens
81    #[error("Insufficient blind auth tokens, must reauth")]
82    InsufficientBlindAuthTokens,
83    /// Auth localstore undefined
84    #[error("Auth localstore undefined")]
85    AuthLocalstoreUndefined,
86    /// Wallet cat not set
87    #[error("Wallet cat not set")]
88    CatNotSet,
89    /// Could not get mint info
90    #[error("Could not get mint info")]
91    CouldNotGetMintInfo,
92    /// Multi-Part Payment not supported for unit and method
93    #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
94    AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
95    /// Duplicate Payment id
96    #[error("Payment id seen for mint")]
97    DuplicatePaymentId,
98    /// Pubkey required
99    #[error("Pubkey required")]
100    PubkeyRequired,
101    /// Invalid payment method
102    #[error("Invalid payment method")]
103    InvalidPaymentMethod,
104    /// Amount undefined
105    #[error("Amount undefined")]
106    AmountUndefined,
107    /// Unsupported payment method
108    #[error("Payment method unsupported")]
109    UnsupportedPaymentMethod,
110    /// Could not parse bolt12
111    #[error("Could not parse bolt12")]
112    Bolt12parse,
113    /// Could not parse invoice (bolt11 or bolt12)
114    #[error("Could not parse invoice")]
115    InvalidInvoice,
116
117    /// BIP353 address parsing error
118    #[error("Failed to parse BIP353 address: {0}")]
119    Bip353Parse(String),
120
121    /// Operation timeout
122    #[error("Operation timeout")]
123    Timeout,
124
125    /// BIP353 address resolution error
126    #[error("Failed to resolve BIP353 address: {0}")]
127    Bip353Resolve(String),
128    /// BIP353 no Lightning offer found
129    #[error("No Lightning offer found in BIP353 payment instructions")]
130    Bip353NoLightningOffer,
131
132    /// Lightning Address parsing error
133    #[error("Failed to parse Lightning address: {0}")]
134    LightningAddressParse(String),
135    /// Lightning Address request error
136    #[error("Failed to request invoice from Lightning address service: {0}")]
137    LightningAddressRequest(String),
138
139    /// Internal Error - Send error
140    #[error("Internal send error: {0}")]
141    SendError(String),
142
143    /// Internal Error - Recv error
144    #[error("Internal receive error: {0}")]
145    RecvError(String),
146
147    // Mint Errors
148    /// Minting is disabled
149    #[error("Minting is disabled")]
150    MintingDisabled,
151    /// Quote is not known
152    #[error("Unknown quote")]
153    UnknownQuote,
154    /// Quote is expired
155    #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
156    ExpiredQuote(u64, u64),
157    /// Amount is outside of allowed range
158    #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
159    AmountOutofLimitRange(Amount, Amount, Amount),
160    /// Quote is not paiud
161    #[error("Quote not paid")]
162    UnpaidQuote,
163    /// Quote is pending
164    #[error("Quote pending")]
165    PendingQuote,
166    /// ecash already issued for quote
167    #[error("Quote already issued")]
168    IssuedQuote,
169    /// Quote has already been paid
170    #[error("Quote is already paid")]
171    PaidQuote,
172    /// Payment state is unknown
173    #[error("Payment state is unknown")]
174    UnknownPaymentState,
175    /// Melting is disabled
176    #[error("Minting is disabled")]
177    MeltingDisabled,
178    /// Unknown Keyset
179    #[error("Unknown Keyset")]
180    UnknownKeySet,
181    /// BlindedMessage is already signed
182    #[error("Blinded Message is already signed")]
183    BlindedMessageAlreadySigned,
184    /// Inactive Keyset
185    #[error("Inactive Keyset")]
186    InactiveKeyset,
187    /// Transaction unbalanced
188    #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
189    TransactionUnbalanced(u64, u64, u64),
190    /// Duplicate proofs provided
191    #[error("Duplicate Inputs")]
192    DuplicateInputs,
193    /// Duplicate output
194    #[error("Duplicate outputs")]
195    DuplicateOutputs,
196    /// Multiple units provided
197    #[error("Cannot have multiple units")]
198    MultipleUnits,
199    /// Unit mismatch
200    #[error("Input unit must match output")]
201    UnitMismatch,
202    /// Sig all cannot be used in melt
203    #[error("Sig all cannot be used in melt")]
204    SigAllUsedInMelt,
205    /// Token is already spent
206    #[error("Token Already Spent")]
207    TokenAlreadySpent,
208    /// Token is already pending
209    #[error("Token Pending")]
210    TokenPending,
211    /// Internal Error
212    #[error("Internal Error")]
213    Internal,
214    /// Oidc config not set
215    #[error("Oidc client not set")]
216    OidcNotSet,
217
218    // Wallet Errors
219    /// P2PK spending conditions not met
220    #[error("P2PK condition not met `{0}`")]
221    P2PKConditionsNotMet(String),
222    /// Duplicate signature from same pubkey in P2PK
223    #[error("Duplicate signature from same pubkey in P2PK")]
224    DuplicateSignatureError,
225    /// Spending Locktime not provided
226    #[error("Spending condition locktime not provided")]
227    LocktimeNotProvided,
228    /// Invalid Spending Conditions
229    #[error("Invalid spending conditions: `{0}`")]
230    InvalidSpendConditions(String),
231    /// Incorrect Wallet
232    #[error("Incorrect wallet: `{0}`")]
233    IncorrectWallet(String),
234    /// Unknown Wallet
235    #[error("Unknown wallet: `{0}`")]
236    #[cfg(feature = "wallet")]
237    UnknownWallet(WalletKey),
238    /// Max Fee Ecxeded
239    #[error("Max fee exceeded")]
240    MaxFeeExceeded,
241    /// Url path segments could not be joined
242    #[error("Url path segments could not be joined")]
243    UrlPathSegments,
244    ///  Unknown error response
245    #[error("Unknown error response: `{0}`")]
246    UnknownErrorResponse(String),
247    /// Invalid DLEQ proof
248    #[error("Could not verify DLEQ proof")]
249    CouldNotVerifyDleq,
250    /// Dleq Proof not provided for signature
251    #[error("Dleq proof not provided for signature")]
252    DleqProofNotProvided,
253    /// Incorrect Mint
254    /// Token does not match wallet mint
255    #[error("Token does not match wallet mint")]
256    IncorrectMint,
257    /// Receive can only be used with tokens from single mint
258    #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
259    MultiMintTokenNotSupported,
260    /// Preimage not provided
261    #[error("Preimage not provided")]
262    PreimageNotProvided,
263
264    // MultiMint Wallet Errors
265    /// Currency unit mismatch in MultiMintWallet
266    #[error("Currency unit mismatch: wallet uses {expected}, but {found} provided")]
267    MultiMintCurrencyUnitMismatch {
268        /// Expected currency unit
269        expected: CurrencyUnit,
270        /// Found currency unit
271        found: CurrencyUnit,
272    },
273    /// Unknown mint in MultiMintWallet
274    #[error("Unknown mint: {mint_url}")]
275    UnknownMint {
276        /// URL of the unknown mint
277        mint_url: String,
278    },
279    /// Transfer between mints timed out
280    #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
281    TransferTimeout {
282        /// Source mint URL
283        source_mint: String,
284        /// Target mint URL  
285        target_mint: String,
286        /// Amount that failed to transfer
287        amount: Amount,
288    },
289    /// Insufficient Funds
290    #[error("Insufficient funds")]
291    InsufficientFunds,
292    /// Unexpected proof state
293    #[error("Unexpected proof state")]
294    UnexpectedProofState,
295    /// No active keyset
296    #[error("No active keyset")]
297    NoActiveKeyset,
298    /// Incorrect quote amount
299    #[error("Incorrect quote amount")]
300    IncorrectQuoteAmount,
301    /// Invoice Description not supported
302    #[error("Invoice Description not supported")]
303    InvoiceDescriptionUnsupported,
304    /// Invalid transaction direction
305    #[error("Invalid transaction direction")]
306    InvalidTransactionDirection,
307    /// Invalid transaction id
308    #[error("Invalid transaction id")]
309    InvalidTransactionId,
310    /// Transaction not found
311    #[error("Transaction not found")]
312    TransactionNotFound,
313    /// KV Store invalid key or namespace
314    #[error("Invalid KV store key or namespace: {0}")]
315    KVStoreInvalidKey(String),
316    /// Custom Error
317    #[error("`{0}`")]
318    Custom(String),
319
320    // External Error conversions
321    /// Parse invoice error
322    #[error(transparent)]
323    Invoice(#[from] lightning_invoice::ParseOrSemanticError),
324    /// Bip32 error
325    #[error(transparent)]
326    Bip32(#[from] bitcoin::bip32::Error),
327    /// Parse int error
328    #[error(transparent)]
329    ParseInt(#[from] std::num::ParseIntError),
330    /// Parse 9rl Error
331    #[error(transparent)]
332    UrlParseError(#[from] url::ParseError),
333    /// Utf8 parse error
334    #[error(transparent)]
335    Utf8ParseError(#[from] std::string::FromUtf8Error),
336    /// Serde Json error
337    #[error(transparent)]
338    SerdeJsonError(#[from] serde_json::Error),
339    /// Base64 error
340    #[error(transparent)]
341    Base64Error(#[from] bitcoin::base64::DecodeError),
342    /// From hex error
343    #[error(transparent)]
344    HexError(#[from] hex::Error),
345    /// Http transport error
346    #[error("Http transport error {0:?}: {1}")]
347    HttpError(Option<u16>, String),
348    /// Parse invoice error
349    #[cfg(feature = "mint")]
350    #[error(transparent)]
351    Uuid(#[from] uuid::Error),
352    // Crate error conversions
353    /// Cashu Url Error
354    #[error(transparent)]
355    CashuUrl(#[from] crate::mint_url::Error),
356    /// Secret error
357    #[error(transparent)]
358    Secret(#[from] crate::secret::Error),
359    /// Amount Error
360    #[error(transparent)]
361    AmountError(#[from] crate::amount::Error),
362    /// DHKE Error
363    #[error(transparent)]
364    DHKE(#[from] crate::dhke::Error),
365    /// NUT00 Error
366    #[error(transparent)]
367    NUT00(#[from] crate::nuts::nut00::Error),
368    /// Nut01 error
369    #[error(transparent)]
370    NUT01(#[from] crate::nuts::nut01::Error),
371    /// NUT02 error
372    #[error(transparent)]
373    NUT02(#[from] crate::nuts::nut02::Error),
374    /// NUT03 error
375    #[error(transparent)]
376    NUT03(#[from] crate::nuts::nut03::Error),
377    /// NUT04 error
378    #[error(transparent)]
379    NUT04(#[from] crate::nuts::nut04::Error),
380    /// NUT05 error
381    #[error(transparent)]
382    NUT05(#[from] crate::nuts::nut05::Error),
383    /// NUT11 Error
384    #[error(transparent)]
385    NUT11(#[from] crate::nuts::nut11::Error),
386    /// NUT12 Error
387    #[error(transparent)]
388    NUT12(#[from] crate::nuts::nut12::Error),
389    /// NUT13 Error
390    #[error(transparent)]
391    #[cfg(feature = "wallet")]
392    NUT13(#[from] crate::nuts::nut13::Error),
393    /// NUT14 Error
394    #[error(transparent)]
395    NUT14(#[from] crate::nuts::nut14::Error),
396    /// NUT18 Error
397    #[error(transparent)]
398    NUT18(#[from] crate::nuts::nut18::Error),
399    /// NUT20 Error
400    #[error(transparent)]
401    NUT20(#[from] crate::nuts::nut20::Error),
402    /// NUT21 Error
403    #[error(transparent)]
404    #[cfg(feature = "auth")]
405    NUT21(#[from] crate::nuts::nut21::Error),
406    /// NUT22 Error
407    #[error(transparent)]
408    #[cfg(feature = "auth")]
409    NUT22(#[from] crate::nuts::nut22::Error),
410    /// NUT23 Error
411    #[error(transparent)]
412    NUT23(#[from] crate::nuts::nut23::Error),
413    /// Quote ID Error
414    #[error(transparent)]
415    #[cfg(feature = "mint")]
416    QuoteId(#[from] crate::quote_id::QuoteIdError),
417    /// From slice error
418    #[error(transparent)]
419    TryFromSliceError(#[from] TryFromSliceError),
420    /// Database Error
421    #[error(transparent)]
422    Database(crate::database::Error),
423    /// Payment Error
424    #[error(transparent)]
425    #[cfg(feature = "mint")]
426    Payment(#[from] crate::payment::Error),
427}
428
429/// CDK Error Response
430///
431/// See NUT definition in [00](https://github.com/cashubtc/nuts/blob/main/00.md)
432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
434pub struct ErrorResponse {
435    /// Error Code
436    pub code: ErrorCode,
437    /// Human readable description
438    #[serde(default)]
439    pub detail: String,
440}
441
442impl fmt::Display for ErrorResponse {
443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444        write!(f, "code: {}, detail: {}", self.code, self.detail)
445    }
446}
447
448impl ErrorResponse {
449    /// Create new [`ErrorResponse`]
450    pub fn new(code: ErrorCode, detail: String) -> Self {
451        Self { code, detail }
452    }
453
454    /// Error response from json
455    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
456        let value: Value = serde_json::from_str(json)?;
457
458        Self::from_value(value)
459    }
460
461    /// Error response from json Value
462    pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
463        match serde_json::from_value::<ErrorResponse>(value.clone()) {
464            Ok(res) => Ok(res),
465            Err(_) => Ok(Self {
466                code: ErrorCode::Unknown(999),
467                detail: value.to_string(),
468            }),
469        }
470    }
471}
472
473/// Maps NUT11 errors to appropriate error codes
474fn map_nut11_error(nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
475    match nut11_error {
476        crate::nuts::nut11::Error::SignaturesNotProvided => ErrorCode::WitnessMissingOrInvalid,
477        crate::nuts::nut11::Error::InvalidSignature => ErrorCode::WitnessMissingOrInvalid,
478        crate::nuts::nut11::Error::DuplicateSignature => ErrorCode::DuplicateSignature,
479        _ => ErrorCode::Unknown(9999), // Parsing/validation errors
480    }
481}
482
483impl From<Error> for ErrorResponse {
484    fn from(err: Error) -> ErrorResponse {
485        match err {
486            Error::TokenAlreadySpent => ErrorResponse {
487                code: ErrorCode::TokenAlreadySpent,
488                detail: err.to_string(),
489            },
490            Error::UnsupportedUnit => ErrorResponse {
491                code: ErrorCode::UnsupportedUnit,
492                detail: err.to_string(),
493            },
494            Error::PaymentFailed => ErrorResponse {
495                code: ErrorCode::LightningError,
496                detail: err.to_string(),
497            },
498            Error::RequestAlreadyPaid => ErrorResponse {
499                code: ErrorCode::InvoiceAlreadyPaid,
500                detail: "Invoice already paid.".to_string(),
501            },
502            Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
503                ErrorResponse {
504                    code: ErrorCode::TransactionUnbalanced,
505                    detail: format!(
506                        "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
507                    ),
508                }
509            }
510            Error::MintingDisabled => ErrorResponse {
511                code: ErrorCode::MintingDisabled,
512                detail: err.to_string(),
513            },
514            Error::BlindedMessageAlreadySigned => ErrorResponse {
515                code: ErrorCode::BlindedMessageAlreadySigned,
516                detail: err.to_string(),
517            },
518            Error::InsufficientFunds => ErrorResponse {
519                code: ErrorCode::TransactionUnbalanced,
520                detail: err.to_string(),
521            },
522            Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
523                code: ErrorCode::AmountOutofLimitRange,
524                detail: err.to_string(),
525            },
526            Error::ExpiredQuote(_, _) => ErrorResponse {
527                code: ErrorCode::QuoteExpired,
528                detail: err.to_string(),
529            },
530            Error::PendingQuote => ErrorResponse {
531                code: ErrorCode::QuotePending,
532                detail: err.to_string(),
533            },
534            Error::TokenPending => ErrorResponse {
535                code: ErrorCode::TokenPending,
536                detail: err.to_string(),
537            },
538            Error::ClearAuthRequired => ErrorResponse {
539                code: ErrorCode::ClearAuthRequired,
540                detail: Error::ClearAuthRequired.to_string(),
541            },
542            Error::ClearAuthFailed => ErrorResponse {
543                code: ErrorCode::ClearAuthFailed,
544                detail: Error::ClearAuthFailed.to_string(),
545            },
546            Error::BlindAuthRequired => ErrorResponse {
547                code: ErrorCode::BlindAuthRequired,
548                detail: Error::BlindAuthRequired.to_string(),
549            },
550            Error::BlindAuthFailed => ErrorResponse {
551                code: ErrorCode::BlindAuthFailed,
552                detail: Error::BlindAuthFailed.to_string(),
553            },
554            Error::NUT20(err) => ErrorResponse {
555                code: ErrorCode::WitnessMissingOrInvalid,
556                detail: err.to_string(),
557            },
558            Error::DuplicateInputs => ErrorResponse {
559                code: ErrorCode::DuplicateInputs,
560                detail: err.to_string(),
561            },
562            Error::DuplicateOutputs => ErrorResponse {
563                code: ErrorCode::DuplicateOutputs,
564                detail: err.to_string(),
565            },
566            Error::MultipleUnits => ErrorResponse {
567                code: ErrorCode::MultipleUnits,
568                detail: err.to_string(),
569            },
570            Error::UnitMismatch => ErrorResponse {
571                code: ErrorCode::UnitMismatch,
572                detail: err.to_string(),
573            },
574            Error::UnpaidQuote => ErrorResponse {
575                code: ErrorCode::QuoteNotPaid,
576                detail: Error::UnpaidQuote.to_string(),
577            },
578            Error::NUT11(err) => {
579                let code = map_nut11_error(&err);
580                let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
581                    Some("P2PK signatures are required but not provided".to_string())
582                } else {
583                    None
584                };
585                ErrorResponse {
586                    code,
587                    detail: match extra {
588                        Some(extra) => format!("{err}. {extra}"),
589                        None => err.to_string(),
590                    },
591                }
592            },
593            Error::DuplicateSignatureError => ErrorResponse {
594                code: ErrorCode::DuplicateSignature,
595                detail: err.to_string(),
596            },
597            _ => ErrorResponse {
598                code: ErrorCode::Unknown(9999),
599                detail: err.to_string(),
600            },
601        }
602    }
603}
604
605#[cfg(feature = "mint")]
606impl From<crate::database::Error> for Error {
607    fn from(db_error: crate::database::Error) -> Self {
608        match db_error {
609            crate::database::Error::InvalidStateTransition(state) => match state {
610                crate::state::Error::Pending => Self::TokenPending,
611                crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
612                state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
613            },
614            db_error => Self::Database(db_error),
615        }
616    }
617}
618
619#[cfg(not(feature = "mint"))]
620impl From<crate::database::Error> for Error {
621    fn from(db_error: crate::database::Error) -> Self {
622        Self::Database(db_error)
623    }
624}
625
626impl From<ErrorResponse> for Error {
627    fn from(err: ErrorResponse) -> Error {
628        match err.code {
629            ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
630            ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
631            ErrorCode::QuotePending => Self::PendingQuote,
632            ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
633            ErrorCode::KeysetNotFound => Self::UnknownKeySet,
634            ErrorCode::KeysetInactive => Self::InactiveKeyset,
635            ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
636            ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
637            ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
638            ErrorCode::MintingDisabled => Self::MintingDisabled,
639            ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
640            ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
641            ErrorCode::LightningError => Self::PaymentFailed,
642            ErrorCode::AmountOutofLimitRange => {
643                Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
644            }
645            ErrorCode::TokenPending => Self::TokenPending,
646            ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
647            ErrorCode::DuplicateInputs => Self::DuplicateInputs,
648            ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
649            ErrorCode::MultipleUnits => Self::MultipleUnits,
650            ErrorCode::UnitMismatch => Self::UnitMismatch,
651            ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
652            ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
653            ErrorCode::DuplicateSignature => Self::DuplicateSignatureError,
654            _ => Self::UnknownErrorResponse(err.to_string()),
655        }
656    }
657}
658
659/// Possible Error Codes
660#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
661#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
662pub enum ErrorCode {
663    /// Token is already spent
664    TokenAlreadySpent,
665    /// Token Pending
666    TokenPending,
667    /// Quote is not paid
668    QuoteNotPaid,
669    /// Quote is not expired
670    QuoteExpired,
671    /// Quote Pending
672    QuotePending,
673    /// Keyset is not found
674    KeysetNotFound,
675    /// Keyset inactive
676    KeysetInactive,
677    /// Blinded Message Already signed
678    BlindedMessageAlreadySigned,
679    /// Unsupported unit
680    UnsupportedUnit,
681    /// Token already issed for quote
682    TokensAlreadyIssued,
683    /// Minting Disabled
684    MintingDisabled,
685    /// Invoice Already Paid
686    InvoiceAlreadyPaid,
687    /// Token Not Verified
688    TokenNotVerified,
689    /// Lightning Error
690    LightningError,
691    /// Unbalanced Error
692    TransactionUnbalanced,
693    /// Amount outside of allowed range
694    AmountOutofLimitRange,
695    /// Witness missing or invalid
696    WitnessMissingOrInvalid,
697    /// Duplicate Inputs
698    DuplicateInputs,
699    /// Duplicate Outputs
700    DuplicateOutputs,
701    /// Multiple Units
702    MultipleUnits,
703    /// Input unit does not match output
704    UnitMismatch,
705    /// Clear Auth Required
706    ClearAuthRequired,
707    /// Clear Auth Failed
708    ClearAuthFailed,
709    /// Blind Auth Required
710    BlindAuthRequired,
711    /// Blind Auth Failed
712    BlindAuthFailed,
713    /// Duplicate signature from same pubkey
714    DuplicateSignature,
715    /// Unknown error code
716    Unknown(u16),
717}
718
719impl ErrorCode {
720    /// Error code from u16
721    pub fn from_code(code: u16) -> Self {
722        match code {
723            10002 => Self::BlindedMessageAlreadySigned,
724            10003 => Self::TokenNotVerified,
725            11001 => Self::TokenAlreadySpent,
726            11002 => Self::TransactionUnbalanced,
727            11005 => Self::UnsupportedUnit,
728            11006 => Self::AmountOutofLimitRange,
729            11007 => Self::DuplicateInputs,
730            11008 => Self::DuplicateOutputs,
731            11009 => Self::MultipleUnits,
732            11010 => Self::UnitMismatch,
733            11012 => Self::TokenPending,
734            12001 => Self::KeysetNotFound,
735            12002 => Self::KeysetInactive,
736            20000 => Self::LightningError,
737            20001 => Self::QuoteNotPaid,
738            20002 => Self::TokensAlreadyIssued,
739            20003 => Self::MintingDisabled,
740            20005 => Self::QuotePending,
741            20006 => Self::InvoiceAlreadyPaid,
742            20007 => Self::QuoteExpired,
743            20008 => Self::WitnessMissingOrInvalid,
744            20009 => Self::DuplicateSignature,
745            30001 => Self::ClearAuthRequired,
746            30002 => Self::ClearAuthFailed,
747            31001 => Self::BlindAuthRequired,
748            31002 => Self::BlindAuthFailed,
749            _ => Self::Unknown(code),
750        }
751    }
752
753    /// Error code to u16
754    pub fn to_code(&self) -> u16 {
755        match self {
756            Self::BlindedMessageAlreadySigned => 10002,
757            Self::TokenNotVerified => 10003,
758            Self::TokenAlreadySpent => 11001,
759            Self::TransactionUnbalanced => 11002,
760            Self::UnsupportedUnit => 11005,
761            Self::AmountOutofLimitRange => 11006,
762            Self::DuplicateInputs => 11007,
763            Self::DuplicateOutputs => 11008,
764            Self::MultipleUnits => 11009,
765            Self::UnitMismatch => 11010,
766            Self::TokenPending => 11012,
767            Self::KeysetNotFound => 12001,
768            Self::KeysetInactive => 12002,
769            Self::LightningError => 20000,
770            Self::QuoteNotPaid => 20001,
771            Self::TokensAlreadyIssued => 20002,
772            Self::MintingDisabled => 20003,
773            Self::QuotePending => 20005,
774            Self::InvoiceAlreadyPaid => 20006,
775            Self::QuoteExpired => 20007,
776            Self::WitnessMissingOrInvalid => 20008,
777            Self::DuplicateSignature => 20009,
778            Self::ClearAuthRequired => 30001,
779            Self::ClearAuthFailed => 30002,
780            Self::BlindAuthRequired => 31001,
781            Self::BlindAuthFailed => 31002,
782            Self::Unknown(code) => *code,
783        }
784    }
785}
786
787impl Serialize for ErrorCode {
788    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
789    where
790        S: Serializer,
791    {
792        serializer.serialize_u16(self.to_code())
793    }
794}
795
796impl<'de> Deserialize<'de> for ErrorCode {
797    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
798    where
799        D: Deserializer<'de>,
800    {
801        let code = u16::deserialize(deserializer)?;
802
803        Ok(ErrorCode::from_code(code))
804    }
805}
806
807impl fmt::Display for ErrorCode {
808    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
809        write!(f, "{}", self.to_code())
810    }
811}