use std::fmt;
use cashu::{CurrencyUnit, PaymentMethod};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use thiserror::Error;
use crate::nuts::Id;
use crate::util::hex;
#[cfg(feature = "wallet")]
use crate::wallet::WalletKey;
use crate::Amount;
#[derive(Debug, Error)]
pub enum Error {
#[error("No Key for Amount")]
AmountKey,
#[error("Keyset id not known: `{0}`")]
KeysetUnknown(Id),
#[error("Unit unsupported")]
UnsupportedUnit,
#[error("Payment failed")]
PaymentFailed,
#[error("Payment pending")]
PaymentPending,
#[error("Request already paid")]
RequestAlreadyPaid,
#[error("Invalid payment request")]
InvalidPaymentRequest,
#[error("Invoice Amount undefined")]
InvoiceAmountUndefined,
#[error("Split Values must be less then or equal to amount")]
SplitValuesGreater,
#[error("Amount Overflow")]
AmountOverflow,
#[error("Signature missing or invalid")]
SignatureMissingOrInvalid,
#[error("Amount Less Invoice is not allowed")]
AmountLessNotAllowed,
#[error("Multi-Part Internal Melt Quotes are not supported")]
InternalMultiPartMeltQuote,
#[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
#[error("Minting is disabled")]
MintingDisabled,
#[error("Unknown quote")]
UnknownQuote,
#[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
ExpiredQuote(u64, u64),
#[error("Amount must be between `{0}` and `{1}` is `{2}`")]
AmountOutofLimitRange(Amount, Amount, Amount),
#[error("Quote not paid")]
UnpaidQuote,
#[error("Quote pending")]
PendingQuote,
#[error("Quote already issued")]
IssuedQuote,
#[error("Quote is already paid")]
PaidQuote,
#[error("Payment state is unknown")]
UnknownPaymentState,
#[error("Minting is disabled")]
MeltingDisabled,
#[error("Unknown Keyset")]
UnknownKeySet,
#[error("Blinded Message is already signed")]
BlindedMessageAlreadySigned,
#[error("Inactive Keyset")]
InactiveKeyset,
#[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
TransactionUnbalanced(u64, u64, u64),
#[error("Duplicate proofs")]
DuplicateProofs,
#[error("Cannot have multiple units")]
MultipleUnits,
#[error("Sig all cannot be used in melt")]
SigAllUsedInMelt,
#[error("Token Already Spent")]
TokenAlreadySpent,
#[error("Token Pending")]
TokenPending,
#[error("Internal Error")]
Internal,
#[error("P2PK condition not met `{0}`")]
P2PKConditionsNotMet(String),
#[error("Spending condition locktime not provided")]
LocktimeNotProvided,
#[error("Invalid spending conditions: `{0}`")]
InvalidSpendConditions(String),
#[error("Incorrect wallet: `{0}`")]
IncorrectWallet(String),
#[error("Unknown wallet: `{0}`")]
#[cfg(feature = "wallet")]
UnknownWallet(WalletKey),
#[error("Max fee exceeded")]
MaxFeeExceeded,
#[error("Url path segments could not be joined")]
UrlPathSegments,
#[error("Unknown error response: `{0}`")]
UnknownErrorResponse(String),
#[error("Could not verify DLEQ proof")]
CouldNotVerifyDleq,
#[error("Token does not match wallet mint")]
IncorrectMint,
#[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
MultiMintTokenNotSupported,
#[error("Preimage not provided")]
PreimageNotProvided,
#[error("Insufficient funds")]
InsufficientFunds,
#[error("No active keyset")]
NoActiveKeyset,
#[error("Incorrect quote amount")]
IncorrectQuoteAmount,
#[error("Invoice Description not supported")]
InvoiceDescriptionUnsupported,
#[error("`{0}`")]
Custom(String),
#[error(transparent)]
Invoice(#[from] lightning_invoice::ParseOrSemanticError),
#[error(transparent)]
Bip32(#[from] bitcoin::bip32::Error),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[error(transparent)]
UrlParseError(#[from] url::ParseError),
#[error(transparent)]
Utf8ParseError(#[from] std::string::FromUtf8Error),
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
#[error(transparent)]
Base64Error(#[from] bitcoin::base64::DecodeError),
#[error(transparent)]
HexError(#[from] hex::Error),
#[error("Http transport error: {0}")]
HttpError(String),
#[error(transparent)]
CashuUrl(#[from] crate::mint_url::Error),
#[error(transparent)]
Secret(#[from] crate::secret::Error),
#[error(transparent)]
AmountError(#[from] crate::amount::Error),
#[error(transparent)]
DHKE(#[from] crate::dhke::Error),
#[error(transparent)]
NUT00(#[from] crate::nuts::nut00::Error),
#[error(transparent)]
NUT01(#[from] crate::nuts::nut01::Error),
#[error(transparent)]
NUT02(#[from] crate::nuts::nut02::Error),
#[error(transparent)]
NUT03(#[from] crate::nuts::nut03::Error),
#[error(transparent)]
NUT04(#[from] crate::nuts::nut04::Error),
#[error(transparent)]
NUT05(#[from] crate::nuts::nut05::Error),
#[error(transparent)]
NUT11(#[from] crate::nuts::nut11::Error),
#[error(transparent)]
NUT12(#[from] crate::nuts::nut12::Error),
#[error(transparent)]
#[cfg(feature = "wallet")]
NUT13(#[from] crate::nuts::nut13::Error),
#[error(transparent)]
NUT14(#[from] crate::nuts::nut14::Error),
#[error(transparent)]
NUT18(#[from] crate::nuts::nut18::Error),
#[error(transparent)]
NUT20(#[from] crate::nuts::nut20::Error),
#[error(transparent)]
Database(#[from] crate::database::Error),
#[error(transparent)]
#[cfg(feature = "mint")]
Lightning(#[from] crate::lightning::Error),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct ErrorResponse {
pub code: ErrorCode,
pub error: Option<String>,
pub detail: Option<String>,
}
impl fmt::Display for ErrorResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"code: {}, error: {}, detail: {}",
self.code,
self.error.clone().unwrap_or_default(),
self.detail.clone().unwrap_or_default()
)
}
}
impl ErrorResponse {
pub fn new(code: ErrorCode, error: Option<String>, detail: Option<String>) -> Self {
Self {
code,
error,
detail,
}
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
let value: Value = serde_json::from_str(json)?;
Self::from_value(value)
}
pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
match serde_json::from_value::<ErrorResponse>(value.clone()) {
Ok(res) => Ok(res),
Err(_) => Ok(Self {
code: ErrorCode::Unknown(999),
error: Some(value.to_string()),
detail: None,
}),
}
}
}
impl From<Error> for ErrorResponse {
fn from(err: Error) -> ErrorResponse {
match err {
Error::TokenAlreadySpent => ErrorResponse {
code: ErrorCode::TokenAlreadySpent,
error: Some(err.to_string()),
detail: None,
},
Error::UnsupportedUnit => ErrorResponse {
code: ErrorCode::UnsupportedUnit,
error: Some(err.to_string()),
detail: None,
},
Error::PaymentFailed => ErrorResponse {
code: ErrorCode::LightningError,
error: Some(err.to_string()),
detail: None,
},
Error::RequestAlreadyPaid => ErrorResponse {
code: ErrorCode::InvoiceAlreadyPaid,
error: Some("Invoice already paid.".to_string()),
detail: None,
},
Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
ErrorResponse {
code: ErrorCode::TransactionUnbalanced,
error: Some(format!(
"Inputs: {}, Outputs: {}, expected_fee: {}",
inputs_total, outputs_total, fee_expected,
)),
detail: Some("Transaction inputs should equal outputs less fee".to_string()),
}
}
Error::MintingDisabled => ErrorResponse {
code: ErrorCode::MintingDisabled,
error: Some(err.to_string()),
detail: None,
},
Error::BlindedMessageAlreadySigned => ErrorResponse {
code: ErrorCode::BlindedMessageAlreadySigned,
error: Some(err.to_string()),
detail: None,
},
Error::InsufficientFunds => ErrorResponse {
code: ErrorCode::TransactionUnbalanced,
error: Some(err.to_string()),
detail: None,
},
Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
code: ErrorCode::AmountOutofLimitRange,
error: Some(err.to_string()),
detail: None,
},
Error::ExpiredQuote(_, _) => ErrorResponse {
code: ErrorCode::QuoteExpired,
error: Some(err.to_string()),
detail: None,
},
Error::PendingQuote => ErrorResponse {
code: ErrorCode::QuotePending,
error: Some(err.to_string()),
detail: None,
},
Error::TokenPending => ErrorResponse {
code: ErrorCode::TokenPending,
error: Some(err.to_string()),
detail: None,
},
Error::NUT20(err) => ErrorResponse {
code: ErrorCode::WitnessMissingOrInvalid,
error: Some(err.to_string()),
detail: None,
},
_ => ErrorResponse {
code: ErrorCode::Unknown(9999),
error: Some(err.to_string()),
detail: None,
},
}
}
}
impl From<ErrorResponse> for Error {
fn from(err: ErrorResponse) -> Error {
match err.code {
ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
ErrorCode::QuotePending => Self::PendingQuote,
ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
ErrorCode::KeysetNotFound => Self::UnknownKeySet,
ErrorCode::KeysetInactive => Self::InactiveKeyset,
ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
ErrorCode::MintingDisabled => Self::MintingDisabled,
ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
ErrorCode::LightningError => Self::PaymentFailed,
ErrorCode::AmountOutofLimitRange => {
Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
}
ErrorCode::TokenPending => Self::TokenPending,
ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
_ => Self::UnknownErrorResponse(err.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub enum ErrorCode {
TokenAlreadySpent,
TokenPending,
QuoteNotPaid,
QuoteExpired,
QuotePending,
KeysetNotFound,
KeysetInactive,
BlindedMessageAlreadySigned,
UnsupportedUnit,
TokensAlreadyIssued,
MintingDisabled,
InvoiceAlreadyPaid,
TokenNotVerified,
LightningError,
TransactionUnbalanced,
AmountOutofLimitRange,
WitnessMissingOrInvalid,
Unknown(u16),
}
impl ErrorCode {
pub fn from_code(code: u16) -> Self {
match code {
10002 => Self::BlindedMessageAlreadySigned,
10003 => Self::TokenNotVerified,
11001 => Self::TokenAlreadySpent,
11002 => Self::TransactionUnbalanced,
11005 => Self::UnsupportedUnit,
11006 => Self::AmountOutofLimitRange,
11007 => Self::TokenPending,
12001 => Self::KeysetNotFound,
12002 => Self::KeysetInactive,
20000 => Self::LightningError,
20001 => Self::QuoteNotPaid,
20002 => Self::TokensAlreadyIssued,
20003 => Self::MintingDisabled,
20005 => Self::QuotePending,
20006 => Self::InvoiceAlreadyPaid,
20007 => Self::QuoteExpired,
20008 => Self::WitnessMissingOrInvalid,
_ => Self::Unknown(code),
}
}
pub fn to_code(&self) -> u16 {
match self {
Self::BlindedMessageAlreadySigned => 10002,
Self::TokenNotVerified => 10003,
Self::TokenAlreadySpent => 11001,
Self::TransactionUnbalanced => 11002,
Self::UnsupportedUnit => 11005,
Self::AmountOutofLimitRange => 11006,
Self::TokenPending => 11007,
Self::KeysetNotFound => 12001,
Self::KeysetInactive => 12002,
Self::LightningError => 20000,
Self::QuoteNotPaid => 20001,
Self::TokensAlreadyIssued => 20002,
Self::MintingDisabled => 20003,
Self::QuotePending => 20005,
Self::InvoiceAlreadyPaid => 20006,
Self::QuoteExpired => 20007,
Self::WitnessMissingOrInvalid => 20008,
Self::Unknown(code) => *code,
}
}
}
impl Serialize for ErrorCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u16(self.to_code())
}
}
impl<'de> Deserialize<'de> for ErrorCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let code = u16::deserialize(deserializer)?;
Ok(ErrorCode::from_code(code))
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_code())
}
}