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#[cfg(feature = "mint")]
12use uuid::Uuid;
13
14use super::{BlindSignature, CurrencyUnit, MeltQuoteState, Mpp, PublicKey};
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 Pending,
61 Issued,
63}
64
65impl fmt::Display for QuoteState {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 match self {
68 Self::Unpaid => write!(f, "UNPAID"),
69 Self::Paid => write!(f, "PAID"),
70 Self::Pending => write!(f, "PENDING"),
71 Self::Issued => write!(f, "ISSUED"),
72 }
73 }
74}
75
76impl FromStr for QuoteState {
77 type Err = Error;
78
79 fn from_str(state: &str) -> Result<Self, Self::Err> {
80 match state {
81 "PENDING" => Ok(Self::Pending),
82 "PAID" => Ok(Self::Paid),
83 "UNPAID" => Ok(Self::Unpaid),
84 "ISSUED" => Ok(Self::Issued),
85 _ => Err(Error::UnknownState),
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
93#[serde(bound = "Q: Serialize + DeserializeOwned")]
94pub struct MintQuoteBolt11Response<Q> {
95 pub quote: Q,
97 pub request: String,
99 pub amount: Option<Amount>,
102 pub unit: Option<CurrencyUnit>,
105 pub state: QuoteState,
107 pub expiry: Option<u64>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub pubkey: Option<PublicKey>,
112}
113impl<Q: ToString> MintQuoteBolt11Response<Q> {
114 pub fn to_string_id(&self) -> MintQuoteBolt11Response<String> {
116 MintQuoteBolt11Response {
117 quote: self.quote.to_string(),
118 request: self.request.clone(),
119 state: self.state,
120 expiry: self.expiry,
121 pubkey: self.pubkey,
122 amount: self.amount,
123 unit: self.unit.clone(),
124 }
125 }
126}
127
128#[cfg(feature = "mint")]
129impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
130 fn from(value: MintQuoteBolt11Response<Uuid>) -> Self {
131 Self {
132 quote: value.quote.to_string(),
133 request: value.request,
134 state: value.state,
135 expiry: value.expiry,
136 pubkey: value.pubkey,
137 amount: value.amount,
138 unit: value.unit.clone(),
139 }
140 }
141}
142
143#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
145#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
146pub struct MeltQuoteBolt11Request {
147 #[cfg_attr(feature = "swagger", schema(value_type = String))]
149 pub request: Bolt11Invoice,
150 pub unit: CurrencyUnit,
152 pub options: Option<MeltOptions>,
154}
155
156#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(untagged)]
159#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
160pub enum MeltOptions {
161 Mpp {
163 mpp: Mpp,
165 },
166 Amountless {
168 amountless: Amountless,
170 },
171}
172
173impl MeltOptions {
174 pub fn new_mpp<A>(amount: A) -> Self
176 where
177 A: Into<Amount>,
178 {
179 Self::Mpp {
180 mpp: Mpp {
181 amount: amount.into(),
182 },
183 }
184 }
185
186 pub fn new_amountless<A>(amount_msat: A) -> Self
188 where
189 A: Into<Amount>,
190 {
191 Self::Amountless {
192 amountless: Amountless {
193 amount_msat: amount_msat.into(),
194 },
195 }
196 }
197
198 pub fn amount_msat(&self) -> Amount {
200 match self {
201 Self::Mpp { mpp } => mpp.amount,
202 Self::Amountless { amountless } => amountless.amount_msat,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
209#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
210pub struct Amountless {
211 pub amount_msat: Amount,
213}
214
215impl MeltQuoteBolt11Request {
216 pub fn amount_msat(&self) -> Result<Amount, Error> {
221 let MeltQuoteBolt11Request {
222 request,
223 unit: _,
224 options,
225 ..
226 } = self;
227
228 match options {
229 None => Ok(request
230 .amount_milli_satoshis()
231 .ok_or(Error::InvalidAmountRequest)?
232 .into()),
233 Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
234 Some(MeltOptions::Amountless { amountless }) => {
235 let amount = amountless.amount_msat;
236 if let Some(amount_msat) = request.amount_milli_satoshis() {
237 if amount != amount_msat.into() {
238 return Err(Error::InvalidAmountRequest);
239 }
240 }
241 Ok(amount)
242 }
243 }
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
249#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
250#[serde(bound = "Q: Serialize")]
251pub struct MeltQuoteBolt11Response<Q> {
252 pub quote: Q,
254 pub amount: Amount,
256 pub fee_reserve: Amount,
258 pub paid: Option<bool>,
262 pub state: MeltQuoteState,
264 pub expiry: u64,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub payment_preimage: Option<String>,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub change: Option<Vec<BlindSignature>>,
272 #[serde(skip_serializing_if = "Option::is_none")]
275 pub request: Option<String>,
276 #[serde(skip_serializing_if = "Option::is_none")]
279 pub unit: Option<CurrencyUnit>,
280}
281
282impl<Q: ToString> MeltQuoteBolt11Response<Q> {
283 pub fn to_string_id(self) -> MeltQuoteBolt11Response<String> {
286 MeltQuoteBolt11Response {
287 quote: self.quote.to_string(),
288 amount: self.amount,
289 fee_reserve: self.fee_reserve,
290 paid: self.paid,
291 state: self.state,
292 expiry: self.expiry,
293 payment_preimage: self.payment_preimage,
294 change: self.change,
295 request: self.request,
296 unit: self.unit,
297 }
298 }
299}
300
301#[cfg(feature = "mint")]
302impl From<MeltQuoteBolt11Response<Uuid>> for MeltQuoteBolt11Response<String> {
303 fn from(value: MeltQuoteBolt11Response<Uuid>) -> Self {
304 Self {
305 quote: value.quote.to_string(),
306 amount: value.amount,
307 fee_reserve: value.fee_reserve,
308 paid: value.paid,
309 state: value.state,
310 expiry: value.expiry,
311 payment_preimage: value.payment_preimage,
312 change: value.change,
313 request: value.request,
314 unit: value.unit,
315 }
316 }
317}
318
319impl<'de, Q: DeserializeOwned> Deserialize<'de> for MeltQuoteBolt11Response<Q> {
322 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323 where
324 D: Deserializer<'de>,
325 {
326 let value = Value::deserialize(deserializer)?;
327
328 let quote: Q = serde_json::from_value(
329 value
330 .get("quote")
331 .ok_or(serde::de::Error::missing_field("quote"))?
332 .clone(),
333 )
334 .map_err(|_| serde::de::Error::custom("Invalid quote if string"))?;
335
336 let amount = value
337 .get("amount")
338 .ok_or(serde::de::Error::missing_field("amount"))?
339 .as_u64()
340 .ok_or(serde::de::Error::missing_field("amount"))?;
341 let amount = Amount::from(amount);
342
343 let fee_reserve = value
344 .get("fee_reserve")
345 .ok_or(serde::de::Error::missing_field("fee_reserve"))?
346 .as_u64()
347 .ok_or(serde::de::Error::missing_field("fee_reserve"))?;
348
349 let fee_reserve = Amount::from(fee_reserve);
350
351 let paid: Option<bool> = value.get("paid").and_then(|p| p.as_bool());
352
353 let state: Option<String> = value
354 .get("state")
355 .and_then(|s| serde_json::from_value(s.clone()).ok());
356
357 let (state, paid) = match (state, paid) {
358 (None, None) => return Err(serde::de::Error::custom("State or paid must be defined")),
359 (Some(state), _) => {
360 let state: MeltQuoteState = MeltQuoteState::from_str(&state)
361 .map_err(|_| serde::de::Error::custom("Unknown state"))?;
362 let paid = state == MeltQuoteState::Paid;
363
364 (state, paid)
365 }
366 (None, Some(paid)) => {
367 let state = if paid {
368 MeltQuoteState::Paid
369 } else {
370 MeltQuoteState::Unpaid
371 };
372 (state, paid)
373 }
374 };
375
376 let expiry = value
377 .get("expiry")
378 .ok_or(serde::de::Error::missing_field("expiry"))?
379 .as_u64()
380 .ok_or(serde::de::Error::missing_field("expiry"))?;
381
382 let payment_preimage: Option<String> = value
383 .get("payment_preimage")
384 .and_then(|p| serde_json::from_value(p.clone()).ok());
385
386 let change: Option<Vec<BlindSignature>> = value
387 .get("change")
388 .and_then(|b| serde_json::from_value(b.clone()).ok());
389
390 let request: Option<String> = value
391 .get("request")
392 .and_then(|r| serde_json::from_value(r.clone()).ok());
393
394 let unit: Option<CurrencyUnit> = value
395 .get("unit")
396 .and_then(|u| serde_json::from_value(u.clone()).ok());
397
398 Ok(Self {
399 quote,
400 amount,
401 fee_reserve,
402 paid: Some(paid),
403 state,
404 expiry,
405 payment_preimage,
406 change,
407 request,
408 unit,
409 })
410 }
411}