cdk_common/
error.rs

1//! Errors
2
3use 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/// CDK Error
17#[derive(Debug, Error)]
18pub enum Error {
19    /// Mint does not have a key for amount
20    #[error("No Key for Amount")]
21    AmountKey,
22    /// Keyset is not known
23    #[error("Keyset id not known: `{0}`")]
24    KeysetUnknown(Id),
25    /// Unsupported unit
26    #[error("Unit unsupported")]
27    UnsupportedUnit,
28    /// Payment failed
29    #[error("Payment failed")]
30    PaymentFailed,
31    /// Payment pending
32    #[error("Payment pending")]
33    PaymentPending,
34    /// Invoice already paid
35    #[error("Request already paid")]
36    RequestAlreadyPaid,
37    /// Invalid payment request
38    #[error("Invalid payment request")]
39    InvalidPaymentRequest,
40    /// Bolt11 invoice does not have amount
41    #[error("Invoice Amount undefined")]
42    InvoiceAmountUndefined,
43    /// Split Values must be less then or equal to amount
44    #[error("Split Values must be less then or equal to amount")]
45    SplitValuesGreater,
46    /// Amount overflow
47    #[error("Amount Overflow")]
48    AmountOverflow,
49    /// Witness missing or invalid
50    #[error("Signature missing or invalid")]
51    SignatureMissingOrInvalid,
52    /// Amountless Invoice Not supported
53    #[error("Amount Less Invoice is not allowed")]
54    AmountLessNotAllowed,
55    /// Multi-Part Internal Melt Quotes are not supported
56    #[error("Multi-Part Internal Melt Quotes are not supported")]
57    InternalMultiPartMeltQuote,
58    /// Multi-Part Payment not supported for unit and method
59    #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
60    MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
61
62    // Mint Errors
63    /// Minting is disabled
64    #[error("Minting is disabled")]
65    MintingDisabled,
66    /// Quote is not known
67    #[error("Unknown quote")]
68    UnknownQuote,
69    /// Quote is expired
70    #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
71    ExpiredQuote(u64, u64),
72    /// Amount is outside of allowed range
73    #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
74    AmountOutofLimitRange(Amount, Amount, Amount),
75    /// Quote is not paiud
76    #[error("Quote not paid")]
77    UnpaidQuote,
78    /// Quote is pending
79    #[error("Quote pending")]
80    PendingQuote,
81    /// ecash already issued for quote
82    #[error("Quote already issued")]
83    IssuedQuote,
84    /// Quote has already been paid
85    #[error("Quote is already paid")]
86    PaidQuote,
87    /// Payment state is unknown
88    #[error("Payment state is unknown")]
89    UnknownPaymentState,
90    /// Melting is disabled
91    #[error("Minting is disabled")]
92    MeltingDisabled,
93    /// Unknown Keyset
94    #[error("Unknown Keyset")]
95    UnknownKeySet,
96    /// BlindedMessage is already signed
97    #[error("Blinded Message is already signed")]
98    BlindedMessageAlreadySigned,
99    /// Inactive Keyset
100    #[error("Inactive Keyset")]
101    InactiveKeyset,
102    /// Transaction unbalanced
103    #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
104    TransactionUnbalanced(u64, u64, u64),
105    /// Duplicate proofs provided
106    #[error("Duplicate Inputs")]
107    DuplicateInputs,
108    /// Duplicate output
109    #[error("Duplicate outputs")]
110    DuplicateOutputs,
111    /// Multiple units provided
112    #[error("Cannot have multiple units")]
113    MultipleUnits,
114    /// Unit mismatch
115    #[error("Input unit must match output")]
116    UnitMismatch,
117    /// Sig all cannot be used in melt
118    #[error("Sig all cannot be used in melt")]
119    SigAllUsedInMelt,
120    /// Token is already spent
121    #[error("Token Already Spent")]
122    TokenAlreadySpent,
123    /// Token is already pending
124    #[error("Token Pending")]
125    TokenPending,
126    /// Internal Error
127    #[error("Internal Error")]
128    Internal,
129
130    // Wallet Errors
131    /// P2PK spending conditions not met
132    #[error("P2PK condition not met `{0}`")]
133    P2PKConditionsNotMet(String),
134    /// Spending Locktime not provided
135    #[error("Spending condition locktime not provided")]
136    LocktimeNotProvided,
137    /// Invalid Spending Conditions
138    #[error("Invalid spending conditions: `{0}`")]
139    InvalidSpendConditions(String),
140    /// Incorrect Wallet
141    #[error("Incorrect wallet: `{0}`")]
142    IncorrectWallet(String),
143    /// Unknown Wallet
144    #[error("Unknown wallet: `{0}`")]
145    #[cfg(feature = "wallet")]
146    UnknownWallet(WalletKey),
147    /// Max Fee Ecxeded
148    #[error("Max fee exceeded")]
149    MaxFeeExceeded,
150    /// Url path segments could not be joined
151    #[error("Url path segments could not be joined")]
152    UrlPathSegments,
153    ///  Unknown error response
154    #[error("Unknown error response: `{0}`")]
155    UnknownErrorResponse(String),
156    /// Invalid DLEQ proof
157    #[error("Could not verify DLEQ proof")]
158    CouldNotVerifyDleq,
159    /// Incorrect Mint
160    /// Token does not match wallet mint
161    #[error("Token does not match wallet mint")]
162    IncorrectMint,
163    /// Receive can only be used with tokens from single mint
164    #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
165    MultiMintTokenNotSupported,
166    /// Preimage not provided
167    #[error("Preimage not provided")]
168    PreimageNotProvided,
169    /// Insufficient Funds
170    #[error("Insufficient funds")]
171    InsufficientFunds,
172    /// No active keyset
173    #[error("No active keyset")]
174    NoActiveKeyset,
175    /// Incorrect quote amount
176    #[error("Incorrect quote amount")]
177    IncorrectQuoteAmount,
178    /// Invoice Description not supported
179    #[error("Invoice Description not supported")]
180    InvoiceDescriptionUnsupported,
181    /// Custom Error
182    #[error("`{0}`")]
183    Custom(String),
184
185    // External Error conversions
186    /// Parse invoice error
187    #[error(transparent)]
188    Invoice(#[from] lightning_invoice::ParseOrSemanticError),
189    /// Bip32 error
190    #[error(transparent)]
191    Bip32(#[from] bitcoin::bip32::Error),
192    /// Parse int error
193    #[error(transparent)]
194    ParseInt(#[from] std::num::ParseIntError),
195    /// Parse 9rl Error
196    #[error(transparent)]
197    UrlParseError(#[from] url::ParseError),
198    /// Utf8 parse error
199    #[error(transparent)]
200    Utf8ParseError(#[from] std::string::FromUtf8Error),
201    /// Serde Json error
202    #[error(transparent)]
203    SerdeJsonError(#[from] serde_json::Error),
204    /// Base64 error
205    #[error(transparent)]
206    Base64Error(#[from] bitcoin::base64::DecodeError),
207    /// From hex error
208    #[error(transparent)]
209    HexError(#[from] hex::Error),
210    /// Http transport error
211    #[error("Http transport error: {0}")]
212    HttpError(String),
213
214    // Crate error conversions
215    /// Cashu Url Error
216    #[error(transparent)]
217    CashuUrl(#[from] crate::mint_url::Error),
218    /// Secret error
219    #[error(transparent)]
220    Secret(#[from] crate::secret::Error),
221    /// Amount Error
222    #[error(transparent)]
223    AmountError(#[from] crate::amount::Error),
224    /// DHKE Error
225    #[error(transparent)]
226    DHKE(#[from] crate::dhke::Error),
227    /// NUT00 Error
228    #[error(transparent)]
229    NUT00(#[from] crate::nuts::nut00::Error),
230    /// Nut01 error
231    #[error(transparent)]
232    NUT01(#[from] crate::nuts::nut01::Error),
233    /// NUT02 error
234    #[error(transparent)]
235    NUT02(#[from] crate::nuts::nut02::Error),
236    /// NUT03 error
237    #[error(transparent)]
238    NUT03(#[from] crate::nuts::nut03::Error),
239    /// NUT04 error
240    #[error(transparent)]
241    NUT04(#[from] crate::nuts::nut04::Error),
242    /// NUT05 error
243    #[error(transparent)]
244    NUT05(#[from] crate::nuts::nut05::Error),
245    /// NUT11 Error
246    #[error(transparent)]
247    NUT11(#[from] crate::nuts::nut11::Error),
248    /// NUT12 Error
249    #[error(transparent)]
250    NUT12(#[from] crate::nuts::nut12::Error),
251    /// NUT13 Error
252    #[error(transparent)]
253    #[cfg(feature = "wallet")]
254    NUT13(#[from] crate::nuts::nut13::Error),
255    /// NUT14 Error
256    #[error(transparent)]
257    NUT14(#[from] crate::nuts::nut14::Error),
258    /// NUT18 Error
259    #[error(transparent)]
260    NUT18(#[from] crate::nuts::nut18::Error),
261    /// NUT20 Error
262    #[error(transparent)]
263    NUT20(#[from] crate::nuts::nut20::Error),
264    /// Database Error
265    #[error(transparent)]
266    Database(#[from] crate::database::Error),
267    /// Lightning Error
268    #[error(transparent)]
269    #[cfg(feature = "mint")]
270    Lightning(#[from] crate::lightning::Error),
271}
272
273/// CDK Error Response
274///
275/// See NUT definition in [00](https://github.com/cashubtc/nuts/blob/main/00.md)
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
278pub struct ErrorResponse {
279    /// Error Code
280    pub code: ErrorCode,
281    /// Human readable Text
282    pub error: Option<String>,
283    /// Longer human readable description
284    pub detail: Option<String>,
285}
286
287impl fmt::Display for ErrorResponse {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        write!(
290            f,
291            "code: {}, error: {}, detail: {}",
292            self.code,
293            self.error.clone().unwrap_or_default(),
294            self.detail.clone().unwrap_or_default()
295        )
296    }
297}
298
299impl ErrorResponse {
300    /// Create new [`ErrorResponse`]
301    pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
302        Self {
303            code,
304            error,
305            detail,
306        }
307    }
308
309    /// Error response from json
310    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
311        let value: Value = serde_json::from_str(json)?;
312
313        Self::from_value(value)
314    }
315
316    /// Error response from json Value
317    pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
318        match serde_json::from_value::<ErrorResponse>(value.clone()) {
319            Ok(res) => Ok(res),
320            Err(_) => Ok(Self {
321                code: ErrorCode::Unknown(999),
322                error: Some(value.to_string()),
323                detail: None,
324            }),
325        }
326    }
327}
328
329impl From<Error> for ErrorResponse {
330    fn from(err: Error) -> ErrorResponse {
331        match err {
332            Error::TokenAlreadySpent => ErrorResponse {
333                code: ErrorCode::TokenAlreadySpent,
334                error: Some(err.to_string()),
335                detail: None,
336            },
337            Error::UnsupportedUnit => ErrorResponse {
338                code: ErrorCode::UnsupportedUnit,
339                error: Some(err.to_string()),
340                detail: None,
341            },
342            Error::PaymentFailed => ErrorResponse {
343                code: ErrorCode::LightningError,
344                error: Some(err.to_string()),
345                detail: None,
346            },
347            Error::RequestAlreadyPaid => ErrorResponse {
348                code: ErrorCode::InvoiceAlreadyPaid,
349                error: Some("Invoice already paid.".to_string()),
350                detail: None,
351            },
352            Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
353                ErrorResponse {
354                    code: ErrorCode::TransactionUnbalanced,
355                    error: Some(format!(
356                        "Inputs: {}, Outputs: {}, expected_fee: {}",
357                        inputs_total, outputs_total, fee_expected,
358                    )),
359                    detail: Some("Transaction inputs should equal outputs less fee".to_string()),
360                }
361            }
362            Error::MintingDisabled => ErrorResponse {
363                code: ErrorCode::MintingDisabled,
364                error: Some(err.to_string()),
365                detail: None,
366            },
367            Error::BlindedMessageAlreadySigned => ErrorResponse {
368                code: ErrorCode::BlindedMessageAlreadySigned,
369                error: Some(err.to_string()),
370                detail: None,
371            },
372            Error::InsufficientFunds => ErrorResponse {
373                code: ErrorCode::TransactionUnbalanced,
374                error: Some(err.to_string()),
375                detail: None,
376            },
377            Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
378                code: ErrorCode::AmountOutofLimitRange,
379                error: Some(err.to_string()),
380                detail: None,
381            },
382            Error::ExpiredQuote(_, _) => ErrorResponse {
383                code: ErrorCode::QuoteExpired,
384                error: Some(err.to_string()),
385                detail: None,
386            },
387            Error::PendingQuote => ErrorResponse {
388                code: ErrorCode::QuotePending,
389                error: Some(err.to_string()),
390                detail: None,
391            },
392            Error::TokenPending => ErrorResponse {
393                code: ErrorCode::TokenPending,
394                error: Some(err.to_string()),
395                detail: None,
396            },
397            Error::NUT20(err) => ErrorResponse {
398                code: ErrorCode::WitnessMissingOrInvalid,
399                error: Some(err.to_string()),
400                detail: None,
401            },
402            Error::DuplicateInputs => ErrorResponse {
403                code: ErrorCode::DuplicateInputs,
404                error: Some(err.to_string()),
405                detail: None,
406            },
407            Error::DuplicateOutputs => ErrorResponse {
408                code: ErrorCode::DuplicateOutputs,
409                error: Some(err.to_string()),
410                detail: None,
411            },
412            Error::MultipleUnits => ErrorResponse {
413                code: ErrorCode::MultipleUnits,
414                error: Some(err.to_string()),
415                detail: None,
416            },
417            Error::UnitMismatch => ErrorResponse {
418                code: ErrorCode::UnitMismatch,
419                error: Some(err.to_string()),
420                detail: None,
421            },
422            _ => ErrorResponse {
423                code: ErrorCode::Unknown(9999),
424                error: Some(err.to_string()),
425                detail: None,
426            },
427        }
428    }
429}
430
431impl From<ErrorResponse> for Error {
432    fn from(err: ErrorResponse) -> Error {
433        match err.code {
434            ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
435            ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
436            ErrorCode::QuotePending => Self::PendingQuote,
437            ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
438            ErrorCode::KeysetNotFound => Self::UnknownKeySet,
439            ErrorCode::KeysetInactive => Self::InactiveKeyset,
440            ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
441            ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
442            ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
443            ErrorCode::MintingDisabled => Self::MintingDisabled,
444            ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
445            ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
446            ErrorCode::LightningError => Self::PaymentFailed,
447            ErrorCode::AmountOutofLimitRange => {
448                Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
449            }
450            ErrorCode::TokenPending => Self::TokenPending,
451            ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
452            ErrorCode::DuplicateInputs => Self::DuplicateInputs,
453            ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
454            ErrorCode::MultipleUnits => Self::MultipleUnits,
455            ErrorCode::UnitMismatch => Self::UnitMismatch,
456            _ => Self::UnknownErrorResponse(err.to_string()),
457        }
458    }
459}
460
461/// Possible Error Codes
462#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
463#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
464pub enum ErrorCode {
465    /// Token is already spent
466    TokenAlreadySpent,
467    /// Token Pending
468    TokenPending,
469    /// Quote is not paid
470    QuoteNotPaid,
471    /// Quote is not expired
472    QuoteExpired,
473    /// Quote Pending
474    QuotePending,
475    /// Keyset is not found
476    KeysetNotFound,
477    /// Keyset inactive
478    KeysetInactive,
479    /// Blinded Message Already signed
480    BlindedMessageAlreadySigned,
481    /// Unsupported unit
482    UnsupportedUnit,
483    /// Token already issed for quote
484    TokensAlreadyIssued,
485    /// Minting Disabled
486    MintingDisabled,
487    /// Invoice Already Paid
488    InvoiceAlreadyPaid,
489    /// Token Not Verified
490    TokenNotVerified,
491    /// Lightning Error
492    LightningError,
493    /// Unbalanced Error
494    TransactionUnbalanced,
495    /// Amount outside of allowed range
496    AmountOutofLimitRange,
497    /// Witness missing or invalid
498    WitnessMissingOrInvalid,
499    /// Duplicate Inputs
500    DuplicateInputs,
501    /// Duplicate Outputs
502    DuplicateOutputs,
503    /// Multiple Units
504    MultipleUnits,
505    /// Input unit does not match output
506    UnitMismatch,
507    /// Unknown error code
508    Unknown(u16),
509}
510
511impl ErrorCode {
512    /// Error code from u16
513    pub fn from_code(code: u16) -> Self {
514        match code {
515            10002 => Self::BlindedMessageAlreadySigned,
516            10003 => Self::TokenNotVerified,
517            11001 => Self::TokenAlreadySpent,
518            11002 => Self::TransactionUnbalanced,
519            11005 => Self::UnsupportedUnit,
520            11006 => Self::AmountOutofLimitRange,
521            11007 => Self::DuplicateInputs,
522            11008 => Self::DuplicateOutputs,
523            11009 => Self::MultipleUnits,
524            11010 => Self::UnitMismatch,
525            11012 => Self::TokenPending,
526            12001 => Self::KeysetNotFound,
527            12002 => Self::KeysetInactive,
528            20000 => Self::LightningError,
529            20001 => Self::QuoteNotPaid,
530            20002 => Self::TokensAlreadyIssued,
531            20003 => Self::MintingDisabled,
532            20005 => Self::QuotePending,
533            20006 => Self::InvoiceAlreadyPaid,
534            20007 => Self::QuoteExpired,
535            20008 => Self::WitnessMissingOrInvalid,
536            _ => Self::Unknown(code),
537        }
538    }
539
540    /// Error code to u16
541    pub fn to_code(&self) -> u16 {
542        match self {
543            Self::BlindedMessageAlreadySigned => 10002,
544            Self::TokenNotVerified => 10003,
545            Self::TokenAlreadySpent => 11001,
546            Self::TransactionUnbalanced => 11002,
547            Self::UnsupportedUnit => 11005,
548            Self::AmountOutofLimitRange => 11006,
549            Self::DuplicateInputs => 11007,
550            Self::DuplicateOutputs => 11008,
551            Self::MultipleUnits => 11009,
552            Self::UnitMismatch => 11010,
553            Self::TokenPending => 11012,
554            Self::KeysetNotFound => 12001,
555            Self::KeysetInactive => 12002,
556            Self::LightningError => 20000,
557            Self::QuoteNotPaid => 20001,
558            Self::TokensAlreadyIssued => 20002,
559            Self::MintingDisabled => 20003,
560            Self::QuotePending => 20005,
561            Self::InvoiceAlreadyPaid => 20006,
562            Self::QuoteExpired => 20007,
563            Self::WitnessMissingOrInvalid => 20008,
564            Self::Unknown(code) => *code,
565        }
566    }
567}
568
569impl Serialize for ErrorCode {
570    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
571    where
572        S: Serializer,
573    {
574        serializer.serialize_u16(self.to_code())
575    }
576}
577
578impl<'de> Deserialize<'de> for ErrorCode {
579    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
580    where
581        D: Deserializer<'de>,
582    {
583        let code = u16::deserialize(deserializer)?;
584
585        Ok(ErrorCode::from_code(code))
586    }
587}
588
589impl fmt::Display for ErrorCode {
590    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591        write!(f, "{}", self.to_code())
592    }
593}