1use 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
15pub struct MintVersion {
16 pub name: String,
18 pub version: String,
20}
21
22impl MintVersion {
23 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#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
65pub struct MintInfo {
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub name: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub pubkey: Option<PublicKey>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub version: Option<MintVersion>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub description: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub description_long: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub contact: Option<Vec<ContactInfo>>,
84 pub nuts: Nuts,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub icon_url: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub urls: Option<Vec<String>>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub motd: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub time: Option<u64>,
98}
99
100impl MintInfo {
101 pub fn new() -> Self {
103 Self::default()
104 }
105
106 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 pub fn pubkey(self, pubkey: PublicKey) -> Self {
119 Self {
120 pubkey: Some(pubkey),
121 ..self
122 }
123 }
124
125 pub fn version(self, mint_version: MintVersion) -> Self {
127 Self {
128 version: Some(mint_version),
129 ..self
130 }
131 }
132
133 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 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 pub fn contact_info(self, contact_info: Vec<ContactInfo>) -> Self {
157 Self {
158 contact: Some(contact_info),
159 ..self
160 }
161 }
162
163 pub fn nuts(self, nuts: Nuts) -> Self {
165 Self { nuts, ..self }
166 }
167
168 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 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 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#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
204#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
205pub struct Nuts {
206 #[serde(default)]
208 #[serde(rename = "4")]
209 pub nut04: nut04::Settings,
210 #[serde(default)]
212 #[serde(rename = "5")]
213 pub nut05: nut05::Settings,
214 #[serde(default)]
216 #[serde(rename = "7")]
217 pub nut07: SupportedSettings,
218 #[serde(default)]
220 #[serde(rename = "8")]
221 pub nut08: SupportedSettings,
222 #[serde(default)]
224 #[serde(rename = "9")]
225 pub nut09: SupportedSettings,
226 #[serde(rename = "10")]
228 #[serde(default)]
229 pub nut10: SupportedSettings,
230 #[serde(rename = "11")]
232 #[serde(default)]
233 pub nut11: SupportedSettings,
234 #[serde(default)]
236 #[serde(rename = "12")]
237 pub nut12: SupportedSettings,
238 #[serde(default)]
240 #[serde(rename = "14")]
241 pub nut14: SupportedSettings,
242 #[serde(default)]
244 #[serde(rename = "15")]
245 pub nut15: nut15::Settings,
246 #[serde(default)]
248 #[serde(rename = "17")]
249 pub nut17: super::nut17::SupportedSettings,
250 #[serde(default)]
252 #[serde(rename = "19")]
253 pub nut19: nut19::Settings,
254 #[serde(default)]
256 #[serde(rename = "20")]
257 pub nut20: SupportedSettings,
258}
259
260impl Nuts {
261 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn nut04(self, nut04_settings: nut04::Settings) -> Self {
268 Self {
269 nut04: nut04_settings,
270 ..self
271 }
272 }
273
274 pub fn nut05(self, nut05_settings: nut05::Settings) -> Self {
276 Self {
277 nut05: nut05_settings,
278 ..self
279 }
280 }
281
282 pub fn nut07(self, supported: bool) -> Self {
284 Self {
285 nut07: SupportedSettings { supported },
286 ..self
287 }
288 }
289
290 pub fn nut08(self, supported: bool) -> Self {
292 Self {
293 nut08: SupportedSettings { supported },
294 ..self
295 }
296 }
297
298 pub fn nut09(self, supported: bool) -> Self {
300 Self {
301 nut09: SupportedSettings { supported },
302 ..self
303 }
304 }
305
306 pub fn nut10(self, supported: bool) -> Self {
308 Self {
309 nut10: SupportedSettings { supported },
310 ..self
311 }
312 }
313
314 pub fn nut11(self, supported: bool) -> Self {
316 Self {
317 nut11: SupportedSettings { supported },
318 ..self
319 }
320 }
321
322 pub fn nut12(self, supported: bool) -> Self {
324 Self {
325 nut12: SupportedSettings { supported },
326 ..self
327 }
328 }
329
330 pub fn nut14(self, supported: bool) -> Self {
332 Self {
333 nut14: SupportedSettings { supported },
334 ..self
335 }
336 }
337
338 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 pub fn nut17(self, supported: Vec<SupportedMethods>) -> Self {
350 Self {
351 nut17: super::nut17::SupportedSettings { supported },
352 ..self
353 }
354 }
355
356 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 pub fn nut20(self, supported: bool) -> Self {
369 Self {
370 nut20: SupportedSettings { supported },
371 ..self
372 }
373 }
374}
375
376#[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#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
385#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
386pub struct ContactInfo {
387 pub method: String,
389 pub info: String,
391}
392
393impl ContactInfo {
394 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 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}