1use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6
7use super::amount::{Amount, CurrencyUnit};
8use super::quote::PaymentMethod;
9use crate::error::FfiError;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, uniffi::Record)]
13#[serde(transparent)]
14pub struct MintUrl {
15 pub url: String,
16}
17
18impl MintUrl {
19 pub fn new(url: String) -> Result<Self, FfiError> {
20 url::Url::parse(&url).map_err(|e| FfiError::internal(format!("Invalid URL: {}", e)))?;
22
23 Ok(Self { url })
24 }
25}
26
27impl From<cdk::mint_url::MintUrl> for MintUrl {
28 fn from(mint_url: cdk::mint_url::MintUrl) -> Self {
29 Self {
30 url: mint_url.to_string(),
31 }
32 }
33}
34
35impl TryFrom<MintUrl> for cdk::mint_url::MintUrl {
36 type Error = FfiError;
37
38 fn try_from(mint_url: MintUrl) -> Result<Self, Self::Error> {
39 cdk::mint_url::MintUrl::from_str(&mint_url.url)
40 .map_err(|e| FfiError::internal(format!("Invalid URL: {}", e)))
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
46pub struct MintVersion {
47 pub name: String,
49 pub version: String,
51}
52
53impl From<cdk::nuts::MintVersion> for MintVersion {
54 fn from(version: cdk::nuts::MintVersion) -> Self {
55 Self {
56 name: version.name,
57 version: version.version,
58 }
59 }
60}
61
62impl From<MintVersion> for cdk::nuts::MintVersion {
63 fn from(version: MintVersion) -> Self {
64 Self {
65 name: version.name,
66 version: version.version,
67 }
68 }
69}
70
71impl MintVersion {
72 pub fn to_json(&self) -> Result<String, FfiError> {
74 Ok(serde_json::to_string(self)?)
75 }
76}
77
78#[uniffi::export]
80pub fn decode_mint_version(json: String) -> Result<MintVersion, FfiError> {
81 Ok(serde_json::from_str(&json)?)
82}
83
84#[uniffi::export]
86pub fn encode_mint_version(version: MintVersion) -> Result<String, FfiError> {
87 Ok(serde_json::to_string(&version)?)
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
92pub struct ContactInfo {
93 pub method: String,
95 pub info: String,
97}
98
99impl From<cdk::nuts::ContactInfo> for ContactInfo {
100 fn from(contact: cdk::nuts::ContactInfo) -> Self {
101 Self {
102 method: contact.method,
103 info: contact.info,
104 }
105 }
106}
107
108impl From<ContactInfo> for cdk::nuts::ContactInfo {
109 fn from(contact: ContactInfo) -> Self {
110 Self {
111 method: contact.method,
112 info: contact.info,
113 }
114 }
115}
116
117impl ContactInfo {
118 pub fn to_json(&self) -> Result<String, FfiError> {
120 Ok(serde_json::to_string(self)?)
121 }
122}
123
124#[uniffi::export]
126pub fn decode_contact_info(json: String) -> Result<ContactInfo, FfiError> {
127 Ok(serde_json::from_str(&json)?)
128}
129
130#[uniffi::export]
132pub fn encode_contact_info(info: ContactInfo) -> Result<String, FfiError> {
133 Ok(serde_json::to_string(&info)?)
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
138#[serde(transparent)]
139pub struct SupportedSettings {
140 pub supported: bool,
142}
143
144impl From<cdk::nuts::nut06::SupportedSettings> for SupportedSettings {
145 fn from(settings: cdk::nuts::nut06::SupportedSettings) -> Self {
146 Self {
147 supported: settings.supported,
148 }
149 }
150}
151
152impl From<SupportedSettings> for cdk::nuts::nut06::SupportedSettings {
153 fn from(settings: SupportedSettings) -> Self {
154 Self {
155 supported: settings.supported,
156 }
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
166pub struct MintMethodSettings {
167 pub method: PaymentMethod,
168 pub unit: CurrencyUnit,
169 pub min_amount: Option<Amount>,
170 pub max_amount: Option<Amount>,
171 pub description: Option<bool>,
173}
174
175impl From<cdk::nuts::nut04::MintMethodSettings> for MintMethodSettings {
176 fn from(s: cdk::nuts::nut04::MintMethodSettings) -> Self {
177 let description = match s.options {
178 Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description }) => Some(description),
179 _ => None,
180 };
181 Self {
182 method: s.method.into(),
183 unit: s.unit.into(),
184 min_amount: s.min_amount.map(Into::into),
185 max_amount: s.max_amount.map(Into::into),
186 description,
187 }
188 }
189}
190
191impl TryFrom<MintMethodSettings> for cdk::nuts::nut04::MintMethodSettings {
192 type Error = FfiError;
193
194 fn try_from(s: MintMethodSettings) -> Result<Self, Self::Error> {
195 let options = match s.method {
196 PaymentMethod::Bolt11 => s
197 .description
198 .map(|description| cdk::nuts::nut04::MintMethodOptions::Bolt11 { description }),
199 PaymentMethod::Custom { .. } => Some(cdk::nuts::nut04::MintMethodOptions::Custom {}),
200 _ => None,
201 };
202 Ok(Self {
203 method: s.method.into(),
204 unit: s.unit.into(),
205 min_amount: s.min_amount.map(Into::into),
206 max_amount: s.max_amount.map(Into::into),
207 options,
208 })
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
214pub struct Nut04Settings {
215 pub methods: Vec<MintMethodSettings>,
216 pub disabled: bool,
217}
218
219impl From<cdk::nuts::nut04::Settings> for Nut04Settings {
220 fn from(s: cdk::nuts::nut04::Settings) -> Self {
221 Self {
222 methods: s.methods.into_iter().map(Into::into).collect(),
223 disabled: s.disabled,
224 }
225 }
226}
227
228impl TryFrom<Nut04Settings> for cdk::nuts::nut04::Settings {
229 type Error = FfiError;
230
231 fn try_from(s: Nut04Settings) -> Result<Self, Self::Error> {
232 Ok(Self {
233 methods: s
234 .methods
235 .into_iter()
236 .map(TryInto::try_into)
237 .collect::<Result<_, _>>()?,
238 disabled: s.disabled,
239 })
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
245pub struct MeltMethodSettings {
246 pub method: PaymentMethod,
247 pub unit: CurrencyUnit,
248 pub min_amount: Option<Amount>,
249 pub max_amount: Option<Amount>,
250 pub amountless: Option<bool>,
252}
253
254impl From<cdk::nuts::nut05::MeltMethodSettings> for MeltMethodSettings {
255 fn from(s: cdk::nuts::nut05::MeltMethodSettings) -> Self {
256 let amountless = match s.options {
257 Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless }) => Some(amountless),
258 _ => None,
259 };
260 Self {
261 method: s.method.into(),
262 unit: s.unit.into(),
263 min_amount: s.min_amount.map(Into::into),
264 max_amount: s.max_amount.map(Into::into),
265 amountless,
266 }
267 }
268}
269
270impl TryFrom<MeltMethodSettings> for cdk::nuts::nut05::MeltMethodSettings {
271 type Error = FfiError;
272
273 fn try_from(s: MeltMethodSettings) -> Result<Self, Self::Error> {
274 let options = match s.method {
275 PaymentMethod::Bolt11 => s
276 .amountless
277 .map(|amountless| cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless }),
278 _ => None,
279 };
280 Ok(Self {
281 method: s.method.into(),
282 unit: s.unit.into(),
283 min_amount: s.min_amount.map(Into::into),
284 max_amount: s.max_amount.map(Into::into),
285 options,
286 })
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
292pub struct Nut05Settings {
293 pub methods: Vec<MeltMethodSettings>,
294 pub disabled: bool,
295}
296
297impl From<cdk::nuts::nut05::Settings> for Nut05Settings {
298 fn from(s: cdk::nuts::nut05::Settings) -> Self {
299 Self {
300 methods: s.methods.into_iter().map(Into::into).collect(),
301 disabled: s.disabled,
302 }
303 }
304}
305
306impl TryFrom<Nut05Settings> for cdk::nuts::nut05::Settings {
307 type Error = FfiError;
308
309 fn try_from(s: Nut05Settings) -> Result<Self, Self::Error> {
310 Ok(Self {
311 methods: s
312 .methods
313 .into_iter()
314 .map(TryInto::try_into)
315 .collect::<Result<_, _>>()?,
316 disabled: s.disabled,
317 })
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
323pub struct ProtectedEndpoint {
324 pub method: String,
326 pub path: String,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
332pub struct ClearAuthSettings {
333 pub openid_discovery: String,
335 pub client_id: String,
337 pub protected_endpoints: Vec<ProtectedEndpoint>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
343pub struct BlindAuthSettings {
344 pub bat_max_mint: u64,
346 pub protected_endpoints: Vec<ProtectedEndpoint>,
348}
349
350impl From<cdk::nuts::ClearAuthSettings> for ClearAuthSettings {
351 fn from(settings: cdk::nuts::ClearAuthSettings) -> Self {
352 Self {
353 openid_discovery: settings.openid_discovery,
354 client_id: settings.client_id,
355 protected_endpoints: settings
356 .protected_endpoints
357 .into_iter()
358 .map(Into::into)
359 .collect(),
360 }
361 }
362}
363
364impl TryFrom<ClearAuthSettings> for cdk::nuts::ClearAuthSettings {
365 type Error = FfiError;
366
367 fn try_from(settings: ClearAuthSettings) -> Result<Self, Self::Error> {
368 Ok(Self {
369 openid_discovery: settings.openid_discovery,
370 client_id: settings.client_id,
371 protected_endpoints: settings
372 .protected_endpoints
373 .into_iter()
374 .map(|e| e.try_into())
375 .collect::<Result<Vec<_>, _>>()?,
376 })
377 }
378}
379
380impl From<cdk::nuts::BlindAuthSettings> for BlindAuthSettings {
381 fn from(settings: cdk::nuts::BlindAuthSettings) -> Self {
382 Self {
383 bat_max_mint: settings.bat_max_mint,
384 protected_endpoints: settings
385 .protected_endpoints
386 .into_iter()
387 .map(Into::into)
388 .collect(),
389 }
390 }
391}
392
393impl TryFrom<BlindAuthSettings> for cdk::nuts::BlindAuthSettings {
394 type Error = FfiError;
395
396 fn try_from(settings: BlindAuthSettings) -> Result<Self, Self::Error> {
397 Ok(Self {
398 bat_max_mint: settings.bat_max_mint,
399 protected_endpoints: settings
400 .protected_endpoints
401 .into_iter()
402 .map(|e| e.try_into())
403 .collect::<Result<Vec<_>, _>>()?,
404 })
405 }
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record, Default)]
410pub struct Nut29Settings {
411 pub max_batch_size: Option<u64>,
413 pub methods: Option<Vec<String>>,
415}
416
417impl From<cdk::nuts::nut29::Settings> for Nut29Settings {
418 fn from(settings: cdk::nuts::nut29::Settings) -> Self {
419 Self {
420 max_batch_size: settings.max_batch_size,
421 methods: settings.methods,
422 }
423 }
424}
425
426impl From<Nut29Settings> for cdk::nuts::nut29::Settings {
427 fn from(settings: Nut29Settings) -> Self {
428 Self {
429 max_batch_size: settings.max_batch_size,
430 methods: settings.methods,
431 }
432 }
433}
434
435impl From<cdk::nuts::ProtectedEndpoint> for ProtectedEndpoint {
436 fn from(endpoint: cdk::nuts::ProtectedEndpoint) -> Self {
437 Self {
438 method: match endpoint.method {
439 cdk::nuts::Method::Get => "GET".to_string(),
440 cdk::nuts::Method::Post => "POST".to_string(),
441 },
442 path: endpoint.path.to_string(),
443 }
444 }
445}
446
447impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
448 type Error = FfiError;
449
450 fn try_from(endpoint: ProtectedEndpoint) -> Result<Self, Self::Error> {
451 let method = match endpoint.method.as_str() {
452 "GET" => cdk::nuts::Method::Get,
453 "POST" => cdk::nuts::Method::Post,
454 _ => {
455 return Err(FfiError::internal(format!(
456 "Invalid HTTP method: {}. Only GET and POST are supported",
457 endpoint.method
458 )))
459 }
460 };
461
462 let route_path = endpoint
463 .path
464 .parse()
465 .map_err(|err| FfiError::internal(format!("Unknown route path: {err}")))?;
466
467 Ok(cdk::nuts::ProtectedEndpoint::new(method, route_path))
468 }
469}
470
471#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
473pub struct Nuts {
474 pub nut04: Nut04Settings,
476 pub nut05: Nut05Settings,
478 pub nut07_supported: bool,
480 pub nut08_supported: bool,
482 pub nut09_supported: bool,
484 pub nut10_supported: bool,
486 pub nut11_supported: bool,
488 pub nut12_supported: bool,
490 pub nut14_supported: bool,
492 pub nut20_supported: bool,
494 pub nut21: Option<ClearAuthSettings>,
496 pub nut22: Option<BlindAuthSettings>,
498 pub nut29: Nut29Settings,
500 pub mint_units: Vec<CurrencyUnit>,
502 pub melt_units: Vec<CurrencyUnit>,
504}
505
506impl From<cdk::nuts::Nuts> for Nuts {
507 fn from(nuts: cdk::nuts::Nuts) -> Self {
508 let mint_units = nuts
509 .supported_mint_units()
510 .into_iter()
511 .map(|u| u.clone().into())
512 .collect();
513 let melt_units = nuts
514 .supported_melt_units()
515 .into_iter()
516 .map(|u| u.clone().into())
517 .collect();
518
519 Self {
520 nut04: nuts.nut04.clone().into(),
521 nut05: nuts.nut05.clone().into(),
522 nut07_supported: nuts.nut07.supported,
523 nut08_supported: nuts.nut08.supported,
524 nut09_supported: nuts.nut09.supported,
525 nut10_supported: nuts.nut10.supported,
526 nut11_supported: nuts.nut11.supported,
527 nut12_supported: nuts.nut12.supported,
528 nut14_supported: nuts.nut14.supported,
529 nut20_supported: nuts.nut20.supported,
530 nut21: nuts.nut21.map(Into::into),
531 nut22: nuts.nut22.map(Into::into),
532 nut29: nuts.nut29.into(),
533 mint_units,
534 melt_units,
535 }
536 }
537}
538
539impl TryFrom<Nuts> for cdk::nuts::Nuts {
540 type Error = FfiError;
541
542 fn try_from(n: Nuts) -> Result<Self, Self::Error> {
543 Ok(Self {
544 nut04: n.nut04.try_into()?,
545 nut05: n.nut05.try_into()?,
546 nut07: cdk::nuts::nut06::SupportedSettings {
547 supported: n.nut07_supported,
548 },
549 nut08: cdk::nuts::nut06::SupportedSettings {
550 supported: n.nut08_supported,
551 },
552 nut09: cdk::nuts::nut06::SupportedSettings {
553 supported: n.nut09_supported,
554 },
555 nut10: cdk::nuts::nut06::SupportedSettings {
556 supported: n.nut10_supported,
557 },
558 nut11: cdk::nuts::nut06::SupportedSettings {
559 supported: n.nut11_supported,
560 },
561 nut12: cdk::nuts::nut06::SupportedSettings {
562 supported: n.nut12_supported,
563 },
564 nut14: cdk::nuts::nut06::SupportedSettings {
565 supported: n.nut14_supported,
566 },
567 nut15: Default::default(),
568 nut17: Default::default(),
569 nut19: Default::default(),
570 nut20: cdk::nuts::nut06::SupportedSettings {
571 supported: n.nut20_supported,
572 },
573 nut21: n.nut21.map(|s| s.try_into()).transpose()?,
574 nut22: n.nut22.map(|s| s.try_into()).transpose()?,
575 nut29: n.nut29.into(),
576 })
577 }
578}
579
580impl Nuts {
581 pub fn to_json(&self) -> Result<String, FfiError> {
583 Ok(serde_json::to_string(self)?)
584 }
585}
586
587#[uniffi::export]
589pub fn decode_nuts(json: String) -> Result<Nuts, FfiError> {
590 Ok(serde_json::from_str(&json)?)
591}
592
593#[uniffi::export]
595pub fn encode_nuts(nuts: Nuts) -> Result<String, FfiError> {
596 Ok(serde_json::to_string(&nuts)?)
597}
598
599#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
601pub struct MintInfo {
602 pub name: Option<String>,
604 pub pubkey: Option<String>,
606 pub version: Option<MintVersion>,
608 pub description: Option<String>,
610 pub description_long: Option<String>,
612 pub contact: Option<Vec<ContactInfo>>,
614 pub nuts: Nuts,
616 pub icon_url: Option<String>,
618 pub urls: Option<Vec<String>>,
620 pub motd: Option<String>,
622 pub time: Option<u64>,
624 pub tos_url: Option<String>,
626}
627
628impl From<cdk::nuts::MintInfo> for MintInfo {
629 fn from(info: cdk::nuts::MintInfo) -> Self {
630 Self {
631 name: info.name,
632 pubkey: info.pubkey.map(|p| p.to_string()),
633 version: info.version.map(Into::into),
634 description: info.description,
635 description_long: info.description_long,
636 contact: info
637 .contact
638 .map(|contacts| contacts.into_iter().map(Into::into).collect()),
639 nuts: info.nuts.into(),
640 icon_url: info.icon_url,
641 urls: info.urls,
642 motd: info.motd,
643 time: info.time,
644 tos_url: info.tos_url,
645 }
646 }
647}
648
649impl TryFrom<MintInfo> for cdk::nuts::MintInfo {
650 type Error = FfiError;
651
652 fn try_from(info: MintInfo) -> Result<Self, Self::Error> {
653 Ok(Self {
654 name: info.name,
655 pubkey: info.pubkey.and_then(|p| p.parse().ok()),
656 version: info.version.map(Into::into),
657 description: info.description,
658 description_long: info.description_long,
659 contact: info
660 .contact
661 .map(|contacts| contacts.into_iter().map(Into::into).collect()),
662 nuts: info.nuts.try_into()?,
663 icon_url: info.icon_url,
664 urls: info.urls,
665 motd: info.motd,
666 time: info.time,
667 tos_url: info.tos_url,
668 })
669 }
670}
671
672impl MintInfo {
673 pub fn to_json(&self) -> Result<String, FfiError> {
675 Ok(serde_json::to_string(self)?)
676 }
677}
678
679#[uniffi::export]
681pub fn decode_mint_info(json: String) -> Result<MintInfo, FfiError> {
682 Ok(serde_json::from_str(&json)?)
683}
684
685#[uniffi::export]
687pub fn encode_mint_info(info: MintInfo) -> Result<String, FfiError> {
688 Ok(serde_json::to_string(&info)?)
689}
690#[cfg(test)]
691mod tests {
692 use cdk::nuts::nut00::{KnownMethod, PaymentMethod as NutPaymentMethod};
693
694 use super::*;
695
696 fn create_sample_cdk_nuts() -> cdk::nuts::Nuts {
698 cdk::nuts::Nuts {
699 nut04: cdk::nuts::nut04::Settings {
700 methods: vec![cdk::nuts::nut04::MintMethodSettings {
701 method: cdk::nuts::PaymentMethod::Known(KnownMethod::Bolt11),
702 unit: cdk::nuts::CurrencyUnit::Sat,
703 min_amount: Some(cdk::Amount::from(1)),
704 max_amount: Some(cdk::Amount::from(100000)),
705 options: Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 {
706 description: true,
707 }),
708 }],
709 disabled: false,
710 },
711 nut05: cdk::nuts::nut05::Settings {
712 methods: vec![cdk::nuts::nut05::MeltMethodSettings {
713 method: cdk::nuts::PaymentMethod::Known(KnownMethod::Bolt11),
714 unit: cdk::nuts::CurrencyUnit::Sat,
715 min_amount: Some(cdk::Amount::from(1)),
716 max_amount: Some(cdk::Amount::from(100000)),
717 options: Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless: true }),
718 }],
719 disabled: false,
720 },
721 nut07: cdk::nuts::nut06::SupportedSettings { supported: true },
722 nut08: cdk::nuts::nut06::SupportedSettings { supported: true },
723 nut09: cdk::nuts::nut06::SupportedSettings { supported: false },
724 nut10: cdk::nuts::nut06::SupportedSettings { supported: true },
725 nut11: cdk::nuts::nut06::SupportedSettings { supported: true },
726 nut12: cdk::nuts::nut06::SupportedSettings { supported: true },
727 nut14: cdk::nuts::nut06::SupportedSettings { supported: false },
728 nut15: Default::default(),
729 nut17: Default::default(),
730 nut19: Default::default(),
731 nut20: cdk::nuts::nut06::SupportedSettings { supported: true },
732 nut21: Some(cdk::nuts::ClearAuthSettings {
733 openid_discovery: "https://example.com/.well-known/openid-configuration"
734 .to_string(),
735 client_id: "test-client".to_string(),
736 protected_endpoints: vec![cdk::nuts::ProtectedEndpoint::new(
737 cdk::nuts::Method::Post,
738 cdk::nuts::RoutePath::Swap,
739 )],
740 }),
741 nut22: Some(cdk::nuts::BlindAuthSettings {
742 bat_max_mint: 100,
743 protected_endpoints: vec![cdk::nuts::ProtectedEndpoint::new(
744 cdk::nuts::Method::Post,
745 cdk::nuts::RoutePath::Mint(
746 NutPaymentMethod::Known(KnownMethod::Bolt11).to_string(),
747 ),
748 )],
749 }),
750 nut29: Default::default(),
751 }
752 }
753
754 #[test]
755 fn test_nuts_from_cdk_to_ffi() {
756 let cdk_nuts = create_sample_cdk_nuts();
757 let ffi_nuts: Nuts = cdk_nuts.clone().into();
758
759 assert!(!ffi_nuts.nut04.disabled);
761 assert_eq!(ffi_nuts.nut04.methods.len(), 1);
762 assert_eq!(ffi_nuts.nut04.methods[0].description, Some(true));
763
764 assert!(!ffi_nuts.nut05.disabled);
766 assert_eq!(ffi_nuts.nut05.methods.len(), 1);
767 assert_eq!(ffi_nuts.nut05.methods[0].amountless, Some(true));
768
769 assert!(ffi_nuts.nut07_supported);
771 assert!(ffi_nuts.nut08_supported);
772 assert!(!ffi_nuts.nut09_supported);
773 assert!(ffi_nuts.nut10_supported);
774 assert!(ffi_nuts.nut11_supported);
775 assert!(ffi_nuts.nut12_supported);
776 assert!(!ffi_nuts.nut14_supported);
777 assert!(ffi_nuts.nut20_supported);
778
779 assert!(ffi_nuts.nut21.is_some());
781 let nut21 = ffi_nuts.nut21.as_ref().unwrap();
782 assert_eq!(
783 nut21.openid_discovery,
784 "https://example.com/.well-known/openid-configuration"
785 );
786 assert_eq!(nut21.client_id, "test-client");
787 assert_eq!(nut21.protected_endpoints.len(), 1);
788
789 assert!(ffi_nuts.nut22.is_some());
790 let nut22 = ffi_nuts.nut22.as_ref().unwrap();
791 assert_eq!(nut22.bat_max_mint, 100);
792 assert_eq!(nut22.protected_endpoints.len(), 1);
793
794 assert!(!ffi_nuts.mint_units.is_empty());
796 assert!(!ffi_nuts.melt_units.is_empty());
797 }
798
799 #[test]
800 fn test_nuts_round_trip_conversion() {
801 let original_cdk_nuts = create_sample_cdk_nuts();
802
803 let ffi_nuts: Nuts = original_cdk_nuts.clone().into();
805 let converted_back: cdk::nuts::Nuts = ffi_nuts.try_into().unwrap();
806
807 assert_eq!(
809 original_cdk_nuts.nut07.supported,
810 converted_back.nut07.supported
811 );
812 assert_eq!(
813 original_cdk_nuts.nut08.supported,
814 converted_back.nut08.supported
815 );
816 assert_eq!(
817 original_cdk_nuts.nut09.supported,
818 converted_back.nut09.supported
819 );
820 assert_eq!(
821 original_cdk_nuts.nut10.supported,
822 converted_back.nut10.supported
823 );
824 assert_eq!(
825 original_cdk_nuts.nut11.supported,
826 converted_back.nut11.supported
827 );
828 assert_eq!(
829 original_cdk_nuts.nut12.supported,
830 converted_back.nut12.supported
831 );
832 assert_eq!(
833 original_cdk_nuts.nut14.supported,
834 converted_back.nut14.supported
835 );
836 assert_eq!(
837 original_cdk_nuts.nut20.supported,
838 converted_back.nut20.supported
839 );
840
841 assert_eq!(
843 original_cdk_nuts.nut04.disabled,
844 converted_back.nut04.disabled
845 );
846 assert_eq!(
847 original_cdk_nuts.nut04.methods.len(),
848 converted_back.nut04.methods.len()
849 );
850
851 assert_eq!(
853 original_cdk_nuts.nut05.disabled,
854 converted_back.nut05.disabled
855 );
856 assert_eq!(
857 original_cdk_nuts.nut05.methods.len(),
858 converted_back.nut05.methods.len()
859 );
860
861 assert_eq!(
863 original_cdk_nuts.nut21.is_some(),
864 converted_back.nut21.is_some()
865 );
866 assert_eq!(
867 original_cdk_nuts.nut22.is_some(),
868 converted_back.nut22.is_some()
869 );
870 }
871
872 #[test]
873 fn test_nuts_without_auth() {
874 let cdk_nuts = cdk::nuts::Nuts {
875 nut04: Default::default(),
876 nut05: Default::default(),
877 nut07: cdk::nuts::nut06::SupportedSettings { supported: true },
878 nut08: cdk::nuts::nut06::SupportedSettings { supported: false },
879 nut09: cdk::nuts::nut06::SupportedSettings { supported: false },
880 nut10: cdk::nuts::nut06::SupportedSettings { supported: false },
881 nut11: cdk::nuts::nut06::SupportedSettings { supported: false },
882 nut12: cdk::nuts::nut06::SupportedSettings { supported: false },
883 nut14: cdk::nuts::nut06::SupportedSettings { supported: false },
884 nut15: Default::default(),
885 nut17: Default::default(),
886 nut19: Default::default(),
887 nut20: cdk::nuts::nut06::SupportedSettings { supported: false },
888 nut21: None,
889 nut22: None,
890 nut29: Default::default(),
891 };
892
893 let ffi_nuts: Nuts = cdk_nuts.into();
894
895 assert!(ffi_nuts.nut21.is_none());
896 assert!(ffi_nuts.nut22.is_none());
897 assert!(ffi_nuts.nut07_supported);
898 assert!(!ffi_nuts.nut08_supported);
899 }
900
901 #[test]
902 fn test_ffi_nuts_to_cdk_with_defaults() {
903 let ffi_nuts = Nuts {
904 nut04: Nut04Settings {
905 methods: vec![],
906 disabled: true,
907 },
908 nut05: Nut05Settings {
909 methods: vec![],
910 disabled: true,
911 },
912 nut07_supported: false,
913 nut08_supported: false,
914 nut09_supported: false,
915 nut10_supported: false,
916 nut11_supported: false,
917 nut12_supported: false,
918 nut14_supported: false,
919 nut20_supported: false,
920 nut21: None,
921 nut22: None,
922 nut29: Default::default(),
923 mint_units: vec![],
924 melt_units: vec![],
925 };
926
927 let cdk_nuts: Result<cdk::nuts::Nuts, _> = ffi_nuts.try_into();
928 assert!(cdk_nuts.is_ok());
929
930 let cdk_nuts = cdk_nuts.unwrap();
931 assert!(!cdk_nuts.nut07.supported);
932 assert!(!cdk_nuts.nut08.supported);
933 assert!(cdk_nuts.nut21.is_none());
934 assert!(cdk_nuts.nut22.is_none());
935
936 assert_eq!(cdk_nuts.nut17.supported.len(), 0);
938 }
939
940 #[test]
941 fn test_nuts_serialization() {
942 let cdk_nuts = create_sample_cdk_nuts();
943 let ffi_nuts: Nuts = cdk_nuts.into();
944
945 let json = ffi_nuts.to_json();
947 assert!(json.is_ok());
948
949 let json_str = json.unwrap();
950 assert!(json_str.contains("nut04"));
951 assert!(json_str.contains("nut05"));
952
953 let decoded: Result<Nuts, _> = serde_json::from_str(&json_str);
955 assert!(decoded.is_ok());
956
957 let decoded_nuts = decoded.unwrap();
958 assert_eq!(decoded_nuts.nut07_supported, ffi_nuts.nut07_supported);
959 assert_eq!(decoded_nuts.nut08_supported, ffi_nuts.nut08_supported);
960 }
961
962 #[test]
963 fn test_nuts_multiple_units() {
964 let mut cdk_nuts = create_sample_cdk_nuts();
965
966 cdk_nuts
968 .nut04
969 .methods
970 .push(cdk::nuts::nut04::MintMethodSettings {
971 method: cdk::nuts::PaymentMethod::Known(KnownMethod::Bolt11),
972 unit: cdk::nuts::CurrencyUnit::Msat,
973 min_amount: Some(cdk::Amount::from(1)),
974 max_amount: Some(cdk::Amount::from(100000)),
975 options: None,
976 });
977
978 cdk_nuts
979 .nut05
980 .methods
981 .push(cdk::nuts::nut05::MeltMethodSettings {
982 method: cdk::nuts::PaymentMethod::Known(KnownMethod::Bolt11),
983 unit: cdk::nuts::CurrencyUnit::Usd,
984 min_amount: None,
985 max_amount: None,
986 options: None,
987 });
988
989 let ffi_nuts: Nuts = cdk_nuts.into();
990
991 assert!(!ffi_nuts.mint_units.is_empty());
993 assert!(!ffi_nuts.melt_units.is_empty());
994 }
995
996 #[test]
997 fn test_protected_endpoint_conversion() {
998 let cdk_endpoint =
999 cdk::nuts::ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Swap);
1000
1001 let ffi_endpoint: ProtectedEndpoint = cdk_endpoint.into();
1002
1003 assert_eq!(ffi_endpoint.method, "POST");
1004 assert_eq!(ffi_endpoint.path, "/v1/swap");
1005
1006 let converted_back: Result<cdk::nuts::ProtectedEndpoint, _> = ffi_endpoint.try_into();
1008 assert!(converted_back.is_ok());
1009 }
1010
1011 #[test]
1012 fn test_invalid_protected_endpoint_method() {
1013 let invalid_endpoint = ProtectedEndpoint {
1014 method: "INVALID".to_string(),
1015 path: "/v1/swap".to_string(),
1016 };
1017
1018 let result: Result<cdk::nuts::ProtectedEndpoint, _> = invalid_endpoint.try_into();
1019 assert!(result.is_err());
1020 }
1021
1022 #[test]
1023 fn test_invalid_protected_endpoint_path() {
1024 let invalid_endpoint = ProtectedEndpoint {
1025 method: "POST".to_string(),
1026 path: "/invalid/path".to_string(),
1027 };
1028
1029 let result: Result<cdk::nuts::ProtectedEndpoint, _> = invalid_endpoint.try_into();
1030 assert!(result.is_err());
1031 }
1032
1033 #[test]
1034 fn test_protected_endpoint_custom_payment_method_path() {
1035 let endpoint = ProtectedEndpoint {
1036 method: "POST".to_string(),
1037 path: "/v1/mint/custom-method".to_string(),
1038 };
1039
1040 let converted: cdk::nuts::ProtectedEndpoint = endpoint
1041 .try_into()
1042 .expect("custom payment method routes should be accepted");
1043 assert_eq!(
1044 converted.path,
1045 cdk::nuts::RoutePath::Mint("custom-method".to_string())
1046 );
1047 }
1048
1049 #[test]
1050 fn test_mint_info_unknown_endpoint_returns_error() {
1051 let ffi_mint_info = MintInfo {
1052 name: None,
1053 pubkey: None,
1054 version: None,
1055 description: None,
1056 description_long: None,
1057 contact: None,
1058 nuts: Nuts {
1059 nut04: Nut04Settings {
1060 methods: vec![],
1061 disabled: false,
1062 },
1063 nut05: Nut05Settings {
1064 methods: vec![],
1065 disabled: false,
1066 },
1067 nut07_supported: true,
1068 nut08_supported: false,
1069 nut09_supported: false,
1070 nut10_supported: false,
1071 nut11_supported: false,
1072 nut12_supported: true,
1073 nut14_supported: false,
1074 nut20_supported: false,
1075 nut21: None,
1076 nut22: Some(BlindAuthSettings {
1077 bat_max_mint: 10,
1078 protected_endpoints: vec![ProtectedEndpoint {
1079 method: "POST".to_string(),
1080 path: "/v1/unknown-custom-endpoint".to_string(),
1081 }],
1082 }),
1083 nut29: Nut29Settings::default(),
1084 mint_units: vec![],
1085 melt_units: vec![],
1086 },
1087 icon_url: None,
1088 urls: None,
1089 motd: None,
1090 time: None,
1091 tos_url: None,
1092 };
1093
1094 let result = cdk::nuts::MintInfo::try_from(ffi_mint_info);
1095 assert!(result.is_err());
1096 }
1097}