1use std::fmt;
6use std::str::FromStr;
7
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11#[cfg(feature = "mint")]
12use uuid::Uuid;
13
14use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
15use super::{MintQuoteState, PublicKey};
16use crate::Amount;
17
18#[derive(Debug, Error)]
20pub enum Error {
21 #[error("Unknown Quote State")]
23 UnknownState,
24 #[error("Amount overflow")]
26 AmountOverflow,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
32pub struct MintQuoteBolt11Request {
33 pub amount: Amount,
35 pub unit: CurrencyUnit,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub description: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub pubkey: Option<PublicKey>,
43}
44
45#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
47#[serde(rename_all = "UPPERCASE")]
48#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MintQuoteState))]
49pub enum QuoteState {
50 #[default]
52 Unpaid,
53 Paid,
55 Pending,
59 Issued,
61}
62
63impl fmt::Display for QuoteState {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 match self {
66 Self::Unpaid => write!(f, "UNPAID"),
67 Self::Paid => write!(f, "PAID"),
68 Self::Pending => write!(f, "PENDING"),
69 Self::Issued => write!(f, "ISSUED"),
70 }
71 }
72}
73
74impl FromStr for QuoteState {
75 type Err = Error;
76
77 fn from_str(state: &str) -> Result<Self, Self::Err> {
78 match state {
79 "PENDING" => Ok(Self::Pending),
80 "PAID" => Ok(Self::Paid),
81 "UNPAID" => Ok(Self::Unpaid),
82 "ISSUED" => Ok(Self::Issued),
83 _ => Err(Error::UnknownState),
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
91#[serde(bound = "Q: Serialize + DeserializeOwned")]
92pub struct MintQuoteBolt11Response<Q> {
93 pub quote: Q,
95 pub request: String,
97 pub amount: Option<Amount>,
100 pub unit: Option<CurrencyUnit>,
103 pub state: MintQuoteState,
105 pub expiry: Option<u64>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub pubkey: Option<PublicKey>,
110}
111
112impl<Q: ToString> MintQuoteBolt11Response<Q> {
113 pub fn to_string_id(&self) -> MintQuoteBolt11Response<String> {
115 MintQuoteBolt11Response {
116 quote: self.quote.to_string(),
117 request: self.request.clone(),
118 state: self.state,
119 expiry: self.expiry,
120 pubkey: self.pubkey,
121 amount: self.amount,
122 unit: self.unit.clone(),
123 }
124 }
125}
126
127#[cfg(feature = "mint")]
128impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
129 fn from(value: MintQuoteBolt11Response<Uuid>) -> Self {
130 Self {
131 quote: value.quote.to_string(),
132 request: value.request,
133 state: value.state,
134 expiry: value.expiry,
135 pubkey: value.pubkey,
136 amount: value.amount,
137 unit: value.unit.clone(),
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
145#[serde(bound = "Q: Serialize + DeserializeOwned")]
146pub struct MintBolt11Request<Q> {
147 #[cfg_attr(feature = "swagger", schema(max_length = 1_000))]
149 pub quote: Q,
150 #[cfg_attr(feature = "swagger", schema(max_items = 1_000))]
152 pub outputs: Vec<BlindedMessage>,
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub signature: Option<String>,
156}
157
158#[cfg(feature = "mint")]
159impl TryFrom<MintBolt11Request<String>> for MintBolt11Request<Uuid> {
160 type Error = uuid::Error;
161
162 fn try_from(value: MintBolt11Request<String>) -> Result<Self, Self::Error> {
163 Ok(Self {
164 quote: Uuid::from_str(&value.quote)?,
165 outputs: value.outputs,
166 signature: value.signature,
167 })
168 }
169}
170
171impl<Q> MintBolt11Request<Q> {
172 pub fn total_amount(&self) -> Result<Amount, Error> {
174 Amount::try_sum(
175 self.outputs
176 .iter()
177 .map(|BlindedMessage { amount, .. }| *amount),
178 )
179 .map_err(|_| Error::AmountOverflow)
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
186pub struct MintBolt11Response {
187 pub signatures: Vec<BlindSignature>,
189}
190
191#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
193#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
194pub struct MintMethodSettings {
195 pub method: PaymentMethod,
197 pub unit: CurrencyUnit,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub min_amount: Option<Amount>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub max_amount: Option<Amount>,
205 #[serde(default)]
207 pub description: bool,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
212#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut04::Settings))]
213pub struct Settings {
214 pub methods: Vec<MintMethodSettings>,
216 pub disabled: bool,
218}
219
220impl Settings {
221 pub fn new(methods: Vec<MintMethodSettings>, disabled: bool) -> Self {
223 Self { methods, disabled }
224 }
225
226 pub fn get_settings(
228 &self,
229 unit: &CurrencyUnit,
230 method: &PaymentMethod,
231 ) -> Option<MintMethodSettings> {
232 for method_settings in self.methods.iter() {
233 if method_settings.method.eq(method) && method_settings.unit.eq(unit) {
234 return Some(method_settings.clone());
235 }
236 }
237
238 None
239 }
240
241 pub fn remove_settings(
243 &mut self,
244 unit: &CurrencyUnit,
245 method: &PaymentMethod,
246 ) -> Option<MintMethodSettings> {
247 self.methods
248 .iter()
249 .position(|settings| &settings.method == method && &settings.unit == unit)
250 .map(|index| self.methods.remove(index))
251 }
252}