cashu/nuts/
nut06.rs

1//! NUT-06: Mint Information
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/06.md>
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use super::nut01::PublicKey;
8use super::nut17::SupportedMethods;
9use super::nut19::CachedEndpoint;
10use super::{nut04, nut05, nut15, nut19, MppMethodSettings};
11
12/// Mint Version
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
15pub struct MintVersion {
16    /// Mint Software name
17    pub name: String,
18    /// Mint Version
19    pub version: String,
20}
21
22impl MintVersion {
23    /// Create new [`MintVersion`]
24    pub fn new(name: String, version: String) -> Self {
25        Self { name, version }
26    }
27}
28
29impl std::fmt::Display for MintVersion {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(f, "{}/{}", self.name, self.version)
32    }
33}
34
35impl Serialize for MintVersion {
36    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
37    where
38        S: Serializer,
39    {
40        let combined = format!("{}/{}", self.name, self.version);
41        serializer.serialize_str(&combined)
42    }
43}
44
45impl<'de> Deserialize<'de> for MintVersion {
46    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
47    where
48        D: Deserializer<'de>,
49    {
50        let combined = String::deserialize(deserializer)?;
51        let parts: Vec<&str> = combined.split('/').collect();
52        if parts.len() != 2 {
53            return Err(serde::de::Error::custom("Invalid input string"));
54        }
55        Ok(MintVersion {
56            name: parts[0].to_string(),
57            version: parts[1].to_string(),
58        })
59    }
60}
61
62/// Mint Info [NIP-06]
63#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
65pub struct MintInfo {
66    /// name of the mint and should be recognizable
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub name: Option<String>,
69    /// hex pubkey of the mint
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub pubkey: Option<PublicKey>,
72    /// implementation name and the version running
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub version: Option<MintVersion>,
75    /// short description of the mint
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub description: Option<String>,
78    /// long description
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub description_long: Option<String>,
81    /// Contact info
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub contact: Option<Vec<ContactInfo>>,
84    /// shows which NUTs the mint supports
85    pub nuts: Nuts,
86    /// Mint's icon URL
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub icon_url: Option<String>,
89    /// Mint's endpoint URLs
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub urls: Option<Vec<String>>,
92    /// message of the day that the wallet must display to the user
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub motd: Option<String>,
95    /// server unix timestamp
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub time: Option<u64>,
98}
99
100impl MintInfo {
101    /// Create new [`MintInfo`]
102    pub fn new() -> Self {
103        Self::default()
104    }
105
106    /// Set name
107    pub fn name<S>(self, name: S) -> Self
108    where
109        S: Into<String>,
110    {
111        Self {
112            name: Some(name.into()),
113            ..self
114        }
115    }
116
117    /// Set pubkey
118    pub fn pubkey(self, pubkey: PublicKey) -> Self {
119        Self {
120            pubkey: Some(pubkey),
121            ..self
122        }
123    }
124
125    /// Set [`MintVersion`]
126    pub fn version(self, mint_version: MintVersion) -> Self {
127        Self {
128            version: Some(mint_version),
129            ..self
130        }
131    }
132
133    /// Set description
134    pub fn description<S>(self, description: S) -> Self
135    where
136        S: Into<String>,
137    {
138        Self {
139            description: Some(description.into()),
140            ..self
141        }
142    }
143
144    /// Set long description
145    pub fn long_description<S>(self, description_long: S) -> Self
146    where
147        S: Into<String>,
148    {
149        Self {
150            description_long: Some(description_long.into()),
151            ..self
152        }
153    }
154
155    /// Set contact info
156    pub fn contact_info(self, contact_info: Vec<ContactInfo>) -> Self {
157        Self {
158            contact: Some(contact_info),
159            ..self
160        }
161    }
162
163    /// Set nuts
164    pub fn nuts(self, nuts: Nuts) -> Self {
165        Self { nuts, ..self }
166    }
167
168    /// Set mint icon url
169    pub fn icon_url<S>(self, icon_url: S) -> Self
170    where
171        S: Into<String>,
172    {
173        Self {
174            icon_url: Some(icon_url.into()),
175            ..self
176        }
177    }
178
179    /// Set motd
180    pub fn motd<S>(self, motd: S) -> Self
181    where
182        S: Into<String>,
183    {
184        Self {
185            motd: Some(motd.into()),
186            ..self
187        }
188    }
189
190    /// Set time
191    pub fn time<S>(self, time: S) -> Self
192    where
193        S: Into<u64>,
194    {
195        Self {
196            time: Some(time.into()),
197            ..self
198        }
199    }
200}
201
202/// Supported nuts and settings
203#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
204#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
205pub struct Nuts {
206    /// NUT04 Settings
207    #[serde(default)]
208    #[serde(rename = "4")]
209    pub nut04: nut04::Settings,
210    /// NUT05 Settings
211    #[serde(default)]
212    #[serde(rename = "5")]
213    pub nut05: nut05::Settings,
214    /// NUT07 Settings
215    #[serde(default)]
216    #[serde(rename = "7")]
217    pub nut07: SupportedSettings,
218    /// NUT08 Settings
219    #[serde(default)]
220    #[serde(rename = "8")]
221    pub nut08: SupportedSettings,
222    /// NUT09 Settings
223    #[serde(default)]
224    #[serde(rename = "9")]
225    pub nut09: SupportedSettings,
226    /// NUT10 Settings
227    #[serde(rename = "10")]
228    #[serde(default)]
229    pub nut10: SupportedSettings,
230    /// NUT11 Settings
231    #[serde(rename = "11")]
232    #[serde(default)]
233    pub nut11: SupportedSettings,
234    /// NUT12 Settings
235    #[serde(default)]
236    #[serde(rename = "12")]
237    pub nut12: SupportedSettings,
238    /// NUT14 Settings
239    #[serde(default)]
240    #[serde(rename = "14")]
241    pub nut14: SupportedSettings,
242    /// NUT15 Settings
243    #[serde(default)]
244    #[serde(rename = "15")]
245    pub nut15: nut15::Settings,
246    /// NUT17 Settings
247    #[serde(default)]
248    #[serde(rename = "17")]
249    pub nut17: super::nut17::SupportedSettings,
250    /// NUT19 Settings
251    #[serde(default)]
252    #[serde(rename = "19")]
253    pub nut19: nut19::Settings,
254    /// NUT20 Settings
255    #[serde(default)]
256    #[serde(rename = "20")]
257    pub nut20: SupportedSettings,
258}
259
260impl Nuts {
261    /// Create new [`Nuts`]
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    /// Nut04 settings
267    pub fn nut04(self, nut04_settings: nut04::Settings) -> Self {
268        Self {
269            nut04: nut04_settings,
270            ..self
271        }
272    }
273
274    /// Nut05 settings
275    pub fn nut05(self, nut05_settings: nut05::Settings) -> Self {
276        Self {
277            nut05: nut05_settings,
278            ..self
279        }
280    }
281
282    /// Nut07 settings
283    pub fn nut07(self, supported: bool) -> Self {
284        Self {
285            nut07: SupportedSettings { supported },
286            ..self
287        }
288    }
289
290    /// Nut08 settings
291    pub fn nut08(self, supported: bool) -> Self {
292        Self {
293            nut08: SupportedSettings { supported },
294            ..self
295        }
296    }
297
298    /// Nut09 settings
299    pub fn nut09(self, supported: bool) -> Self {
300        Self {
301            nut09: SupportedSettings { supported },
302            ..self
303        }
304    }
305
306    /// Nut10 settings
307    pub fn nut10(self, supported: bool) -> Self {
308        Self {
309            nut10: SupportedSettings { supported },
310            ..self
311        }
312    }
313
314    /// Nut11 settings
315    pub fn nut11(self, supported: bool) -> Self {
316        Self {
317            nut11: SupportedSettings { supported },
318            ..self
319        }
320    }
321
322    /// Nut12 settings
323    pub fn nut12(self, supported: bool) -> Self {
324        Self {
325            nut12: SupportedSettings { supported },
326            ..self
327        }
328    }
329
330    /// Nut14 settings
331    pub fn nut14(self, supported: bool) -> Self {
332        Self {
333            nut14: SupportedSettings { supported },
334            ..self
335        }
336    }
337
338    /// Nut15 settings
339    pub fn nut15(self, mpp_settings: Vec<MppMethodSettings>) -> Self {
340        Self {
341            nut15: nut15::Settings {
342                methods: mpp_settings,
343            },
344            ..self
345        }
346    }
347
348    /// Nut17 settings
349    pub fn nut17(self, supported: Vec<SupportedMethods>) -> Self {
350        Self {
351            nut17: super::nut17::SupportedSettings { supported },
352            ..self
353        }
354    }
355
356    /// Nut19 settings
357    pub fn nut19(self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
358        Self {
359            nut19: nut19::Settings {
360                ttl,
361                cached_endpoints,
362            },
363            ..self
364        }
365    }
366
367    /// Nut20 settings
368    pub fn nut20(self, supported: bool) -> Self {
369        Self {
370            nut20: SupportedSettings { supported },
371            ..self
372        }
373    }
374}
375
376/// Check state Settings
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
378#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
379pub struct SupportedSettings {
380    supported: bool,
381}
382
383/// Contact Info
384#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
385#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
386pub struct ContactInfo {
387    /// Contact Method i.e. nostr
388    pub method: String,
389    /// Contact info i.e. npub...
390    pub info: String,
391}
392
393impl ContactInfo {
394    /// Create new [`ContactInfo`]
395    pub fn new(method: String, info: String) -> Self {
396        Self { method, info }
397    }
398}
399
400#[cfg(test)]
401mod tests {
402
403    use super::*;
404
405    #[test]
406    fn test_des_mint_into() {
407        let mint_info_str = r#"{
408    "name": "Cashu mint",
409    "pubkey": "0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679",
410    "version": "Nutshell/0.15.3",
411    "contact": [
412        ["", ""],
413        ["", ""]
414    ],
415    "nuts": {
416        "4": {
417            "methods": [
418                {"method": "bolt11", "unit": "sat", "description": true},
419                {"method": "bolt11", "unit": "usd", "description": true}
420            ],
421            "disabled": false
422        },
423        "5": {
424            "methods": [
425                {"method": "bolt11", "unit": "sat"},
426                {"method": "bolt11", "unit": "usd"}
427            ],
428            "disabled": false
429        },
430        "7": {"supported": true},
431        "8": {"supported": true},
432        "9": {"supported": true},
433        "10": {"supported": true},
434        "11": {"supported": true}
435    }
436}"#;
437
438        let _mint_info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
439    }
440
441    #[test]
442    fn test_ser_mint_info() {
443        /*
444                let mint_info = serde_json::to_string(&MintInfo {
445                    name: Some("Cashu-crab".to_string()),
446                    pubkey: None,
447                    version: None,
448                    description: Some("A mint".to_string()),
449                    description_long: Some("Some longer test".to_string()),
450                    contact: None,
451                    nuts: Nuts::default(),
452                    motd: None,
453                })
454                .unwrap();
455
456                println!("{}", mint_info);
457        */
458        let mint_info_str = r#"{
459  "name": "Bob's Cashu mint",
460  "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
461  "version": "Nutshell/0.15.0",
462  "description": "The short mint description",
463  "description_long": "A description that can be a long piece of text.",
464  "contact": [
465    {
466        "method": "nostr",
467        "info": "xxxxx"
468    },
469    {
470        "method": "email",
471        "info": "contact@me.com"
472    }
473  ],
474  "motd": "Message to display to users.",
475  "icon_url": "https://this-is-a-mint-icon-url.com/icon.png",
476  "nuts": {
477    "4": {
478      "methods": [
479        {
480        "method": "bolt11",
481        "unit": "sat",
482        "min_amount": 0,
483        "max_amount": 10000,
484        "description": true
485        }
486      ],
487      "disabled": false
488    },
489    "5": {
490      "methods": [
491        {
492        "method": "bolt11",
493        "unit": "sat",
494        "min_amount": 0,
495        "max_amount": 10000
496        }
497      ],
498      "disabled": false
499    },
500    "7": {"supported": true},
501    "8": {"supported": true},
502    "9": {"supported": true},
503    "10": {"supported": true},
504    "12": {"supported": true}
505  }
506}"#;
507        let info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
508        let mint_info_str = r#"{
509  "name": "Bob's Cashu mint",
510  "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
511  "version": "Nutshell/0.15.0",
512  "description": "The short mint description",
513  "description_long": "A description that can be a long piece of text.",
514  "contact": [
515        ["nostr", "xxxxx"],
516        ["email", "contact@me.com"]
517  ],
518  "motd": "Message to display to users.",
519  "icon_url": "https://this-is-a-mint-icon-url.com/icon.png",
520  "nuts": {
521    "4": {
522      "methods": [
523        {
524        "method": "bolt11",
525        "unit": "sat",
526        "min_amount": 0,
527        "max_amount": 10000,
528        "description": true
529        }
530      ],
531      "disabled": false
532    },
533    "5": {
534      "methods": [
535        {
536        "method": "bolt11",
537        "unit": "sat",
538        "min_amount": 0,
539        "max_amount": 10000
540        }
541      ],
542      "disabled": false
543    },
544    "7": {"supported": true},
545    "8": {"supported": true},
546    "9": {"supported": true},
547    "10": {"supported": true},
548    "12": {"supported": true}
549  }
550}"#;
551        let mint_info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
552
553        assert_eq!(info, mint_info);
554    }
555}