1use std::fmt;
6use std::str::FromStr;
7
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Deserializer, Serialize};
10use serde_json::Value;
11use thiserror::Error;
12#[cfg(feature = "mint")]
13use uuid::Uuid;
14
15use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod, Proofs};
16use super::nut15::Mpp;
17use crate::nuts::MeltQuoteState;
18use crate::{Amount, Bolt11Invoice};
19
20#[derive(Debug, Error)]
22pub enum Error {
23 #[error("Unknown quote state")]
25 UnknownState,
26 #[error("Amount Overflow")]
28 AmountOverflow,
29 #[error("Invalid Request")]
31 InvalidAmountRequest,
32 #[error("Unsupported unit")]
34 UnsupportedUnit,
35}
36
37#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
39#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
40pub struct MeltQuoteBolt11Request {
41 #[cfg_attr(feature = "swagger", schema(value_type = String))]
43 pub request: Bolt11Invoice,
44 pub unit: CurrencyUnit,
46 pub options: Option<MeltOptions>,
48}
49
50#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(untagged)]
53#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
54pub enum MeltOptions {
55 Mpp {
57 mpp: Mpp,
59 },
60}
61
62impl MeltOptions {
63 pub fn new_mpp<A>(amount: A) -> Self
65 where
66 A: Into<Amount>,
67 {
68 Self::Mpp {
69 mpp: Mpp {
70 amount: amount.into(),
71 },
72 }
73 }
74
75 pub fn amount_msat(&self) -> Amount {
77 match self {
78 Self::Mpp { mpp } => mpp.amount,
79 }
80 }
81}
82
83impl MeltQuoteBolt11Request {
84 pub fn amount_msat(&self) -> Result<Amount, Error> {
89 let MeltQuoteBolt11Request {
90 request,
91 unit: _,
92 options,
93 ..
94 } = self;
95
96 match options {
97 None => Ok(request
98 .amount_milli_satoshis()
99 .ok_or(Error::InvalidAmountRequest)?
100 .into()),
101 Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
102 }
103 }
104}
105
106#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
108#[serde(rename_all = "UPPERCASE")]
109#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MeltQuoteState))]
110pub enum QuoteState {
111 #[default]
113 Unpaid,
114 Paid,
116 Pending,
118 Unknown,
120 Failed,
122}
123
124impl fmt::Display for QuoteState {
125 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126 match self {
127 Self::Unpaid => write!(f, "UNPAID"),
128 Self::Paid => write!(f, "PAID"),
129 Self::Pending => write!(f, "PENDING"),
130 Self::Unknown => write!(f, "UNKNOWN"),
131 Self::Failed => write!(f, "FAILED"),
132 }
133 }
134}
135
136impl FromStr for QuoteState {
137 type Err = Error;
138
139 fn from_str(state: &str) -> Result<Self, Self::Err> {
140 match state {
141 "PENDING" => Ok(Self::Pending),
142 "PAID" => Ok(Self::Paid),
143 "UNPAID" => Ok(Self::Unpaid),
144 "UNKNOWN" => Ok(Self::Unknown),
145 "FAILED" => Ok(Self::Failed),
146 _ => Err(Error::UnknownState),
147 }
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
153#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
154#[serde(bound = "Q: Serialize")]
155pub struct MeltQuoteBolt11Response<Q> {
156 pub quote: Q,
158 pub amount: Amount,
160 pub fee_reserve: Amount,
162 pub paid: Option<bool>,
166 pub state: MeltQuoteState,
168 pub expiry: u64,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub payment_preimage: Option<String>,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub change: Option<Vec<BlindSignature>>,
176 #[serde(skip_serializing_if = "Option::is_none")]
179 pub request: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
183 pub unit: Option<CurrencyUnit>,
184}
185
186impl<Q: ToString> MeltQuoteBolt11Response<Q> {
187 pub fn to_string_id(self) -> MeltQuoteBolt11Response<String> {
190 MeltQuoteBolt11Response {
191 quote: self.quote.to_string(),
192 amount: self.amount,
193 fee_reserve: self.fee_reserve,
194 paid: self.paid,
195 state: self.state,
196 expiry: self.expiry,
197 payment_preimage: self.payment_preimage,
198 change: self.change,
199 request: self.request,
200 unit: self.unit,
201 }
202 }
203}
204
205#[cfg(feature = "mint")]
206impl From<MeltQuoteBolt11Response<Uuid>> for MeltQuoteBolt11Response<String> {
207 fn from(value: MeltQuoteBolt11Response<Uuid>) -> Self {
208 Self {
209 quote: value.quote.to_string(),
210 amount: value.amount,
211 fee_reserve: value.fee_reserve,
212 paid: value.paid,
213 state: value.state,
214 expiry: value.expiry,
215 payment_preimage: value.payment_preimage,
216 change: value.change,
217 request: value.request,
218 unit: value.unit,
219 }
220 }
221}
222
223impl<'de, Q: DeserializeOwned> Deserialize<'de> for MeltQuoteBolt11Response<Q> {
226 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
227 where
228 D: Deserializer<'de>,
229 {
230 let value = Value::deserialize(deserializer)?;
231
232 let quote: Q = serde_json::from_value(
233 value
234 .get("quote")
235 .ok_or(serde::de::Error::missing_field("quote"))?
236 .clone(),
237 )
238 .map_err(|_| serde::de::Error::custom("Invalid quote if string"))?;
239
240 let amount = value
241 .get("amount")
242 .ok_or(serde::de::Error::missing_field("amount"))?
243 .as_u64()
244 .ok_or(serde::de::Error::missing_field("amount"))?;
245 let amount = Amount::from(amount);
246
247 let fee_reserve = value
248 .get("fee_reserve")
249 .ok_or(serde::de::Error::missing_field("fee_reserve"))?
250 .as_u64()
251 .ok_or(serde::de::Error::missing_field("fee_reserve"))?;
252
253 let fee_reserve = Amount::from(fee_reserve);
254
255 let paid: Option<bool> = value.get("paid").and_then(|p| p.as_bool());
256
257 let state: Option<String> = value
258 .get("state")
259 .and_then(|s| serde_json::from_value(s.clone()).ok());
260
261 let (state, paid) = match (state, paid) {
262 (None, None) => return Err(serde::de::Error::custom("State or paid must be defined")),
263 (Some(state), _) => {
264 let state: QuoteState = QuoteState::from_str(&state)
265 .map_err(|_| serde::de::Error::custom("Unknown state"))?;
266 let paid = state == QuoteState::Paid;
267
268 (state, paid)
269 }
270 (None, Some(paid)) => {
271 let state = if paid {
272 QuoteState::Paid
273 } else {
274 QuoteState::Unpaid
275 };
276 (state, paid)
277 }
278 };
279
280 let expiry = value
281 .get("expiry")
282 .ok_or(serde::de::Error::missing_field("expiry"))?
283 .as_u64()
284 .ok_or(serde::de::Error::missing_field("expiry"))?;
285
286 let payment_preimage: Option<String> = value
287 .get("payment_preimage")
288 .and_then(|p| serde_json::from_value(p.clone()).ok());
289
290 let change: Option<Vec<BlindSignature>> = value
291 .get("change")
292 .and_then(|b| serde_json::from_value(b.clone()).ok());
293
294 let request: Option<String> = value
295 .get("request")
296 .and_then(|r| serde_json::from_value(r.clone()).ok());
297
298 let unit: Option<CurrencyUnit> = value
299 .get("unit")
300 .and_then(|u| serde_json::from_value(u.clone()).ok());
301
302 Ok(Self {
303 quote,
304 amount,
305 fee_reserve,
306 paid: Some(paid),
307 state,
308 expiry,
309 payment_preimage,
310 change,
311 request,
312 unit,
313 })
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
319#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
320#[serde(bound = "Q: Serialize + DeserializeOwned")]
321pub struct MeltBolt11Request<Q> {
322 pub quote: Q,
324 #[cfg_attr(feature = "swagger", schema(value_type = Vec<crate::Proof>))]
326 pub inputs: Proofs,
327 pub outputs: Option<Vec<BlindedMessage>>,
330}
331
332#[cfg(feature = "mint")]
333impl TryFrom<MeltBolt11Request<String>> for MeltBolt11Request<Uuid> {
334 type Error = uuid::Error;
335
336 fn try_from(value: MeltBolt11Request<String>) -> Result<Self, Self::Error> {
337 Ok(Self {
338 quote: Uuid::from_str(&value.quote)?,
339 inputs: value.inputs,
340 outputs: value.outputs,
341 })
342 }
343}
344
345impl<Q: Serialize + DeserializeOwned> MeltBolt11Request<Q> {
346 pub fn proofs_amount(&self) -> Result<Amount, Error> {
348 Amount::try_sum(self.inputs.iter().map(|proof| proof.amount))
349 .map_err(|_| Error::AmountOverflow)
350 }
351}
352
353#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
355#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
356pub struct MeltMethodSettings {
357 pub method: PaymentMethod,
359 pub unit: CurrencyUnit,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub min_amount: Option<Amount>,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub max_amount: Option<Amount>,
367}
368
369impl Settings {
370 pub fn new(methods: Vec<MeltMethodSettings>, disabled: bool) -> Self {
372 Self { methods, disabled }
373 }
374
375 pub fn get_settings(
377 &self,
378 unit: &CurrencyUnit,
379 method: &PaymentMethod,
380 ) -> Option<MeltMethodSettings> {
381 for method_settings in self.methods.iter() {
382 if method_settings.method.eq(method) && method_settings.unit.eq(unit) {
383 return Some(method_settings.clone());
384 }
385 }
386
387 None
388 }
389
390 pub fn remove_settings(
392 &mut self,
393 unit: &CurrencyUnit,
394 method: &PaymentMethod,
395 ) -> Option<MeltMethodSettings> {
396 self.methods
397 .iter()
398 .position(|settings| settings.method.eq(method) && settings.unit.eq(unit))
399 .map(|index| self.methods.remove(index))
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
405#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut05::Settings))]
406pub struct Settings {
407 pub methods: Vec<MeltMethodSettings>,
409 pub disabled: bool,
411}