1use std::collections::{HashMap, HashSet};
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use super::nut01::PublicKey;
10use super::nut17::SupportedMethods;
11use super::nut19::CachedEndpoint;
12use super::{
13 nut04, nut05, nut15, nut19, nut29, AuthRequired, BlindAuthSettings, ClearAuthSettings,
14 MppMethodSettings, ProtectedEndpoint,
15};
16use crate::util::serde_helpers::deserialize_empty_string_as_none;
17use crate::CurrencyUnit;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct MintVersion {
22 pub name: String,
24 pub version: String,
26}
27
28impl MintVersion {
29 pub fn new(name: String, version: String) -> Self {
31 Self { name, version }
32 }
33}
34
35impl std::fmt::Display for MintVersion {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}/{}", self.name, self.version)
38 }
39}
40
41impl Serialize for MintVersion {
42 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43 where
44 S: Serializer,
45 {
46 let combined = format!("{}/{}", self.name, self.version);
47 serializer.serialize_str(&combined)
48 }
49}
50
51impl<'de> Deserialize<'de> for MintVersion {
52 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53 where
54 D: Deserializer<'de>,
55 {
56 let combined = String::deserialize(deserializer)?;
57 let parts: Vec<&str> = combined.split('/').collect();
58 if parts.len() != 2 {
59 return Err(serde::de::Error::custom("Invalid input string"));
60 }
61 Ok(MintVersion {
62 name: parts[0].to_string(),
63 version: parts[1].to_string(),
64 })
65 }
66}
67
68#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
70pub struct MintInfo {
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub name: Option<String>,
74 #[serde(
76 default,
77 skip_serializing_if = "Option::is_none",
78 deserialize_with = "deserialize_empty_string_as_none"
79 )]
80 pub pubkey: Option<PublicKey>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub version: Option<MintVersion>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub description: Option<String>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub description_long: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub contact: Option<Vec<ContactInfo>>,
93 pub nuts: Nuts,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub icon_url: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub urls: Option<Vec<String>>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub motd: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub time: Option<u64>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub tos_url: Option<String>,
110}
111
112impl MintInfo {
113 pub fn new() -> Self {
115 Self::default()
116 }
117
118 pub fn name<S>(self, name: S) -> Self
120 where
121 S: Into<String>,
122 {
123 Self {
124 name: Some(name.into()),
125 ..self
126 }
127 }
128
129 pub fn pubkey(self, pubkey: PublicKey) -> Self {
131 Self {
132 pubkey: Some(pubkey),
133 ..self
134 }
135 }
136
137 pub fn version(self, mint_version: MintVersion) -> Self {
139 Self {
140 version: Some(mint_version),
141 ..self
142 }
143 }
144
145 pub fn description<S>(self, description: S) -> Self
147 where
148 S: Into<String>,
149 {
150 Self {
151 description: Some(description.into()),
152 ..self
153 }
154 }
155
156 pub fn long_description<S>(self, description_long: S) -> Self
158 where
159 S: Into<String>,
160 {
161 Self {
162 description_long: Some(description_long.into()),
163 ..self
164 }
165 }
166
167 pub fn contact_info(self, contact_info: Vec<ContactInfo>) -> Self {
169 Self {
170 contact: Some(contact_info),
171 ..self
172 }
173 }
174
175 pub fn nuts(self, nuts: Nuts) -> Self {
177 Self { nuts, ..self }
178 }
179
180 pub fn icon_url<S>(self, icon_url: S) -> Self
182 where
183 S: Into<String>,
184 {
185 Self {
186 icon_url: Some(icon_url.into()),
187 ..self
188 }
189 }
190
191 pub fn motd<S>(self, motd: S) -> Self
193 where
194 S: Into<String>,
195 {
196 Self {
197 motd: Some(motd.into()),
198 ..self
199 }
200 }
201
202 pub fn time<S>(self, time: S) -> Self
204 where
205 S: Into<u64>,
206 {
207 Self {
208 time: Some(time.into()),
209 ..self
210 }
211 }
212
213 pub fn tos_url<S>(self, tos_url: S) -> Self
215 where
216 S: Into<String>,
217 {
218 Self {
219 tos_url: Some(tos_url.into()),
220 ..self
221 }
222 }
223
224 pub fn protected_endpoints(&self) -> HashMap<ProtectedEndpoint, AuthRequired> {
226 let mut protected_endpoints = HashMap::new();
227
228 if let Some(nut21_settings) = &self.nuts.nut21 {
229 for endpoint in nut21_settings.protected_endpoints.iter() {
230 protected_endpoints.insert(endpoint.clone(), AuthRequired::Clear);
231 }
232 }
233
234 if let Some(nut22_settings) = &self.nuts.nut22 {
235 for endpoint in nut22_settings.protected_endpoints.iter() {
236 protected_endpoints.insert(endpoint.clone(), AuthRequired::Blind);
237 }
238 }
239 protected_endpoints
240 }
241
242 pub fn openid_discovery(&self) -> Option<String> {
244 self.nuts
245 .nut21
246 .as_ref()
247 .map(|s| s.openid_discovery.to_string())
248 }
249
250 pub fn client_id(&self) -> Option<String> {
252 self.nuts.nut21.as_ref().map(|s| s.client_id.clone())
253 }
254
255 pub fn bat_max_mint(&self) -> Option<u64> {
257 self.nuts.nut22.as_ref().map(|s| s.bat_max_mint)
258 }
259
260 pub fn supported_units(&self) -> Vec<&CurrencyUnit> {
262 let mut units = HashSet::new();
263
264 units.extend(self.nuts.supported_mint_units());
265 units.extend(self.nuts.supported_melt_units());
266
267 units.into_iter().collect()
268 }
269}
270
271#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
273pub struct Nuts {
274 #[serde(default)]
276 #[serde(rename = "4")]
277 pub nut04: nut04::Settings,
278 #[serde(default)]
280 #[serde(rename = "5")]
281 pub nut05: nut05::Settings,
282 #[serde(default)]
284 #[serde(rename = "7")]
285 pub nut07: SupportedSettings,
286 #[serde(default)]
288 #[serde(rename = "8")]
289 pub nut08: SupportedSettings,
290 #[serde(default)]
292 #[serde(rename = "9")]
293 pub nut09: SupportedSettings,
294 #[serde(rename = "10")]
296 #[serde(default)]
297 pub nut10: SupportedSettings,
298 #[serde(rename = "11")]
300 #[serde(default)]
301 pub nut11: SupportedSettings,
302 #[serde(default)]
304 #[serde(rename = "12")]
305 pub nut12: SupportedSettings,
306 #[serde(default)]
308 #[serde(rename = "14")]
309 pub nut14: SupportedSettings,
310 #[serde(default)]
312 #[serde(rename = "15")]
313 #[serde(skip_serializing_if = "nut15::Settings::is_empty")]
314 pub nut15: nut15::Settings,
315 #[serde(default)]
317 #[serde(rename = "17")]
318 pub nut17: super::nut17::SupportedSettings,
319 #[serde(default)]
321 #[serde(rename = "19")]
322 pub nut19: nut19::Settings,
323 #[serde(default)]
325 #[serde(rename = "20")]
326 pub nut20: SupportedSettings,
327 #[serde(rename = "21")]
329 #[serde(skip_serializing_if = "Option::is_none")]
330 pub nut21: Option<ClearAuthSettings>,
331 #[serde(rename = "22")]
333 #[serde(skip_serializing_if = "Option::is_none")]
334 pub nut22: Option<BlindAuthSettings>,
335 #[serde(default)]
337 #[serde(rename = "29")]
338 #[serde(skip_serializing_if = "nut29::Settings::is_empty")]
339 pub nut29: nut29::Settings,
340}
341
342impl Nuts {
343 pub fn new() -> Self {
345 Self::default()
346 }
347
348 pub fn nut04(self, nut04_settings: nut04::Settings) -> Self {
350 Self {
351 nut04: nut04_settings,
352 ..self
353 }
354 }
355
356 pub fn nut05(self, nut05_settings: nut05::Settings) -> Self {
358 Self {
359 nut05: nut05_settings,
360 ..self
361 }
362 }
363
364 pub fn nut07(self, supported: bool) -> Self {
366 Self {
367 nut07: SupportedSettings { supported },
368 ..self
369 }
370 }
371
372 pub fn nut08(self, supported: bool) -> Self {
374 Self {
375 nut08: SupportedSettings { supported },
376 ..self
377 }
378 }
379
380 pub fn nut09(self, supported: bool) -> Self {
382 Self {
383 nut09: SupportedSettings { supported },
384 ..self
385 }
386 }
387
388 pub fn nut10(self, supported: bool) -> Self {
390 Self {
391 nut10: SupportedSettings { supported },
392 ..self
393 }
394 }
395
396 pub fn nut11(self, supported: bool) -> Self {
398 Self {
399 nut11: SupportedSettings { supported },
400 ..self
401 }
402 }
403
404 pub fn nut12(self, supported: bool) -> Self {
406 Self {
407 nut12: SupportedSettings { supported },
408 ..self
409 }
410 }
411
412 pub fn nut14(self, supported: bool) -> Self {
414 Self {
415 nut14: SupportedSettings { supported },
416 ..self
417 }
418 }
419
420 pub fn nut15(self, mpp_settings: Vec<MppMethodSettings>) -> Self {
422 Self {
423 nut15: nut15::Settings {
424 methods: mpp_settings,
425 },
426 ..self
427 }
428 }
429
430 pub fn nut17(self, supported: Vec<SupportedMethods>) -> Self {
432 Self {
433 nut17: super::nut17::SupportedSettings { supported },
434 ..self
435 }
436 }
437
438 pub fn nut19(self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
440 Self {
441 nut19: nut19::Settings {
442 ttl,
443 cached_endpoints,
444 },
445 ..self
446 }
447 }
448
449 pub fn nut20(self, supported: bool) -> Self {
451 Self {
452 nut20: SupportedSettings { supported },
453 ..self
454 }
455 }
456
457 pub fn nut29(self, settings: nut29::Settings) -> Self {
459 Self {
460 nut29: settings,
461 ..self
462 }
463 }
464
465 pub fn supported_mint_units(&self) -> Vec<&CurrencyUnit> {
467 self.nut04
468 .methods
469 .iter()
470 .map(|s| &s.unit)
471 .collect::<HashSet<_>>()
472 .into_iter()
473 .collect()
474 }
475
476 pub fn supported_melt_units(&self) -> Vec<&CurrencyUnit> {
478 self.nut05
479 .methods
480 .iter()
481 .map(|s| &s.unit)
482 .collect::<HashSet<_>>()
483 .into_iter()
484 .collect()
485 }
486}
487
488#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
490pub struct SupportedSettings {
491 pub supported: bool,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
497pub struct ContactInfo {
498 pub method: String,
500 pub info: String,
502}
503
504impl ContactInfo {
505 pub fn new(method: String, info: String) -> Self {
507 Self { method, info }
508 }
509}
510
511#[cfg(test)]
512mod tests {
513
514 use super::*;
515 use crate::nut00::KnownMethod;
516 use crate::nut04::MintMethodOptions;
517 use crate::{Amount, Method, PaymentMethod, RoutePath};
518
519 #[test]
520 fn test_des_mint_into() {
521 let mint_info_str = r#"{
522"name": "Cashu mint",
523"pubkey": "0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679",
524"version": "Nutshell/0.15.3",
525"contact": [
526 ["", ""],
527 ["", ""]
528 ],
529 "nuts": {
530 "4": {
531 "methods": [
532 {"method": "bolt11", "unit": "sat", "description": true},
533 {"method": "bolt11", "unit": "usd", "description": true}
534 ],
535 "disabled": false
536 },
537 "5": {
538 "methods": [
539 {"method": "bolt11", "unit": "sat"},
540 {"method": "bolt11", "unit": "usd"}
541 ],
542 "disabled": false
543 },
544 "7": {"supported": true},
545 "8": {"supported": true},
546 "9": {"supported": true},
547 "10": {"supported": true},
548 "11": {"supported": true}
549 },
550"tos_url": "https://cashu.mint/tos"
551}"#;
552
553 let _mint_info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
554 }
555
556 #[test]
557 fn test_ser_mint_info() {
558 let mint_info_str = r#"
574{
575 "name": "Bob's Cashu mint",
576 "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
577 "version": "Nutshell/0.15.0",
578 "description": "The short mint description",
579 "description_long": "A description that can be a long piece of text.",
580 "contact": [
581 {
582 "method": "nostr",
583 "info": "xxxxx"
584 },
585 {
586 "method": "email",
587 "info": "contact@me.com"
588 }
589 ],
590 "motd": "Message to display to users.",
591 "icon_url": "https://this-is-a-mint-icon-url.com/icon.png",
592 "nuts": {
593 "4": {
594 "methods": [
595 {
596 "method": "bolt11",
597 "unit": "sat",
598 "min_amount": 0,
599 "max_amount": 10000,
600 "options": {
601 "description": true
602 }
603 }
604 ],
605 "disabled": false
606 },
607 "5": {
608 "methods": [
609 {
610 "method": "bolt11",
611 "unit": "sat",
612 "min_amount": 0,
613 "max_amount": 10000
614 }
615 ],
616 "disabled": false
617 },
618 "7": {"supported": true},
619 "8": {"supported": true},
620 "9": {"supported": true},
621 "10": {"supported": true},
622 "12": {"supported": true}
623 },
624 "tos_url": "https://cashu.mint/tos"
625}"#;
626 let info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
627 let mint_info_str = r#"
628{
629 "name": "Bob's Cashu mint",
630 "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
631 "version": "Nutshell/0.15.0",
632 "description": "The short mint description",
633 "description_long": "A description that can be a long piece of text.",
634 "contact": [
635 ["nostr", "xxxxx"],
636 ["email", "contact@me.com"]
637 ],
638 "motd": "Message to display to users.",
639 "icon_url": "https://this-is-a-mint-icon-url.com/icon.png",
640 "nuts": {
641 "4": {
642 "methods": [
643 {
644 "method": "bolt11",
645 "unit": "sat",
646 "min_amount": 0,
647 "max_amount": 10000,
648 "options": {
649 "description": true
650 }
651 }
652 ],
653 "disabled": false
654 },
655 "5": {
656 "methods": [
657 {
658 "method": "bolt11",
659 "unit": "sat",
660 "min_amount": 0,
661 "max_amount": 10000
662 }
663 ],
664 "disabled": false
665 },
666 "7": {"supported": true},
667 "8": {"supported": true},
668 "9": {"supported": true},
669 "10": {"supported": true},
670 "12": {"supported": true}
671 },
672 "tos_url": "https://cashu.mint/tos"
673}"#;
674 let mint_info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
675
676 let t = mint_info
677 .nuts
678 .nut04
679 .get_settings(
680 &crate::CurrencyUnit::Sat,
681 &crate::PaymentMethod::Known(KnownMethod::Bolt11),
682 )
683 .unwrap();
684
685 let t = t.options.unwrap();
686
687 matches!(t, MintMethodOptions::Bolt11 { description: true });
688
689 assert_eq!(info, mint_info);
690 }
691
692 #[test]
693 fn test_nut15_not_serialized_when_empty() {
694 let mint_info = MintInfo {
696 name: Some("Test Mint".to_string()),
697 nuts: Nuts::default(),
698 ..Default::default()
699 };
700
701 let json = serde_json::to_string(&mint_info).unwrap();
702 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
703
704 assert!(parsed["nuts"]["15"].is_null());
706
707 let mint_info_with_nut15 = MintInfo {
709 name: Some("Test Mint".to_string()),
710 nuts: Nuts::default().nut15(vec![MppMethodSettings {
711 method: crate::PaymentMethod::Known(KnownMethod::Bolt11),
712 unit: crate::CurrencyUnit::Sat,
713 }]),
714 ..Default::default()
715 };
716
717 let json = serde_json::to_string(&mint_info_with_nut15).unwrap();
718 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
719
720 assert!(!parsed["nuts"]["15"].is_null());
722 assert!(parsed["nuts"]["15"]["methods"].is_array());
723 assert_eq!(parsed["nuts"]["15"]["methods"].as_array().unwrap().len(), 1);
724 }
725
726 #[test]
727 fn mint_version_display_uses_name_and_version() {
728 let version = MintVersion::new("cdk".to_string(), "1.2.3".to_string());
729
730 assert_eq!(version.to_string(), "cdk/1.2.3");
731 }
732
733 #[test]
734 fn mint_info_builder_preserves_all_fields() {
735 let pubkey = PublicKey::from_hex(
736 "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
737 )
738 .unwrap();
739 let contact = vec![ContactInfo::new(
740 "email".to_string(),
741 "mint@example.com".to_string(),
742 )];
743 let nuts = Nuts::new().nut07(true).nut08(true);
744
745 let info = MintInfo::new()
746 .name("Test mint")
747 .pubkey(pubkey)
748 .version(MintVersion::new("cdk".to_string(), "1.2.3".to_string()))
749 .description("short")
750 .long_description("long")
751 .contact_info(contact.clone())
752 .nuts(nuts.clone())
753 .icon_url("https://example.com/icon.png")
754 .motd("hello")
755 .time(123_u64)
756 .tos_url("https://example.com/tos");
757
758 assert_eq!(info.name.as_deref(), Some("Test mint"));
759 assert_eq!(info.pubkey, Some(pubkey));
760 assert_eq!(
761 info.version.as_ref().map(ToString::to_string).as_deref(),
762 Some("cdk/1.2.3")
763 );
764 assert_eq!(info.description.as_deref(), Some("short"));
765 assert_eq!(info.description_long.as_deref(), Some("long"));
766 assert_eq!(info.contact, Some(contact));
767 assert_eq!(info.nuts, nuts);
768 assert_eq!(
769 info.icon_url.as_deref(),
770 Some("https://example.com/icon.png")
771 );
772 assert_eq!(info.motd.as_deref(), Some("hello"));
773 assert_eq!(info.time, Some(123));
774 assert_eq!(info.tos_url.as_deref(), Some("https://example.com/tos"));
775 }
776
777 #[test]
778 fn mint_info_auth_helpers_return_configured_values() {
779 let clear_endpoint = ProtectedEndpoint::new(Method::Get, RoutePath::Swap);
780 let blind_endpoint = ProtectedEndpoint::new(
781 Method::Post,
782 RoutePath::Mint(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
783 );
784 let info = MintInfo {
785 nuts: Nuts {
786 nut21: Some(ClearAuthSettings::new(
787 "https://issuer.example/.well-known/openid-configuration".to_string(),
788 "wallet-client".to_string(),
789 vec![clear_endpoint.clone()],
790 )),
791 nut22: Some(BlindAuthSettings::new(42, vec![blind_endpoint.clone()])),
792 ..Default::default()
793 },
794 ..Default::default()
795 };
796
797 let protected = info.protected_endpoints();
798
799 assert_eq!(
800 info.openid_discovery().as_deref(),
801 Some("https://issuer.example/.well-known/openid-configuration")
802 );
803 assert_eq!(info.client_id().as_deref(), Some("wallet-client"));
804 assert_eq!(info.bat_max_mint(), Some(42));
805 assert_eq!(protected.get(&clear_endpoint), Some(&AuthRequired::Clear));
806 assert_eq!(protected.get(&blind_endpoint), Some(&AuthRequired::Blind));
807 }
808
809 #[test]
810 fn nuts_builder_preserves_capabilities_and_supported_units() {
811 let bolt11 = PaymentMethod::Known(KnownMethod::Bolt11);
812 let bolt12 = PaymentMethod::Known(KnownMethod::Bolt12);
813 let mint_settings = nut04::Settings::new(
814 vec![nut04::MintMethodSettings {
815 method: bolt11.clone(),
816 unit: CurrencyUnit::Eur,
817 method_name: Some("Lightning".to_string()),
818 min_amount: Some(Amount::from(1)),
819 max_amount: Some(Amount::from(10)),
820 options: Some(MintMethodOptions::Bolt11 { description: true }),
821 }],
822 false,
823 );
824 let melt_settings = nut05::Settings::new(
825 vec![nut05::MeltMethodSettings {
826 method: bolt12,
827 unit: CurrencyUnit::Usd,
828 method_name: None,
829 min_amount: Some(Amount::from(2)),
830 max_amount: Some(Amount::from(20)),
831 options: None,
832 }],
833 false,
834 );
835 let mpp = vec![MppMethodSettings {
836 method: bolt11.clone(),
837 unit: CurrencyUnit::Sat,
838 }];
839 let supported_ws = vec![SupportedMethods::default_bolt11(CurrencyUnit::Sat)];
840 let cached = vec![CachedEndpoint::new(nut19::Method::Get, nut19::Path::Swap)];
841 let nut29 = nut29::Settings::new(Some(3), Some(vec!["bolt11".to_string()]));
842
843 let nuts = Nuts::new()
844 .nut04(mint_settings.clone())
845 .nut05(melt_settings.clone())
846 .nut07(true)
847 .nut08(true)
848 .nut09(true)
849 .nut10(true)
850 .nut11(true)
851 .nut12(true)
852 .nut14(true)
853 .nut15(mpp.clone())
854 .nut17(supported_ws.clone())
855 .nut19(Some(60), cached.clone())
856 .nut20(true)
857 .nut29(nut29.clone());
858
859 assert_eq!(nuts.nut04, mint_settings);
860 assert_eq!(nuts.nut05, melt_settings);
861 assert!(nuts.nut07.supported);
862 assert!(nuts.nut08.supported);
863 assert!(nuts.nut09.supported);
864 assert!(nuts.nut10.supported);
865 assert!(nuts.nut11.supported);
866 assert!(nuts.nut12.supported);
867 assert!(nuts.nut14.supported);
868 assert_eq!(nuts.nut15.methods, mpp);
869 assert_eq!(nuts.nut17.supported, supported_ws);
870 assert_eq!(nuts.nut19.ttl, Some(60));
871 assert_eq!(nuts.nut19.cached_endpoints, cached);
872 assert!(nuts.nut20.supported);
873 assert_eq!(nuts.nut29, nut29);
874 assert!(nuts.supported_mint_units().contains(&&CurrencyUnit::Eur));
875 assert!(nuts.supported_melt_units().contains(&&CurrencyUnit::Usd));
876
877 let info = MintInfo {
878 nuts,
879 ..Default::default()
880 };
881 let supported_units = info.supported_units();
882 assert!(supported_units.contains(&&CurrencyUnit::Eur));
883 assert!(supported_units.contains(&&CurrencyUnit::Usd));
884 }
885}