1use serde::{Deserialize, Serialize};
4
5use crate::error::Error;
6use crate::nuts::nut00::ProofsMethods;
7use crate::nuts::{CurrencyUnit, MeltQuoteState, PaymentMethod, Proofs};
8#[cfg(feature = "wallet")]
10pub use crate::wallet::ProofInfo;
11use crate::Amount;
12
13#[derive(Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
15pub struct FinalizedMelt {
16 quote_id: String,
18 state: MeltQuoteState,
20 payment_proof: Option<String>,
22 change: Option<Proofs>,
24 amount: Amount,
26 fee_paid: Amount,
28}
29
30impl FinalizedMelt {
31 pub fn new(
33 quote_id: String,
34 state: MeltQuoteState,
35 payment_proof: Option<String>,
36 amount: Amount,
37 fee_paid: Amount,
38 change: Option<Proofs>,
39 ) -> Self {
40 Self {
41 quote_id,
42 state,
43 payment_proof,
44 change,
45 amount,
46 fee_paid,
47 }
48 }
49
50 pub fn from_proofs(
52 quote_id: String,
53 state: MeltQuoteState,
54 payment_proof: Option<String>,
55 quote_amount: Amount,
56 proofs: Proofs,
57 change_proofs: Option<Proofs>,
58 ) -> Result<Self, Error> {
59 let proofs_amount = proofs.total_amount()?;
60 let change_amount = match &change_proofs {
61 Some(change_proofs) => change_proofs.total_amount()?,
62 None => Amount::ZERO,
63 };
64
65 tracing::info!(
66 "Proofs amount: {} Amount: {} Change: {}",
67 proofs_amount,
68 quote_amount,
69 change_amount
70 );
71
72 let fee_paid = proofs_amount
73 .checked_sub(
74 quote_amount
75 .checked_add(change_amount)
76 .ok_or(Error::AmountOverflow)?,
77 )
78 .ok_or(Error::AmountOverflow)?;
79
80 Ok(Self {
81 quote_id,
82 state,
83 payment_proof,
84 change: change_proofs,
85 amount: quote_amount,
86 fee_paid,
87 })
88 }
89
90 #[inline]
92 pub fn quote_id(&self) -> &str {
93 &self.quote_id
94 }
95
96 #[inline]
98 pub fn state(&self) -> MeltQuoteState {
99 self.state
100 }
101
102 #[inline]
104 pub fn payment_proof(&self) -> Option<&str> {
105 self.payment_proof.as_deref()
106 }
107
108 #[inline]
110 pub fn change(&self) -> Option<&Proofs> {
111 self.change.as_ref()
112 }
113
114 #[inline]
116 pub fn into_change(self) -> Option<Proofs> {
117 self.change
118 }
119
120 #[inline]
122 pub fn amount(&self) -> Amount {
123 self.amount
124 }
125
126 #[inline]
128 pub fn fee_paid(&self) -> Amount {
129 self.fee_paid
130 }
131
132 #[inline]
139 pub fn total_amount(&self) -> Amount {
140 self.amount
141 .checked_add(self.fee_paid)
142 .expect("We check when calc fee paid")
143 }
144}
145
146impl std::fmt::Debug for FinalizedMelt {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 f.debug_struct("FinalizedMelt")
149 .field("quote_id", &self.quote_id)
150 .field("state", &self.state)
151 .field("amount", &self.amount)
152 .field("fee_paid", &self.fee_paid)
153 .finish()
154 }
155}
156
157#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
160pub struct PaymentProcessorKey {
161 pub unit: CurrencyUnit,
163 pub method: PaymentMethod,
165}
166
167impl PaymentProcessorKey {
168 pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self {
170 Self { unit, method }
171 }
172}
173
174#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
176pub struct QuoteTTL {
177 pub mint_ttl: u64,
179 pub melt_ttl: u64,
181}
182
183impl QuoteTTL {
184 pub fn new(mint_ttl: u64, melt_ttl: u64) -> QuoteTTL {
186 Self { mint_ttl, melt_ttl }
187 }
188}
189
190impl Default for QuoteTTL {
191 fn default() -> Self {
192 Self {
193 mint_ttl: 60 * 60, melt_ttl: 60, }
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub struct FeeReserve {
202 pub min_fee_reserve: Amount,
204 pub percent_fee_reserve: f32,
206}
207
208#[derive(Debug, Clone, Hash, PartialEq, Eq)]
210pub struct IssuerVersion {
211 pub implementation: String,
213 pub major: u16,
215 pub minor: u16,
217 pub patch: u16,
219}
220
221impl IssuerVersion {
222 pub fn new(implementation: String, major: u16, minor: u16, patch: u16) -> Self {
224 Self {
225 implementation,
226 major,
227 minor,
228 patch,
229 }
230 }
231}
232
233impl std::fmt::Display for IssuerVersion {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 write!(
236 f,
237 "{}/{}.{}.{}",
238 self.implementation, self.major, self.minor, self.patch
239 )
240 }
241}
242
243impl PartialOrd for IssuerVersion {
244 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
245 if self.implementation != other.implementation {
246 return None;
247 }
248
249 match self.major.cmp(&other.major) {
250 std::cmp::Ordering::Equal => match self.minor.cmp(&other.minor) {
251 std::cmp::Ordering::Equal => Some(self.patch.cmp(&other.patch)),
252 other => Some(other),
253 },
254 other => Some(other),
255 }
256 }
257}
258
259impl std::str::FromStr for IssuerVersion {
260 type Err = Error;
261
262 fn from_str(s: &str) -> Result<Self, Self::Err> {
263 let (implementation, version_str) = s
264 .split_once('/')
265 .ok_or(Error::Custom(format!("Invalid version string: {}", s)))?;
266 let implementation = implementation.to_string();
267
268 let parts: Vec<&str> = version_str.splitn(3, '.').collect();
269 if parts.len() != 3 {
270 return Err(Error::Custom(format!("Invalid version string: {}", s)));
271 }
272
273 let major = parts[0]
274 .parse()
275 .map_err(|_| Error::Custom(format!("Invalid major version: {}", parts[0])))?;
276 let minor = parts[1]
277 .parse()
278 .map_err(|_| Error::Custom(format!("Invalid minor version: {}", parts[1])))?;
279
280 let patch_str = parts[2];
282 let patch_end = patch_str
283 .find(|c: char| !c.is_numeric())
284 .unwrap_or(patch_str.len());
285 let patch = patch_str[..patch_end]
286 .parse()
287 .map_err(|_| Error::Custom(format!("Invalid patch version: {}", parts[2])))?;
288
289 Ok(Self {
290 implementation,
291 major,
292 minor,
293 patch,
294 })
295 }
296}
297
298impl Serialize for IssuerVersion {
299 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
300 where
301 S: serde::Serializer,
302 {
303 serializer.serialize_str(&self.to_string())
304 }
305}
306
307impl<'de> Deserialize<'de> for IssuerVersion {
308 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
309 where
310 D: serde::Deserializer<'de>,
311 {
312 let s = String::deserialize(deserializer)?;
313 std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use std::str::FromStr;
320
321 use super::FinalizedMelt;
322 use crate::nuts::{Id, Proof, PublicKey};
323 use crate::secret::Secret;
324 use crate::Amount;
325
326 #[test]
327 fn test_finalized_melt() {
328 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
329 let proof = Proof::new(
330 Amount::from(64),
331 keyset_id,
332 Secret::generate(),
333 PublicKey::from_hex(
334 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
335 )
336 .unwrap(),
337 );
338 let finalized = FinalizedMelt::from_proofs(
339 "test_quote_id".to_string(),
340 super::MeltQuoteState::Paid,
341 Some("preimage".to_string()),
342 Amount::from(64),
343 vec![proof.clone()],
344 None,
345 )
346 .unwrap();
347 assert_eq!(finalized.quote_id(), "test_quote_id");
348 assert_eq!(finalized.amount(), Amount::from(64));
349 assert_eq!(finalized.fee_paid(), Amount::ZERO);
350 assert_eq!(finalized.total_amount(), Amount::from(64));
351 }
352
353 #[test]
354 fn test_finalized_melt_with_change() {
355 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
356 let proof = Proof::new(
357 Amount::from(64),
358 keyset_id,
359 Secret::generate(),
360 PublicKey::from_hex(
361 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
362 )
363 .unwrap(),
364 );
365 let change_proof = Proof::new(
366 Amount::from(32),
367 keyset_id,
368 Secret::generate(),
369 PublicKey::from_hex(
370 "03deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
371 )
372 .unwrap(),
373 );
374 let finalized = FinalizedMelt::from_proofs(
375 "test_quote_id".to_string(),
376 super::MeltQuoteState::Paid,
377 Some("preimage".to_string()),
378 Amount::from(31),
379 vec![proof.clone()],
380 Some(vec![change_proof.clone()]),
381 )
382 .unwrap();
383 assert_eq!(finalized.quote_id(), "test_quote_id");
384 assert_eq!(finalized.amount(), Amount::from(31));
385 assert_eq!(finalized.fee_paid(), Amount::from(1));
386 assert_eq!(finalized.total_amount(), Amount::from(32));
387 }
388
389 use super::IssuerVersion;
390
391 #[test]
392 fn test_version_parsing() {
393 let v = IssuerVersion::from_str("cdk/1.2.3").unwrap();
395 assert_eq!(v.implementation, "cdk");
396 assert_eq!(v.major, 1);
397 assert_eq!(v.minor, 2);
398 assert_eq!(v.patch, 3);
399 assert_eq!(v.to_string(), "cdk/1.2.3");
400
401 let v = IssuerVersion::from_str("nutshell/0.16.0").unwrap();
403 assert_eq!(v.implementation, "nutshell");
404 assert_eq!(v.major, 0);
405 assert_eq!(v.minor, 16);
406 assert_eq!(v.patch, 0);
407 assert_eq!(v.to_string(), "nutshell/0.16.0");
408 }
409
410 #[test]
411 fn test_version_ordering() {
412 let v1 = IssuerVersion::from_str("cdk/0.1.0").unwrap();
413 let v2 = IssuerVersion::from_str("cdk/0.1.1").unwrap();
414 let v3 = IssuerVersion::from_str("cdk/0.2.0").unwrap();
415 let v4 = IssuerVersion::from_str("cdk/1.0.0").unwrap();
416
417 assert!(v1 < v2);
418 assert!(v2 < v3);
419 assert!(v3 < v4);
420 assert!(v1 < v4);
421
422 let v_nutshell = IssuerVersion::from_str("nutshell/0.1.0").unwrap();
424 assert_eq!(v1.partial_cmp(&v_nutshell), None);
425 assert!(!(v1 < v_nutshell));
426 assert!(!(v1 > v_nutshell));
427 assert!(!(v1 == v_nutshell));
428 }
429
430 #[test]
431 fn test_version_serialization() {
432 let v = IssuerVersion::from_str("cdk/0.14.2").unwrap();
433 let json = serde_json::to_string(&v).unwrap();
434 assert_eq!(json, "\"cdk/0.14.2\"");
435
436 let v_deserialized: IssuerVersion = serde_json::from_str(&json).unwrap();
437 assert_eq!(v, v_deserialized);
438 }
439
440 #[test]
441 fn test_cdk_version_parsing_with_suffix() {
442 let version_str = "cdk/0.15.0-rc1";
443 let version = IssuerVersion::from_str(version_str).unwrap();
444 assert_eq!(version.implementation, "cdk");
445 assert_eq!(version.major, 0);
446 assert_eq!(version.minor, 15);
447 assert_eq!(version.patch, 0);
448 }
449
450 #[test]
451 fn test_cdk_version_parsing_standard() {
452 let version_str = "cdk/0.15.0";
453 let version = IssuerVersion::from_str(version_str).unwrap();
454 assert_eq!(version.implementation, "cdk");
455 assert_eq!(version.major, 0);
456 assert_eq!(version.minor, 15);
457 assert_eq!(version.patch, 0);
458 }
459
460 #[test]
461 fn test_cdk_version_parsing_complex_suffix() {
462 let version_str = "cdk/0.15.0-beta.1+build123";
463 let version = IssuerVersion::from_str(version_str).unwrap();
464 assert_eq!(version.implementation, "cdk");
465 assert_eq!(version.major, 0);
466 assert_eq!(version.minor, 15);
467 assert_eq!(version.patch, 0);
468 }
469
470 #[test]
471 fn test_cdk_version_parsing_invalid() {
472 let version_str = "0.15.0";
474 assert!(IssuerVersion::from_str(version_str).is_err());
475
476 let version_str = "cdk/0.15";
478 assert!(IssuerVersion::from_str(version_str).is_err());
479
480 let version_str = "cdk/0.15.a";
481 assert!(IssuerVersion::from_str(version_str).is_err());
482 }
483
484 #[test]
485 fn test_cdk_version_parsing_with_implementation() {
486 let version_str = "nutshell/0.16.2";
487 let version = IssuerVersion::from_str(version_str).unwrap();
488 assert_eq!(version.implementation, "nutshell");
489 assert_eq!(version.major, 0);
490 assert_eq!(version.minor, 16);
491 assert_eq!(version.patch, 2);
492 }
493
494 #[test]
495 fn test_cdk_version_comparison_different_implementations() {
496 let v1 = IssuerVersion::from_str("cdk/0.15.0").unwrap();
497 let v2 = IssuerVersion::from_str("nutshell/0.15.0").unwrap();
498
499 assert_eq!(v1.partial_cmp(&v2), None);
500 }
501}