use std::fmt;
use std::str::FromStr;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "mint")]
use uuid::Uuid;
use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
use super::{MintQuoteState, PublicKey};
use crate::Amount;
#[derive(Debug, Error)]
pub enum Error {
#[error("Unknown Quote State")]
UnknownState,
#[error("Amount overflow")]
AmountOverflow,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct MintQuoteBolt11Request {
pub amount: Amount,
pub unit: CurrencyUnit,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pubkey: Option<PublicKey>,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MintQuoteState))]
pub enum QuoteState {
#[default]
Unpaid,
Paid,
Pending,
Issued,
}
impl fmt::Display for QuoteState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Unpaid => write!(f, "UNPAID"),
Self::Paid => write!(f, "PAID"),
Self::Pending => write!(f, "PENDING"),
Self::Issued => write!(f, "ISSUED"),
}
}
}
impl FromStr for QuoteState {
type Err = Error;
fn from_str(state: &str) -> Result<Self, Self::Err> {
match state {
"PENDING" => Ok(Self::Pending),
"PAID" => Ok(Self::Paid),
"UNPAID" => Ok(Self::Unpaid),
"ISSUED" => Ok(Self::Issued),
_ => Err(Error::UnknownState),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
#[serde(bound = "Q: Serialize + DeserializeOwned")]
pub struct MintQuoteBolt11Response<Q> {
pub quote: Q,
pub request: String,
pub state: MintQuoteState,
pub expiry: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pubkey: Option<PublicKey>,
}
impl<Q: ToString> MintQuoteBolt11Response<Q> {
pub fn to_string_id(&self) -> MintQuoteBolt11Response<String> {
MintQuoteBolt11Response {
quote: self.quote.to_string(),
request: self.request.clone(),
state: self.state,
expiry: self.expiry,
pubkey: self.pubkey,
}
}
}
#[cfg(feature = "mint")]
impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
fn from(value: MintQuoteBolt11Response<Uuid>) -> Self {
Self {
quote: value.quote.to_string(),
request: value.request,
state: value.state,
expiry: value.expiry,
pubkey: value.pubkey,
}
}
}
#[cfg(feature = "mint")]
impl From<crate::mint::MintQuote> for MintQuoteBolt11Response<Uuid> {
fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<Uuid> {
MintQuoteBolt11Response {
quote: mint_quote.id,
request: mint_quote.request,
state: mint_quote.state,
expiry: Some(mint_quote.expiry),
pubkey: mint_quote.pubkey,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
#[serde(bound = "Q: Serialize + DeserializeOwned")]
pub struct MintBolt11Request<Q> {
#[cfg_attr(feature = "swagger", schema(max_length = 1_000))]
pub quote: Q,
#[cfg_attr(feature = "swagger", schema(max_items = 1_000))]
pub outputs: Vec<BlindedMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
}
#[cfg(feature = "mint")]
impl TryFrom<MintBolt11Request<String>> for MintBolt11Request<Uuid> {
type Error = uuid::Error;
fn try_from(value: MintBolt11Request<String>) -> Result<Self, Self::Error> {
Ok(Self {
quote: Uuid::from_str(&value.quote)?,
outputs: value.outputs,
signature: value.signature,
})
}
}
impl<Q> MintBolt11Request<Q> {
pub fn total_amount(&self) -> Result<Amount, Error> {
Amount::try_sum(
self.outputs
.iter()
.map(|BlindedMessage { amount, .. }| *amount),
)
.map_err(|_| Error::AmountOverflow)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct MintBolt11Response {
pub signatures: Vec<BlindSignature>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct MintMethodSettings {
pub method: PaymentMethod,
pub unit: CurrencyUnit,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_amount: Option<Amount>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_amount: Option<Amount>,
#[serde(default)]
pub description: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut04::Settings))]
pub struct Settings {
pub methods: Vec<MintMethodSettings>,
pub disabled: bool,
}
impl Settings {
pub fn new(methods: Vec<MintMethodSettings>, disabled: bool) -> Self {
Self { methods, disabled }
}
pub fn get_settings(
&self,
unit: &CurrencyUnit,
method: &PaymentMethod,
) -> Option<MintMethodSettings> {
for method_settings in self.methods.iter() {
if method_settings.method.eq(method) && method_settings.unit.eq(unit) {
return Some(method_settings.clone());
}
}
None
}
pub fn remove_settings(
&mut self,
unit: &CurrencyUnit,
method: &PaymentMethod,
) -> Option<MintMethodSettings> {
self.methods
.iter()
.position(|settings| &settings.method == method && &settings.unit == unit)
.map(|index| self.methods.remove(index))
}
}