Skip to main content

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    /// Over issue - tried to issue more than paid
51    #[error("Cannot issue more than amount paid")]
52    OverIssue,
53    /// Witness missing or invalid
54    #[error("Signature missing or invalid")]
55    SignatureMissingOrInvalid,
56    /// Amountless Invoice Not supported
57    #[error("Amount Less Invoice is not allowed")]
58    AmountLessNotAllowed,
59    /// Multi-Part Internal Melt Quotes are not supported
60    #[error("Multi-Part Internal Melt Quotes are not supported")]
61    InternalMultiPartMeltQuote,
62    /// Multi-Part Payment not supported for unit and method
63    #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
64    MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
65    /// Clear Auth Required
66    #[error("Clear Auth Required")]
67    ClearAuthRequired,
68    /// Blind Auth Required
69    #[error("Blind Auth Required")]
70    BlindAuthRequired,
71    /// Clear Auth Failed
72    #[error("Clear Auth Failed")]
73    ClearAuthFailed,
74    /// Blind Auth Failed
75    #[error("Blind Auth Failed")]
76    BlindAuthFailed,
77    /// Auth settings undefined
78    #[error("Auth settings undefined")]
79    AuthSettingsUndefined,
80    /// Mint time outside of tolerance
81    #[error("Mint time outside of tolerance")]
82    MintTimeExceedsTolerance,
83    /// Insufficient blind auth tokens
84    #[error("Insufficient blind auth tokens, must reauth")]
85    InsufficientBlindAuthTokens,
86    /// Auth localstore undefined
87    #[error("Auth localstore undefined")]
88    AuthLocalstoreUndefined,
89    /// Wallet cat not set
90    #[error("Wallet cat not set")]
91    CatNotSet,
92    /// Could not get mint info
93    #[error("Could not get mint info")]
94    CouldNotGetMintInfo,
95    /// Multi-Part Payment not supported for unit and method
96    #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
97    AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
98    /// Duplicate Payment id
99    #[error("Payment id seen for mint")]
100    DuplicatePaymentId,
101    /// Pubkey required
102    #[error("Pubkey required")]
103    PubkeyRequired,
104    /// Invalid payment method
105    #[error("Invalid payment method")]
106    InvalidPaymentMethod,
107    /// Amount undefined
108    #[error("Amount undefined")]
109    AmountUndefined,
110    /// Unsupported payment method
111    #[error("Payment method unsupported")]
112    UnsupportedPaymentMethod,
113    /// Payment method required
114    #[error("Payment method required")]
115    PaymentMethodRequired,
116    /// Could not parse bolt12
117    #[error("Could not parse bolt12")]
118    Bolt12parse,
119    /// Could not parse invoice (bolt11 or bolt12)
120    #[error("Could not parse invoice")]
121    InvalidInvoice,
122
123    /// BIP353 address parsing error
124    #[error("Failed to parse BIP353 address: {0}")]
125    Bip353Parse(String),
126
127    /// Operation timeout
128    #[error("Operation timeout")]
129    Timeout,
130
131    /// BIP353 address resolution error
132    #[error("Failed to resolve BIP353 address: {0}")]
133    Bip353Resolve(String),
134    /// BIP353 no Lightning offer found
135    #[error("No Lightning offer found in BIP353 payment instructions")]
136    Bip353NoLightningOffer,
137
138    /// Lightning Address parsing error
139    #[error("Failed to parse Lightning address: {0}")]
140    LightningAddressParse(String),
141    /// Lightning Address request error
142    #[error("Failed to request invoice from Lightning address service: {0}")]
143    LightningAddressRequest(String),
144
145    /// Internal Error - Send error
146    #[error("Internal send error: {0}")]
147    SendError(String),
148
149    /// Internal Error - Recv error
150    #[error("Internal receive error: {0}")]
151    RecvError(String),
152
153    // Mint Errors
154    /// Minting is disabled
155    #[error("Minting is disabled")]
156    MintingDisabled,
157    /// Quote is not known
158    #[error("Unknown quote")]
159    UnknownQuote,
160    /// Quote is expired
161    #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
162    ExpiredQuote(u64, u64),
163    /// Amount is outside of allowed range
164    #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
165    AmountOutofLimitRange(Amount, Amount, Amount),
166    /// Quote is not paid
167    #[error("Quote not paid")]
168    UnpaidQuote,
169    /// Quote is pending
170    #[error("Quote pending")]
171    PendingQuote,
172    /// ecash already issued for quote
173    #[error("Quote already issued")]
174    IssuedQuote,
175    /// Quote has already been paid
176    #[error("Quote is already paid")]
177    PaidQuote,
178    /// Payment state is unknown
179    #[error("Payment state is unknown")]
180    UnknownPaymentState,
181    /// Melting is disabled
182    #[error("Melting is disabled")]
183    MeltingDisabled,
184    /// Unknown Keyset
185    #[error("Unknown Keyset")]
186    UnknownKeySet,
187    /// BlindedMessage is already signed
188    #[error("Blinded Message is already signed")]
189    BlindedMessageAlreadySigned,
190    /// Inactive Keyset
191    #[error("Inactive Keyset")]
192    InactiveKeyset,
193    /// Transaction unbalanced
194    #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
195    TransactionUnbalanced(u64, u64, u64),
196    /// Duplicate proofs provided
197    #[error("Duplicate Inputs")]
198    DuplicateInputs,
199    /// Duplicate output
200    #[error("Duplicate outputs")]
201    DuplicateOutputs,
202    /// Maximum number of inputs exceeded
203    #[error("Maximum inputs exceeded: {actual} provided, max {max}")]
204    MaxInputsExceeded {
205        /// Actual number of inputs provided
206        actual: usize,
207        /// Maximum allowed inputs
208        max: usize,
209    },
210    /// Maximum number of outputs exceeded
211    #[error("Maximum outputs exceeded: {actual} provided, max {max}")]
212    MaxOutputsExceeded {
213        /// Actual number of outputs provided
214        actual: usize,
215        /// Maximum allowed outputs
216        max: usize,
217    },
218    /// Proof content too large (secret or witness exceeds max length)
219    #[error("Proof content too large: {actual} bytes, max {max}")]
220    ProofContentTooLarge {
221        /// Actual size in bytes
222        actual: usize,
223        /// Maximum allowed size in bytes
224        max: usize,
225    },
226    /// Request field content too large (description or extra exceeds max length)
227    #[error("Request field '{field}' too large: {actual} bytes, max {max}")]
228    RequestFieldTooLarge {
229        /// Name of the field that exceeded the limit
230        field: String,
231        /// Actual size in bytes
232        actual: usize,
233        /// Maximum allowed size in bytes
234        max: usize,
235    },
236    /// Multiple units provided
237    #[error("Cannot have multiple units")]
238    MultipleUnits,
239    /// Unit mismatch
240    #[error("Input unit must match output")]
241    UnitMismatch,
242    /// Sig all cannot be used in melt
243    #[error("Sig all cannot be used in melt")]
244    SigAllUsedInMelt,
245    /// Token is already spent
246    #[error("Token Already Spent")]
247    TokenAlreadySpent,
248    /// Token is already pending
249    #[error("Token Pending")]
250    TokenPending,
251    /// Internal Error
252    #[error("Internal Error")]
253    Internal,
254    /// Oidc config not set
255    #[error("Oidc client not set")]
256    OidcNotSet,
257
258    // Wallet Errors
259    /// P2PK spending conditions not met
260    #[error("P2PK condition not met `{0}`")]
261    P2PKConditionsNotMet(String),
262    /// Duplicate signature from same pubkey in P2PK
263    #[error("Duplicate signature from same pubkey in P2PK")]
264    DuplicateSignatureError,
265    /// Spending Locktime not provided
266    #[error("Spending condition locktime not provided")]
267    LocktimeNotProvided,
268    /// Invalid Spending Conditions
269    #[error("Invalid spending conditions: `{0}`")]
270    InvalidSpendConditions(String),
271    /// Incorrect Wallet
272    #[error("Incorrect wallet: `{0}`")]
273    IncorrectWallet(String),
274    /// Unknown Wallet
275    #[error("Unknown wallet: `{0}`")]
276    #[cfg(feature = "wallet")]
277    UnknownWallet(WalletKey),
278    /// Max Fee Ecxeded
279    #[error("Max fee exceeded")]
280    MaxFeeExceeded,
281    /// Url path segments could not be joined
282    #[error("Url path segments could not be joined")]
283    UrlPathSegments,
284    ///  Unknown error response
285    #[error("Unknown error response: `{0}`")]
286    UnknownErrorResponse(String),
287    /// Invalid DLEQ proof
288    #[error("Could not verify DLEQ proof")]
289    CouldNotVerifyDleq,
290    /// Dleq Proof not provided for signature
291    #[error("Dleq proof not provided for signature")]
292    DleqProofNotProvided,
293    /// Incorrect Mint
294    /// Token does not match wallet mint
295    #[error("Token does not match wallet mint")]
296    IncorrectMint,
297    /// Receive can only be used with tokens from single mint
298    #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
299    MultiMintTokenNotSupported,
300    /// Preimage not provided
301    #[error("Preimage not provided")]
302    PreimageNotProvided,
303
304    /// Unknown mint
305    #[error("Unknown mint: {mint_url}")]
306    UnknownMint {
307        /// URL of the unknown mint
308        mint_url: String,
309    },
310    /// Transfer between mints timed out
311    #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
312    TransferTimeout {
313        /// Source mint URL
314        source_mint: String,
315        /// Target mint URL
316        target_mint: String,
317        /// Amount that failed to transfer
318        amount: Amount,
319    },
320    /// Insufficient Funds
321    #[error("Insufficient funds")]
322    InsufficientFunds,
323    /// Unexpected proof state
324    #[error("Unexpected proof state")]
325    UnexpectedProofState,
326    /// No active keyset
327    #[error("No active keyset")]
328    NoActiveKeyset,
329    /// Incorrect quote amount
330    #[error("Incorrect quote amount")]
331    IncorrectQuoteAmount,
332    /// Invoice Description not supported
333    #[error("Invoice Description not supported")]
334    InvoiceDescriptionUnsupported,
335    /// Invalid transaction direction
336    #[error("Invalid transaction direction")]
337    InvalidTransactionDirection,
338    /// Invalid transaction id
339    #[error("Invalid transaction id")]
340    InvalidTransactionId,
341    /// Transaction not found
342    #[error("Transaction not found")]
343    TransactionNotFound,
344    /// Invalid operation kind
345    #[error("Invalid operation kind")]
346    InvalidOperationKind,
347    /// Invalid operation state
348    #[error("Invalid operation state")]
349    InvalidOperationState,
350    /// Operation not found
351    #[error("Operation not found")]
352    OperationNotFound,
353    /// KV Store invalid key or namespace
354    #[error("Invalid KV store key or namespace: {0}")]
355    KVStoreInvalidKey(String),
356    /// Concurrent update detected
357    #[error("Concurrent update detected")]
358    ConcurrentUpdate,
359    /// Invalid response from mint
360    #[error("Invalid mint response: {0}")]
361    InvalidMintResponse(String),
362    /// Subscription error
363    #[error("Subscription error: {0}")]
364    SubscriptionError(String),
365    /// Custom Error
366    #[error("`{0}`")]
367    Custom(String),
368
369    // External Error conversions
370    /// Parse invoice error
371    #[error(transparent)]
372    Invoice(#[from] lightning_invoice::ParseOrSemanticError),
373    /// Bip32 error
374    #[error(transparent)]
375    Bip32(#[from] bitcoin::bip32::Error),
376    /// Parse int error
377    #[error(transparent)]
378    ParseInt(#[from] std::num::ParseIntError),
379    /// Parse 9rl Error
380    #[error(transparent)]
381    UrlParseError(#[from] url::ParseError),
382    /// Utf8 parse error
383    #[error(transparent)]
384    Utf8ParseError(#[from] std::string::FromUtf8Error),
385    /// Serde Json error
386    #[error(transparent)]
387    SerdeJsonError(#[from] serde_json::Error),
388    /// Base64 error
389    #[error(transparent)]
390    Base64Error(#[from] bitcoin::base64::DecodeError),
391    /// From hex error
392    #[error(transparent)]
393    HexError(#[from] hex::Error),
394    /// Http transport error
395    #[error("Http transport error {0:?}: {1}")]
396    HttpError(Option<u16>, String),
397    /// Parse invoice error
398    #[cfg(feature = "mint")]
399    #[error(transparent)]
400    Uuid(#[from] uuid::Error),
401    // Crate error conversions
402    /// Cashu Url Error
403    #[error(transparent)]
404    CashuUrl(#[from] crate::mint_url::Error),
405    /// Secret error
406    #[error(transparent)]
407    Secret(#[from] crate::secret::Error),
408    /// Amount Error
409    #[error(transparent)]
410    AmountError(#[from] crate::amount::Error),
411    /// DHKE Error
412    #[error(transparent)]
413    DHKE(#[from] crate::dhke::Error),
414    /// NUT00 Error
415    #[error(transparent)]
416    NUT00(#[from] crate::nuts::nut00::Error),
417    /// Nut01 error
418    #[error(transparent)]
419    NUT01(#[from] crate::nuts::nut01::Error),
420    /// NUT02 error
421    #[error(transparent)]
422    NUT02(#[from] crate::nuts::nut02::Error),
423    /// NUT03 error
424    #[error(transparent)]
425    NUT03(#[from] crate::nuts::nut03::Error),
426    /// NUT04 error
427    #[error(transparent)]
428    NUT04(#[from] crate::nuts::nut04::Error),
429    /// NUT05 error
430    #[error(transparent)]
431    NUT05(#[from] crate::nuts::nut05::Error),
432    /// NUT11 Error
433    #[error(transparent)]
434    NUT11(#[from] crate::nuts::nut11::Error),
435    /// NUT12 Error
436    #[error(transparent)]
437    NUT12(#[from] crate::nuts::nut12::Error),
438    /// NUT13 Error
439    #[error(transparent)]
440    #[cfg(feature = "wallet")]
441    NUT13(#[from] crate::nuts::nut13::Error),
442    /// NUT14 Error
443    #[error(transparent)]
444    NUT14(#[from] crate::nuts::nut14::Error),
445    /// NUT18 Error
446    #[error(transparent)]
447    NUT18(#[from] crate::nuts::nut18::Error),
448    /// NUT20 Error
449    #[error(transparent)]
450    NUT20(#[from] crate::nuts::nut20::Error),
451    /// NUT21 Error
452    #[error(transparent)]
453    NUT21(#[from] crate::nuts::nut21::Error),
454    /// NUT22 Error
455    #[error(transparent)]
456    NUT22(#[from] crate::nuts::nut22::Error),
457    /// NUT23 Error
458    #[error(transparent)]
459    NUT23(#[from] crate::nuts::nut23::Error),
460    /// Quote ID Error
461    #[error(transparent)]
462    #[cfg(feature = "mint")]
463    QuoteId(#[from] crate::quote_id::QuoteIdError),
464    /// From slice error
465    #[error(transparent)]
466    TryFromSliceError(#[from] TryFromSliceError),
467    /// Database Error
468    #[error(transparent)]
469    Database(crate::database::Error),
470    /// Payment Error
471    #[error(transparent)]
472    #[cfg(feature = "mint")]
473    Payment(#[from] crate::payment::Error),
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479
480    #[test]
481    fn test_is_definitive_failure() {
482        // Test definitive failures
483        assert!(Error::AmountOverflow.is_definitive_failure());
484        assert!(Error::TokenAlreadySpent.is_definitive_failure());
485        assert!(Error::MintingDisabled.is_definitive_failure());
486
487        // Test HTTP client errors (4xx) - simulated
488        assert!(Error::HttpError(Some(400), "Bad Request".to_string()).is_definitive_failure());
489        assert!(Error::HttpError(Some(404), "Not Found".to_string()).is_definitive_failure());
490        assert!(
491            Error::HttpError(Some(429), "Too Many Requests".to_string()).is_definitive_failure()
492        );
493
494        // Test ambiguous failures
495        assert!(!Error::Timeout.is_definitive_failure());
496        assert!(!Error::Internal.is_definitive_failure());
497        assert!(!Error::ConcurrentUpdate.is_definitive_failure());
498
499        // Test HTTP server errors (5xx)
500        assert!(
501            !Error::HttpError(Some(500), "Internal Server Error".to_string())
502                .is_definitive_failure()
503        );
504        assert!(!Error::HttpError(Some(502), "Bad Gateway".to_string()).is_definitive_failure());
505        assert!(
506            !Error::HttpError(Some(503), "Service Unavailable".to_string()).is_definitive_failure()
507        );
508
509        // Test HTTP network errors (no status)
510        assert!(!Error::HttpError(None, "Connection refused".to_string()).is_definitive_failure());
511    }
512}
513
514impl Error {
515    /// Check if the error is a definitive failure
516    ///
517    /// A definitive failure means the mint definitely rejected the request
518    /// and did not update its state. In these cases, it is safe to revert
519    /// the transaction locally.
520    ///
521    /// If false, the failure is ambiguous (e.g. timeout, network error, 500)
522    /// and the transaction state at the mint is unknown.
523    pub fn is_definitive_failure(&self) -> bool {
524        match self {
525            // Logic/Validation Errors (Safe to revert)
526            Self::AmountKey
527            | Self::KeysetUnknown(_)
528            | Self::UnsupportedUnit
529            | Self::InvoiceAmountUndefined
530            | Self::SplitValuesGreater
531            | Self::AmountOverflow
532            | Self::OverIssue
533            | Self::SignatureMissingOrInvalid
534            | Self::AmountLessNotAllowed
535            | Self::InternalMultiPartMeltQuote
536            | Self::MppUnitMethodNotSupported(_, _)
537            | Self::AmountlessInvoiceNotSupported(_, _)
538            | Self::DuplicatePaymentId
539            | Self::PubkeyRequired
540            | Self::InvalidPaymentMethod
541            | Self::UnsupportedPaymentMethod
542            | Self::InvalidInvoice
543            | Self::MintingDisabled
544            | Self::UnknownQuote
545            | Self::ExpiredQuote(_, _)
546            | Self::AmountOutofLimitRange(_, _, _)
547            | Self::UnpaidQuote
548            | Self::PendingQuote
549            | Self::IssuedQuote
550            | Self::PaidQuote
551            | Self::MeltingDisabled
552            | Self::UnknownKeySet
553            | Self::BlindedMessageAlreadySigned
554            | Self::InactiveKeyset
555            | Self::TransactionUnbalanced(_, _, _)
556            | Self::DuplicateInputs
557            | Self::DuplicateOutputs
558            | Self::MultipleUnits
559            | Self::UnitMismatch
560            | Self::SigAllUsedInMelt
561            | Self::TokenAlreadySpent
562            | Self::TokenPending
563            | Self::P2PKConditionsNotMet(_)
564            | Self::DuplicateSignatureError
565            | Self::LocktimeNotProvided
566            | Self::InvalidSpendConditions(_)
567            | Self::IncorrectWallet(_)
568            | Self::MaxFeeExceeded
569            | Self::DleqProofNotProvided
570            | Self::IncorrectMint
571            | Self::MultiMintTokenNotSupported
572            | Self::PreimageNotProvided
573            | Self::UnknownMint { .. }
574            | Self::UnexpectedProofState
575            | Self::NoActiveKeyset
576            | Self::IncorrectQuoteAmount
577            | Self::InvoiceDescriptionUnsupported
578            | Self::InvalidTransactionDirection
579            | Self::InvalidTransactionId
580            | Self::InvalidOperationKind
581            | Self::InvalidOperationState
582            | Self::OperationNotFound
583            | Self::KVStoreInvalidKey(_) => true,
584
585            // HTTP Errors
586            Self::HttpError(Some(status), _) => {
587                // Client errors (400-499) are definitive failures
588                // Server errors (500-599) are ambiguous
589                (400..500).contains(status)
590            }
591
592            // Ambiguous Errors (Unsafe to revert)
593            Self::Timeout
594            | Self::Internal
595            | Self::UnknownPaymentState
596            | Self::CouldNotGetMintInfo
597            | Self::UnknownErrorResponse(_)
598            | Self::InvalidMintResponse(_)
599            | Self::ConcurrentUpdate
600            | Self::SendError(_)
601            | Self::RecvError(_)
602            | Self::TransferTimeout { .. } => false,
603
604            // Network/IO/Parsing Errors (Usually ambiguous as they could happen reading response)
605            Self::HttpError(None, _) // No status code means network error
606            | Self::SerdeJsonError(_) // Could be malformed success response
607            | Self::Database(_)
608            | Self::Custom(_) => false,
609
610            // Auth Errors (Generally definitive if rejected)
611            Self::ClearAuthRequired
612            | Self::BlindAuthRequired
613            | Self::ClearAuthFailed
614            | Self::BlindAuthFailed
615            | Self::InsufficientBlindAuthTokens
616            | Self::AuthSettingsUndefined
617            | Self::AuthLocalstoreUndefined
618            | Self::OidcNotSet => true,
619
620            // External conversions - check specifically
621            Self::Invoice(_) => true, // Parsing error
622            Self::Bip32(_) => true, // Key derivation error
623            Self::ParseInt(_) => true,
624            Self::UrlParseError(_) => true,
625            Self::Utf8ParseError(_) => true,
626            Self::Base64Error(_) => true,
627            Self::HexError(_) => true,
628            #[cfg(feature = "mint")]
629            Self::Uuid(_) => true,
630            Self::CashuUrl(_) => true,
631            Self::Secret(_) => true,
632            Self::AmountError(_) => true,
633            Self::DHKE(_) => true, // Crypto errors
634            Self::NUT00(_) => true,
635            Self::NUT01(_) => true,
636            Self::NUT02(_) => true,
637            Self::NUT03(_) => true,
638            Self::NUT04(_) => true,
639            Self::NUT05(_) => true,
640            Self::NUT11(_) => true,
641            Self::NUT12(_) => true,
642            #[cfg(feature = "wallet")]
643            Self::NUT13(_) => true,
644            Self::NUT14(_) => true,
645            Self::NUT18(_) => true,
646            Self::NUT20(_) => true,
647            Self::NUT21(_) => true,
648            Self::NUT22(_) => true,
649            Self::NUT23(_) => true,
650            #[cfg(feature = "mint")]
651            Self::QuoteId(_) => true,
652            Self::TryFromSliceError(_) => true,
653            #[cfg(feature = "mint")]
654            Self::Payment(_) => false, // Payment errors could be ambiguous? assume ambiguous to be safe
655
656            // Catch-all
657            _ => false,
658        }
659    }
660}
661
662/// CDK Error Response
663///
664/// See NUT definition in [00](https://github.com/cashubtc/nuts/blob/main/00.md)
665#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
666#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
667pub struct ErrorResponse {
668    /// Error Code
669    pub code: ErrorCode,
670    /// Human readable description
671    #[serde(default)]
672    pub detail: String,
673}
674
675impl fmt::Display for ErrorResponse {
676    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677        write!(f, "code: {}, detail: {}", self.code, self.detail)
678    }
679}
680
681impl ErrorResponse {
682    /// Create new [`ErrorResponse`]
683    pub fn new(code: ErrorCode, detail: String) -> Self {
684        Self { code, detail }
685    }
686
687    /// Error response from json
688    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
689        let value: Value = serde_json::from_str(json)?;
690
691        Self::from_value(value)
692    }
693
694    /// Error response from json Value
695    pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
696        match serde_json::from_value::<ErrorResponse>(value.clone()) {
697            Ok(res) => Ok(res),
698            Err(_) => Ok(Self {
699                code: ErrorCode::Unknown(999),
700                detail: value.to_string(),
701            }),
702        }
703    }
704}
705
706/// Maps NUT11 errors to appropriate error codes
707/// All NUT11 errors are witness/signature related, so they map to WitnessMissingOrInvalid (20008)
708fn map_nut11_error(_nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
709    // All NUT11 errors relate to P2PK/witness validation, which maps to 20008
710    ErrorCode::WitnessMissingOrInvalid
711}
712
713impl From<Error> for ErrorResponse {
714    fn from(err: Error) -> ErrorResponse {
715        match err {
716            Error::TokenAlreadySpent => ErrorResponse {
717                code: ErrorCode::TokenAlreadySpent,
718                detail: err.to_string(),
719            },
720            Error::UnsupportedUnit => ErrorResponse {
721                code: ErrorCode::UnsupportedUnit,
722                detail: err.to_string(),
723            },
724            Error::PaymentFailed => ErrorResponse {
725                code: ErrorCode::LightningError,
726                detail: err.to_string(),
727            },
728            Error::RequestAlreadyPaid => ErrorResponse {
729                code: ErrorCode::InvoiceAlreadyPaid,
730                detail: "Invoice already paid.".to_string(),
731            },
732            Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
733                ErrorResponse {
734                    code: ErrorCode::TransactionUnbalanced,
735                    detail: format!(
736                        "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
737                    ),
738                }
739            }
740            Error::MintingDisabled => ErrorResponse {
741                code: ErrorCode::MintingDisabled,
742                detail: err.to_string(),
743            },
744            Error::BlindedMessageAlreadySigned => ErrorResponse {
745                code: ErrorCode::BlindedMessageAlreadySigned,
746                detail: err.to_string(),
747            },
748            Error::InsufficientFunds => ErrorResponse {
749                code: ErrorCode::TransactionUnbalanced,
750                detail: err.to_string(),
751            },
752            Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
753                code: ErrorCode::AmountOutofLimitRange,
754                detail: err.to_string(),
755            },
756            Error::ExpiredQuote(_, _) => ErrorResponse {
757                code: ErrorCode::QuoteExpired,
758                detail: err.to_string(),
759            },
760            Error::PendingQuote => ErrorResponse {
761                code: ErrorCode::QuotePending,
762                detail: err.to_string(),
763            },
764            Error::TokenPending => ErrorResponse {
765                code: ErrorCode::TokenPending,
766                detail: err.to_string(),
767            },
768            Error::ClearAuthRequired => ErrorResponse {
769                code: ErrorCode::ClearAuthRequired,
770                detail: Error::ClearAuthRequired.to_string(),
771            },
772            Error::ClearAuthFailed => ErrorResponse {
773                code: ErrorCode::ClearAuthFailed,
774                detail: Error::ClearAuthFailed.to_string(),
775            },
776            Error::BlindAuthRequired => ErrorResponse {
777                code: ErrorCode::BlindAuthRequired,
778                detail: Error::BlindAuthRequired.to_string(),
779            },
780            Error::BlindAuthFailed => ErrorResponse {
781                code: ErrorCode::BlindAuthFailed,
782                detail: Error::BlindAuthFailed.to_string(),
783            },
784            Error::NUT20(err) => ErrorResponse {
785                code: ErrorCode::WitnessMissingOrInvalid,
786                detail: err.to_string(),
787            },
788            Error::DuplicateInputs => ErrorResponse {
789                code: ErrorCode::DuplicateInputs,
790                detail: err.to_string(),
791            },
792            Error::DuplicateOutputs => ErrorResponse {
793                code: ErrorCode::DuplicateOutputs,
794                detail: err.to_string(),
795            },
796            Error::MultipleUnits => ErrorResponse {
797                code: ErrorCode::MultipleUnits,
798                detail: err.to_string(),
799            },
800            Error::UnitMismatch => ErrorResponse {
801                code: ErrorCode::UnitMismatch,
802                detail: err.to_string(),
803            },
804            Error::UnpaidQuote => ErrorResponse {
805                code: ErrorCode::QuoteNotPaid,
806                detail: Error::UnpaidQuote.to_string(),
807            },
808            Error::NUT11(err) => {
809                let code = map_nut11_error(&err);
810                let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
811                    Some("P2PK signatures are required but not provided".to_string())
812                } else {
813                    None
814                };
815                ErrorResponse {
816                    code,
817                    detail: match extra {
818                        Some(extra) => format!("{err}. {extra}"),
819                        None => err.to_string(),
820                    },
821                }
822            },
823            Error::DuplicateSignatureError => ErrorResponse {
824                code: ErrorCode::WitnessMissingOrInvalid,
825                detail: err.to_string(),
826            },
827            Error::IssuedQuote => ErrorResponse {
828                code: ErrorCode::TokensAlreadyIssued,
829                detail: err.to_string(),
830            },
831            Error::UnknownKeySet => ErrorResponse {
832                code: ErrorCode::KeysetNotFound,
833                detail: err.to_string(),
834            },
835            Error::InactiveKeyset => ErrorResponse {
836                code: ErrorCode::KeysetInactive,
837                detail: err.to_string(),
838            },
839            Error::AmountLessNotAllowed => ErrorResponse {
840                code: ErrorCode::AmountlessInvoiceNotSupported,
841                detail: err.to_string(),
842            },
843            Error::IncorrectQuoteAmount => ErrorResponse {
844                code: ErrorCode::IncorrectQuoteAmount,
845                detail: err.to_string(),
846            },
847            Error::PubkeyRequired => ErrorResponse {
848                code: ErrorCode::PubkeyRequired,
849                detail: err.to_string(),
850            },
851            Error::PaidQuote => ErrorResponse {
852                code: ErrorCode::InvoiceAlreadyPaid,
853                detail: err.to_string(),
854            },
855            Error::DuplicatePaymentId => ErrorResponse {
856                code: ErrorCode::InvoiceAlreadyPaid,
857                detail: err.to_string(),
858            },
859            // Database duplicate error indicates another quote with same invoice is already pending/paid
860            Error::Database(crate::database::Error::Duplicate) => ErrorResponse {
861                code: ErrorCode::InvoiceAlreadyPaid,
862                detail: "Invoice already paid or pending".to_string(),
863            },
864
865            // DHKE errors - TokenNotVerified for actual verification failures
866            Error::DHKE(crate::dhke::Error::TokenNotVerified) => ErrorResponse {
867                code: ErrorCode::TokenNotVerified,
868                detail: err.to_string(),
869            },
870            Error::DHKE(_) => ErrorResponse {
871                code: ErrorCode::Unknown(50000),
872                detail: err.to_string(),
873            },
874
875            // Verification errors
876            Error::CouldNotVerifyDleq => ErrorResponse {
877                code: ErrorCode::TokenNotVerified,
878                detail: err.to_string(),
879            },
880            Error::SignatureMissingOrInvalid => ErrorResponse {
881                code: ErrorCode::WitnessMissingOrInvalid,
882                detail: err.to_string(),
883            },
884            Error::SigAllUsedInMelt => ErrorResponse {
885                code: ErrorCode::WitnessMissingOrInvalid,
886                detail: err.to_string(),
887            },
888
889            // Keyset/key errors
890            Error::AmountKey => ErrorResponse {
891                code: ErrorCode::KeysetNotFound,
892                detail: err.to_string(),
893            },
894            Error::KeysetUnknown(_) => ErrorResponse {
895                code: ErrorCode::KeysetNotFound,
896                detail: err.to_string(),
897            },
898            Error::NoActiveKeyset => ErrorResponse {
899                code: ErrorCode::KeysetInactive,
900                detail: err.to_string(),
901            },
902
903            // Quote/payment errors
904            Error::UnknownQuote => ErrorResponse {
905                code: ErrorCode::Unknown(50000),
906                detail: err.to_string(),
907            },
908            Error::MeltingDisabled => ErrorResponse {
909                code: ErrorCode::MintingDisabled,
910                detail: err.to_string(),
911            },
912            Error::PaymentPending => ErrorResponse {
913                code: ErrorCode::QuotePending,
914                detail: err.to_string(),
915            },
916            Error::UnknownPaymentState => ErrorResponse {
917                code: ErrorCode::Unknown(50000),
918                detail: err.to_string(),
919            },
920
921            // Transaction/amount errors
922            Error::SplitValuesGreater => ErrorResponse {
923                code: ErrorCode::TransactionUnbalanced,
924                detail: err.to_string(),
925            },
926            Error::AmountOverflow => ErrorResponse {
927                code: ErrorCode::TransactionUnbalanced,
928                detail: err.to_string(),
929            },
930            Error::OverIssue => ErrorResponse {
931                code: ErrorCode::TransactionUnbalanced,
932                detail: err.to_string(),
933            },
934
935            // Invoice parsing errors - no spec code for invalid format
936            Error::InvalidPaymentRequest => ErrorResponse {
937                code: ErrorCode::Unknown(50000),
938                detail: err.to_string(),
939            },
940            Error::InvoiceAmountUndefined => ErrorResponse {
941                code: ErrorCode::AmountlessInvoiceNotSupported,
942                detail: err.to_string(),
943            },
944
945            // Internal/system errors - use Unknown(99999)
946            Error::Internal => ErrorResponse {
947                code: ErrorCode::Unknown(50000),
948                detail: err.to_string(),
949            },
950            Error::Database(_) => ErrorResponse {
951                code: ErrorCode::Unknown(50000),
952                detail: err.to_string(),
953            },
954            Error::ConcurrentUpdate => ErrorResponse {
955                code: ErrorCode::ConcurrentUpdate,
956                detail: err.to_string(),
957            },
958            Error::MaxInputsExceeded { .. } => ErrorResponse {
959                code: ErrorCode::MaxInputsExceeded,
960                detail: err.to_string()
961            },
962            Error::MaxOutputsExceeded { .. } => ErrorResponse {
963                code: ErrorCode::MaxOutputsExceeded,
964                detail: err.to_string()
965            },
966            // Fallback for any remaining errors - use Unknown(99999) instead of TokenNotVerified
967            _ => ErrorResponse {
968                code: ErrorCode::Unknown(50000),
969                detail: err.to_string(),
970            },
971        }
972    }
973}
974
975#[cfg(feature = "mint")]
976impl From<crate::database::Error> for Error {
977    fn from(db_error: crate::database::Error) -> Self {
978        match db_error {
979            crate::database::Error::InvalidStateTransition(state) => match state {
980                crate::state::Error::Pending => Self::TokenPending,
981                crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
982                crate::state::Error::AlreadyPaid => Self::RequestAlreadyPaid,
983                state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
984            },
985            crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
986            db_error => Self::Database(db_error),
987        }
988    }
989}
990
991#[cfg(not(feature = "mint"))]
992impl From<crate::database::Error> for Error {
993    fn from(db_error: crate::database::Error) -> Self {
994        match db_error {
995            crate::database::Error::ConcurrentUpdate => Self::ConcurrentUpdate,
996            db_error => Self::Database(db_error),
997        }
998    }
999}
1000
1001impl From<ErrorResponse> for Error {
1002    fn from(err: ErrorResponse) -> Error {
1003        match err.code {
1004            // 10xxx - Proof/Token verification errors
1005            ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
1006            // 11xxx - Input/Output errors
1007            ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
1008            ErrorCode::TokenPending => Self::TokenPending,
1009            ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
1010            ErrorCode::OutputsPending => Self::TokenPending, // Map to closest equivalent
1011            ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
1012            ErrorCode::AmountOutofLimitRange => {
1013                Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
1014            }
1015            ErrorCode::DuplicateInputs => Self::DuplicateInputs,
1016            ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
1017            ErrorCode::MultipleUnits => Self::MultipleUnits,
1018            ErrorCode::UnitMismatch => Self::UnitMismatch,
1019            ErrorCode::AmountlessInvoiceNotSupported => Self::AmountLessNotAllowed,
1020            ErrorCode::IncorrectQuoteAmount => Self::IncorrectQuoteAmount,
1021            ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
1022            // 12xxx - Keyset errors
1023            ErrorCode::KeysetNotFound => Self::UnknownKeySet,
1024            ErrorCode::KeysetInactive => Self::InactiveKeyset,
1025            // 20xxx - Quote/Payment errors
1026            ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
1027            ErrorCode::TokensAlreadyIssued => Self::IssuedQuote,
1028            ErrorCode::MintingDisabled => Self::MintingDisabled,
1029            ErrorCode::LightningError => Self::PaymentFailed,
1030            ErrorCode::QuotePending => Self::PendingQuote,
1031            ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
1032            ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
1033            ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
1034            ErrorCode::PubkeyRequired => Self::PubkeyRequired,
1035            // 30xxx - Clear auth errors
1036            ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
1037            ErrorCode::ClearAuthFailed => Self::ClearAuthFailed,
1038            // 31xxx - Blind auth errors
1039            ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
1040            ErrorCode::BlindAuthFailed => Self::BlindAuthFailed,
1041            ErrorCode::BatMintMaxExceeded => Self::InsufficientBlindAuthTokens,
1042            ErrorCode::BatRateLimitExceeded => Self::InsufficientBlindAuthTokens,
1043            _ => Self::UnknownErrorResponse(err.to_string()),
1044        }
1045    }
1046}
1047
1048/// Possible Error Codes
1049#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1050#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
1051pub enum ErrorCode {
1052    // 10xxx - Proof/Token verification errors
1053    /// Proof verification failed (10001)
1054    TokenNotVerified,
1055
1056    // 11xxx - Input/Output errors
1057    /// Proofs already spent (11001)
1058    TokenAlreadySpent,
1059    /// Proofs are pending (11002)
1060    TokenPending,
1061    /// Outputs already signed (11003)
1062    BlindedMessageAlreadySigned,
1063    /// Outputs are pending (11004)
1064    OutputsPending,
1065    /// Transaction is not balanced (11005)
1066    TransactionUnbalanced,
1067    /// Amount outside of limit range (11006)
1068    AmountOutofLimitRange,
1069    /// Duplicate inputs provided (11007)
1070    DuplicateInputs,
1071    /// Duplicate outputs provided (11008)
1072    DuplicateOutputs,
1073    /// Inputs/Outputs of multiple units (11009)
1074    MultipleUnits,
1075    /// Inputs and outputs not of same unit (11010)
1076    UnitMismatch,
1077    /// Amountless invoice is not supported (11011)
1078    AmountlessInvoiceNotSupported,
1079    /// Amount in request does not equal invoice (11012)
1080    IncorrectQuoteAmount,
1081    /// Unit in request is not supported (11013)
1082    UnsupportedUnit,
1083    /// The max number of inputs is exceeded
1084    MaxInputsExceeded,
1085    /// The max number of outputs is exceeded
1086    MaxOutputsExceeded,
1087    // 12xxx - Keyset errors
1088    /// Keyset is not known (12001)
1089    KeysetNotFound,
1090    /// Keyset is inactive, cannot sign messages (12002)
1091    KeysetInactive,
1092
1093    // 20xxx - Quote/Payment errors
1094    /// Quote request is not paid (20001)
1095    QuoteNotPaid,
1096    /// Quote has already been issued (20002)
1097    TokensAlreadyIssued,
1098    /// Minting is disabled (20003)
1099    MintingDisabled,
1100    /// Lightning payment failed (20004)
1101    LightningError,
1102    /// Quote is pending (20005)
1103    QuotePending,
1104    /// Invoice already paid (20006)
1105    InvoiceAlreadyPaid,
1106    /// Quote is expired (20007)
1107    QuoteExpired,
1108    /// Signature for mint request invalid (20008)
1109    WitnessMissingOrInvalid,
1110    /// Pubkey required for mint quote (20009)
1111    PubkeyRequired,
1112
1113    // 30xxx - Clear auth errors
1114    /// Endpoint requires clear auth (30001)
1115    ClearAuthRequired,
1116    /// Clear authentication failed (30002)
1117    ClearAuthFailed,
1118
1119    // 31xxx - Blind auth errors
1120    /// Endpoint requires blind auth (31001)
1121    BlindAuthRequired,
1122    /// Blind authentication failed (31002)
1123    BlindAuthFailed,
1124    /// Maximum BAT mint amount exceeded (31003)
1125    BatMintMaxExceeded,
1126    /// BAT mint rate limit exceeded (31004)
1127    BatRateLimitExceeded,
1128
1129    /// Concurrent update detected
1130    ConcurrentUpdate,
1131
1132    /// Unknown error code
1133    Unknown(u16),
1134}
1135
1136impl ErrorCode {
1137    /// Error code from u16
1138    pub fn from_code(code: u16) -> Self {
1139        match code {
1140            // 10xxx - Proof/Token verification errors
1141            10001 => Self::TokenNotVerified,
1142            // 11xxx - Input/Output errors
1143            11001 => Self::TokenAlreadySpent,
1144            11002 => Self::TokenPending,
1145            11003 => Self::BlindedMessageAlreadySigned,
1146            11004 => Self::OutputsPending,
1147            11005 => Self::TransactionUnbalanced,
1148            11006 => Self::AmountOutofLimitRange,
1149            11007 => Self::DuplicateInputs,
1150            11008 => Self::DuplicateOutputs,
1151            11009 => Self::MultipleUnits,
1152            11010 => Self::UnitMismatch,
1153            11011 => Self::AmountlessInvoiceNotSupported,
1154            11012 => Self::IncorrectQuoteAmount,
1155            11013 => Self::UnsupportedUnit,
1156            11014 => Self::MaxInputsExceeded,
1157            11015 => Self::MaxOutputsExceeded,
1158            // 12xxx - Keyset errors
1159            12001 => Self::KeysetNotFound,
1160            12002 => Self::KeysetInactive,
1161            // 20xxx - Quote/Payment errors
1162            20001 => Self::QuoteNotPaid,
1163            20002 => Self::TokensAlreadyIssued,
1164            20003 => Self::MintingDisabled,
1165            20004 => Self::LightningError,
1166            20005 => Self::QuotePending,
1167            20006 => Self::InvoiceAlreadyPaid,
1168            20007 => Self::QuoteExpired,
1169            20008 => Self::WitnessMissingOrInvalid,
1170            20009 => Self::PubkeyRequired,
1171            // 30xxx - Clear auth errors
1172            30001 => Self::ClearAuthRequired,
1173            30002 => Self::ClearAuthFailed,
1174            // 31xxx - Blind auth errors
1175            31001 => Self::BlindAuthRequired,
1176            31002 => Self::BlindAuthFailed,
1177            31003 => Self::BatMintMaxExceeded,
1178            31004 => Self::BatRateLimitExceeded,
1179            _ => Self::Unknown(code),
1180        }
1181    }
1182
1183    /// Error code to u16
1184    pub fn to_code(&self) -> u16 {
1185        match self {
1186            // 10xxx - Proof/Token verification errors
1187            Self::TokenNotVerified => 10001,
1188            // 11xxx - Input/Output errors
1189            Self::TokenAlreadySpent => 11001,
1190            Self::TokenPending => 11002,
1191            Self::BlindedMessageAlreadySigned => 11003,
1192            Self::OutputsPending => 11004,
1193            Self::TransactionUnbalanced => 11005,
1194            Self::AmountOutofLimitRange => 11006,
1195            Self::DuplicateInputs => 11007,
1196            Self::DuplicateOutputs => 11008,
1197            Self::MultipleUnits => 11009,
1198            Self::UnitMismatch => 11010,
1199            Self::AmountlessInvoiceNotSupported => 11011,
1200            Self::IncorrectQuoteAmount => 11012,
1201            Self::UnsupportedUnit => 11013,
1202            Self::MaxInputsExceeded => 11014,
1203            Self::MaxOutputsExceeded => 11015,
1204            // 12xxx - Keyset errors
1205            Self::KeysetNotFound => 12001,
1206            Self::KeysetInactive => 12002,
1207            // 20xxx - Quote/Payment errors
1208            Self::QuoteNotPaid => 20001,
1209            Self::TokensAlreadyIssued => 20002,
1210            Self::MintingDisabled => 20003,
1211            Self::LightningError => 20004,
1212            Self::QuotePending => 20005,
1213            Self::InvoiceAlreadyPaid => 20006,
1214            Self::QuoteExpired => 20007,
1215            Self::WitnessMissingOrInvalid => 20008,
1216            Self::PubkeyRequired => 20009,
1217            // 30xxx - Clear auth errors
1218            Self::ClearAuthRequired => 30001,
1219            Self::ClearAuthFailed => 30002,
1220            // 31xxx - Blind auth errors
1221            Self::BlindAuthRequired => 31001,
1222            Self::BlindAuthFailed => 31002,
1223            Self::BatMintMaxExceeded => 31003,
1224            Self::BatRateLimitExceeded => 31004,
1225            Self::ConcurrentUpdate => 50000,
1226            Self::Unknown(code) => *code,
1227        }
1228    }
1229}
1230
1231impl Serialize for ErrorCode {
1232    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1233    where
1234        S: Serializer,
1235    {
1236        serializer.serialize_u16(self.to_code())
1237    }
1238}
1239
1240impl<'de> Deserialize<'de> for ErrorCode {
1241    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1242    where
1243        D: Deserializer<'de>,
1244    {
1245        let code = u16::deserialize(deserializer)?;
1246
1247        Ok(ErrorCode::from_code(code))
1248    }
1249}
1250
1251impl fmt::Display for ErrorCode {
1252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1253        write!(f, "{}", self.to_code())
1254    }
1255}