1use std::fmt;
4use std::str::FromStr;
5
6use lightning_invoice::Bolt11Invoice;
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Deserializer, Serialize};
9use serde_json::Value;
10use thiserror::Error;
11
12use super::{BlindSignature, CurrencyUnit, MeltQuoteState, Mpp, PublicKey};
13#[cfg(feature = "mint")]
14use crate::quote_id::QuoteId;
15use crate::Amount;
16
17#[derive(Debug, Error)]
19pub enum Error {
20 #[error("Unknown Quote State")]
22 UnknownState,
23 #[error("Amount overflow")]
25 AmountOverflow,
26 #[error("Invalid Request")]
28 InvalidAmountRequest,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
34pub struct MintQuoteBolt11Request {
35 pub amount: Amount,
37 pub unit: CurrencyUnit,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub description: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub pubkey: Option<PublicKey>,
45}
46
47#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
49#[serde(rename_all = "UPPERCASE")]
50#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = MintQuoteState))]
51pub enum QuoteState {
52 #[default]
54 Unpaid,
55 Paid,
57 Issued,
59}
60
61impl fmt::Display for QuoteState {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 match self {
64 Self::Unpaid => write!(f, "UNPAID"),
65 Self::Paid => write!(f, "PAID"),
66 Self::Issued => write!(f, "ISSUED"),
67 }
68 }
69}
70
71impl FromStr for QuoteState {
72 type Err = Error;
73
74 fn from_str(state: &str) -> Result<Self, Self::Err> {
75 match state {
76 "PAID" => Ok(Self::Paid),
77 "UNPAID" => Ok(Self::Unpaid),
78 "ISSUED" => Ok(Self::Issued),
79 _ => Err(Error::UnknownState),
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
87#[serde(bound = "Q: Serialize + DeserializeOwned")]
88pub struct MintQuoteBolt11Response<Q> {
89 pub quote: Q,
91 pub request: String,
93 pub amount: Option<Amount>,
96 pub unit: Option<CurrencyUnit>,
99 pub state: QuoteState,
101 pub expiry: Option<u64>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub pubkey: Option<PublicKey>,
106}
107impl<Q: ToString> MintQuoteBolt11Response<Q> {
108 pub fn to_string_id(&self) -> MintQuoteBolt11Response<String> {
110 MintQuoteBolt11Response {
111 quote: self.quote.to_string(),
112 request: self.request.clone(),
113 state: self.state,
114 expiry: self.expiry,
115 pubkey: self.pubkey,
116 amount: self.amount,
117 unit: self.unit.clone(),
118 }
119 }
120}
121
122#[cfg(feature = "mint")]
123impl From<MintQuoteBolt11Response<QuoteId>> for MintQuoteBolt11Response<String> {
124 fn from(value: MintQuoteBolt11Response<QuoteId>) -> Self {
125 Self {
126 quote: value.quote.to_string(),
127 request: value.request,
128 state: value.state,
129 expiry: value.expiry,
130 pubkey: value.pubkey,
131 amount: value.amount,
132 unit: value.unit.clone(),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
139#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
140pub struct MeltQuoteBolt11Request {
141 #[cfg_attr(feature = "swagger", schema(value_type = String))]
143 pub request: Bolt11Invoice,
144 pub unit: CurrencyUnit,
146 pub options: Option<MeltOptions>,
148}
149
150#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(untagged)]
153#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
154pub enum MeltOptions {
155 Mpp {
157 mpp: Mpp,
159 },
160 Amountless {
162 amountless: Amountless,
164 },
165}
166
167impl MeltOptions {
168 pub fn new_mpp<A>(amount: A) -> Self
170 where
171 A: Into<Amount>,
172 {
173 Self::Mpp {
174 mpp: Mpp {
175 amount: amount.into(),
176 },
177 }
178 }
179
180 pub fn new_amountless<A>(amount_msat: A) -> Self
182 where
183 A: Into<Amount>,
184 {
185 Self::Amountless {
186 amountless: Amountless {
187 amount_msat: amount_msat.into(),
188 },
189 }
190 }
191
192 pub fn amount_msat(&self) -> Amount {
194 match self {
195 Self::Mpp { mpp } => mpp.amount,
196 Self::Amountless { amountless } => amountless.amount_msat,
197 }
198 }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
203#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
204pub struct Amountless {
205 pub amount_msat: Amount,
207}
208
209impl MeltQuoteBolt11Request {
210 pub fn amount_msat(&self) -> Result<Amount, Error> {
215 let MeltQuoteBolt11Request {
216 request,
217 unit: _,
218 options,
219 ..
220 } = self;
221
222 match options {
223 None => Ok(request
224 .amount_milli_satoshis()
225 .ok_or(Error::InvalidAmountRequest)?
226 .into()),
227 Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
228 Some(MeltOptions::Amountless { amountless }) => {
229 let amount = amountless.amount_msat;
230 if let Some(amount_msat) = request.amount_milli_satoshis() {
231 if amount != amount_msat.into() {
232 return Err(Error::InvalidAmountRequest);
233 }
234 }
235 Ok(amount)
236 }
237 }
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
243#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
244#[serde(bound = "Q: Serialize")]
245pub struct MeltQuoteBolt11Response<Q> {
246 pub quote: Q,
248 pub amount: Amount,
250 pub fee_reserve: Amount,
252 pub paid: Option<bool>,
256 pub state: MeltQuoteState,
258 pub expiry: u64,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub payment_preimage: Option<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub change: Option<Vec<BlindSignature>>,
266 #[serde(skip_serializing_if = "Option::is_none")]
269 pub request: Option<String>,
270 #[serde(skip_serializing_if = "Option::is_none")]
273 pub unit: Option<CurrencyUnit>,
274}
275
276impl<Q: ToString> MeltQuoteBolt11Response<Q> {
277 pub fn to_string_id(self) -> MeltQuoteBolt11Response<String> {
280 MeltQuoteBolt11Response {
281 quote: self.quote.to_string(),
282 amount: self.amount,
283 fee_reserve: self.fee_reserve,
284 paid: self.paid,
285 state: self.state,
286 expiry: self.expiry,
287 payment_preimage: self.payment_preimage,
288 change: self.change,
289 request: self.request,
290 unit: self.unit,
291 }
292 }
293}
294
295#[cfg(feature = "mint")]
296impl From<MeltQuoteBolt11Response<QuoteId>> for MeltQuoteBolt11Response<String> {
297 fn from(value: MeltQuoteBolt11Response<QuoteId>) -> Self {
298 Self {
299 quote: value.quote.to_string(),
300 amount: value.amount,
301 fee_reserve: value.fee_reserve,
302 paid: value.paid,
303 state: value.state,
304 expiry: value.expiry,
305 payment_preimage: value.payment_preimage,
306 change: value.change,
307 request: value.request,
308 unit: value.unit,
309 }
310 }
311}
312
313impl<'de, Q: DeserializeOwned> Deserialize<'de> for MeltQuoteBolt11Response<Q> {
316 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
317 where
318 D: Deserializer<'de>,
319 {
320 let value = Value::deserialize(deserializer)?;
321
322 let quote: Q = serde_json::from_value(
323 value
324 .get("quote")
325 .ok_or(serde::de::Error::missing_field("quote"))?
326 .clone(),
327 )
328 .map_err(|_| serde::de::Error::custom("Invalid quote if string"))?;
329
330 let amount = value
331 .get("amount")
332 .ok_or(serde::de::Error::missing_field("amount"))?
333 .as_u64()
334 .ok_or(serde::de::Error::missing_field("amount"))?;
335 let amount = Amount::from(amount);
336
337 let fee_reserve = value
338 .get("fee_reserve")
339 .ok_or(serde::de::Error::missing_field("fee_reserve"))?
340 .as_u64()
341 .ok_or(serde::de::Error::missing_field("fee_reserve"))?;
342
343 let fee_reserve = Amount::from(fee_reserve);
344
345 let paid: Option<bool> = value.get("paid").and_then(|p| p.as_bool());
346
347 let state: Option<String> = value
348 .get("state")
349 .and_then(|s| serde_json::from_value(s.clone()).ok());
350
351 let (state, paid) = match (state, paid) {
352 (None, None) => return Err(serde::de::Error::custom("State or paid must be defined")),
353 (Some(state), _) => {
354 let state: MeltQuoteState = MeltQuoteState::from_str(&state)
355 .map_err(|_| serde::de::Error::custom("Unknown state"))?;
356 let paid = state == MeltQuoteState::Paid;
357
358 (state, paid)
359 }
360 (None, Some(paid)) => {
361 let state = if paid {
362 MeltQuoteState::Paid
363 } else {
364 MeltQuoteState::Unpaid
365 };
366 (state, paid)
367 }
368 };
369
370 let expiry = value
371 .get("expiry")
372 .ok_or(serde::de::Error::missing_field("expiry"))?
373 .as_u64()
374 .ok_or(serde::de::Error::missing_field("expiry"))?;
375
376 let payment_preimage: Option<String> = value
377 .get("payment_preimage")
378 .and_then(|p| serde_json::from_value(p.clone()).ok());
379
380 let change: Option<Vec<BlindSignature>> = value
381 .get("change")
382 .and_then(|b| serde_json::from_value(b.clone()).ok());
383
384 let request: Option<String> = value
385 .get("request")
386 .and_then(|r| serde_json::from_value(r.clone()).ok());
387
388 let unit: Option<CurrencyUnit> = value
389 .get("unit")
390 .and_then(|u| serde_json::from_value(u.clone()).ok());
391
392 Ok(Self {
393 quote,
394 amount,
395 fee_reserve,
396 paid: Some(paid),
397 state,
398 expiry,
399 payment_preimage,
400 change,
401 request,
402 unit,
403 })
404 }
405}