1use std::fmt;
4use std::str::FromStr;
5
6use lightning_invoice::Bolt11Invoice;
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use super::{BlindSignature, CurrencyUnit, MeltQuoteState, Mpp, PublicKey};
12#[cfg(feature = "mint")]
13use crate::quote_id::QuoteId;
14use crate::util::serde_helpers::deserialize_empty_string_as_none;
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(
105 default,
106 skip_serializing_if = "Option::is_none",
107 deserialize_with = "deserialize_empty_string_as_none"
108 )]
109 pub pubkey: Option<PublicKey>,
110}
111impl<Q: ToString> MintQuoteBolt11Response<Q> {
112 pub fn to_string_id(&self) -> MintQuoteBolt11Response<String> {
114 MintQuoteBolt11Response {
115 quote: self.quote.to_string(),
116 request: self.request.clone(),
117 state: self.state,
118 expiry: self.expiry,
119 pubkey: self.pubkey,
120 amount: self.amount,
121 unit: self.unit.clone(),
122 }
123 }
124}
125
126#[cfg(feature = "mint")]
127impl From<MintQuoteBolt11Response<QuoteId>> for MintQuoteBolt11Response<String> {
128 fn from(value: MintQuoteBolt11Response<QuoteId>) -> Self {
129 Self {
130 quote: value.quote.to_string(),
131 request: value.request,
132 state: value.state,
133 expiry: value.expiry,
134 pubkey: value.pubkey,
135 amount: value.amount,
136 unit: value.unit.clone(),
137 }
138 }
139}
140
141#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
143#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
144pub struct MeltQuoteBolt11Request {
145 #[cfg_attr(feature = "swagger", schema(value_type = String))]
147 pub request: Bolt11Invoice,
148 pub unit: CurrencyUnit,
150 pub options: Option<MeltOptions>,
152}
153
154#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(untagged)]
157#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
158pub enum MeltOptions {
159 Mpp {
161 mpp: Mpp,
163 },
164 Amountless {
166 amountless: Amountless,
168 },
169}
170
171impl MeltOptions {
172 pub fn new_mpp<A>(amount: A) -> Self
174 where
175 A: Into<Amount>,
176 {
177 Self::Mpp {
178 mpp: Mpp {
179 amount: amount.into(),
180 },
181 }
182 }
183
184 pub fn new_amountless<A>(amount_msat: A) -> Self
186 where
187 A: Into<Amount>,
188 {
189 Self::Amountless {
190 amountless: Amountless {
191 amount_msat: amount_msat.into(),
192 },
193 }
194 }
195
196 pub fn amount_msat(&self) -> Amount {
198 match self {
199 Self::Mpp { mpp } => mpp.amount,
200 Self::Amountless { amountless } => amountless.amount_msat,
201 }
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
207#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
208pub struct Amountless {
209 pub amount_msat: Amount,
211}
212
213impl MeltQuoteBolt11Request {
214 pub fn amount_msat(&self) -> Result<Amount, Error> {
219 let MeltQuoteBolt11Request {
220 request, options, ..
221 } = self;
222
223 match options {
224 None => Ok(request
225 .amount_milli_satoshis()
226 .ok_or(Error::InvalidAmountRequest)?
227 .into()),
228 Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
229 Some(MeltOptions::Amountless { amountless }) => {
230 let amount = amountless.amount_msat;
231 if let Some(amount_msat) = request.amount_milli_satoshis() {
232 if amount != amount_msat.into() {
233 return Err(Error::InvalidAmountRequest);
234 }
235 }
236 Ok(amount)
237 }
238 }
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
245#[serde(bound = "Q: Serialize + DeserializeOwned")]
246pub struct MeltQuoteBolt11Response<Q> {
247 pub quote: Q,
249 pub amount: Amount,
251 pub fee_reserve: Amount,
253 pub state: MeltQuoteState,
255 pub expiry: u64,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub payment_preimage: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub change: Option<Vec<BlindSignature>>,
263 #[serde(skip_serializing_if = "Option::is_none")]
266 pub request: Option<String>,
267 #[serde(skip_serializing_if = "Option::is_none")]
270 pub unit: Option<CurrencyUnit>,
271}
272
273impl<Q: ToString> MeltQuoteBolt11Response<Q> {
274 pub fn to_string_id(self) -> MeltQuoteBolt11Response<String> {
277 MeltQuoteBolt11Response {
278 quote: self.quote.to_string(),
279 amount: self.amount,
280 fee_reserve: self.fee_reserve,
281 state: self.state,
282 expiry: self.expiry,
283 payment_preimage: self.payment_preimage,
284 change: self.change,
285 request: self.request,
286 unit: self.unit,
287 }
288 }
289}
290
291#[cfg(feature = "mint")]
292impl From<MeltQuoteBolt11Response<QuoteId>> for MeltQuoteBolt11Response<String> {
293 fn from(value: MeltQuoteBolt11Response<QuoteId>) -> Self {
294 Self {
295 quote: value.quote.to_string(),
296 amount: value.amount,
297 fee_reserve: value.fee_reserve,
298 state: value.state,
299 expiry: value.expiry,
300 payment_preimage: value.payment_preimage,
301 change: value.change,
302 request: value.request,
303 unit: value.unit,
304 }
305 }
306}