1use std::{collections::HashMap, fmt::Display};
5
6use secp256k1::PublicKey;
7use serde::{Deserialize, Serialize};
8use serde_with::skip_serializing_none;
9use utoipa::ToSchema;
10use uuid::Uuid;
11
12use crate::{
13 blind::{BlindedMessage, BlindedSignature},
14 proof::Proofs,
15};
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct PaymentRequest {
19 pub pr: String,
20 pub hash: String, }
22
23#[derive(Clone, Debug, Serialize, Deserialize, Default)]
24pub struct PostMintResponse {
25 pub promises: Vec<BlindedSignature>,
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
29pub struct PostMintRequest {
30 pub outputs: Vec<BlindedMessage>,
31}
32
33#[derive(Clone, Debug, Serialize, Deserialize)]
34pub struct CheckFeesRequest {
35 pub pr: String,
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize)]
39pub struct CheckFeesResponse {
40 pub fee: u64,
42}
43
44#[derive(Clone, Debug, Serialize, Deserialize)]
45pub struct PostMeltRequest {
46 pub proofs: Proofs,
47 pub pr: String,
48 pub outputs: Vec<BlindedMessage>,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize, Default)]
52pub struct PostMeltResponse {
53 pub paid: bool,
54 pub preimage: String,
55 pub change: Vec<BlindedSignature>,
56}
57
58#[derive(Clone, Debug, Serialize, Deserialize)]
59pub struct PostSplitRequest {
60 pub proofs: Proofs,
61 pub outputs: Vec<BlindedMessage>,
62}
63
64#[derive(Clone, Debug, Serialize, Deserialize, Default)]
65pub struct PostSplitResponse {
66 pub promises: Vec<BlindedSignature>,
67}
68
69impl PostSplitResponse {
70 pub fn with_promises(promises: Vec<BlindedSignature>) -> Self {
71 Self { promises }
72 }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
76pub struct PostSwapRequest {
77 pub inputs: Proofs,
78 pub outputs: Vec<BlindedMessage>,
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize, Default, ToSchema)]
82pub struct PostSwapResponse {
83 pub signatures: Vec<BlindedSignature>,
84}
85
86#[derive(Deserialize, Debug)]
87pub struct CashuErrorResponse {
88 pub code: u64,
89 pub detail: String,
90}
91
92#[skip_serializing_none]
93#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
94pub struct MintLegacyInfoResponse {
95 pub name: Option<String>,
96 pub pubkey: PublicKey,
97 pub version: Option<String>,
98 pub description: Option<String>,
99 pub description_long: Option<String>,
100 pub contact: Option<Vec<Vec<String>>>,
101 pub nuts: Vec<String>,
102 pub motd: Option<String>,
103 pub parameter: Parameter,
104}
105
106#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
107pub struct Parameter {
108 pub peg_out_only: bool,
109}
110
111#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
112pub struct KeysResponse {
113 pub keysets: Vec<KeyResponse>,
114}
115
116impl KeysResponse {
117 pub fn new(keyset: KeyResponse) -> Self {
118 Self {
119 keysets: vec![keyset],
120 }
121 }
122}
123
124#[derive(serde::Deserialize, Serialize, Clone, Debug, PartialEq, Eq, ToSchema)]
125pub struct KeyResponse {
126 pub id: String, pub unit: CurrencyUnit,
128 #[schema(value_type = HashMap<u64, String>)]
129 pub keys: HashMap<u64, PublicKey>,
130}
131
132#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, ToSchema, Hash)]
133#[serde(rename_all = "lowercase")]
134pub enum CurrencyUnit {
135 Sat,
136 Usd,
137}
138
139impl Display for CurrencyUnit {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 match self {
142 Self::Sat => write!(f, "sat"),
143 Self::Usd => write!(f, "usd"),
144 }
145 }
146}
147
148#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, ToSchema, Hash)]
149#[serde(rename_all = "lowercase")]
150pub enum PaymentMethod {
151 Bolt11,
152 BtcOnchain,
153}
154
155impl Display for PaymentMethod {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 match self {
158 Self::Bolt11 => write!(f, "Lightning"),
159 Self::BtcOnchain => write!(f, "Onchain"),
160 }
161 }
162}
163
164#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
165pub struct PostMintQuoteBolt11Request {
166 pub amount: u64,
167 pub unit: CurrencyUnit,
168}
169
170#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
171pub struct PostMintQuoteBolt11Response {
172 pub quote: String,
173 #[serde(rename = "request")]
174 pub payment_request: String,
175 pub paid: bool,
176 pub expiry: Option<u64>,
177}
178
179impl From<Bolt11MintQuote> for PostMintQuoteBolt11Response {
180 fn from(quote: Bolt11MintQuote) -> Self {
181 Self {
182 quote: quote.quote_id.to_string(),
183 payment_request: quote.payment_request,
184 paid: quote.paid,
185 expiry: Some(quote.expiry),
186 }
187 }
188}
189
190#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
191pub struct PostMintBolt11Request {
192 pub quote: String,
193 pub outputs: Vec<BlindedMessage>,
194}
195
196#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
197pub struct PostMintBolt11Response {
198 pub signatures: Vec<BlindedSignature>,
199}
200
201#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
202pub struct PostMeltQuoteBolt11Request {
203 pub request: String,
205 pub unit: CurrencyUnit,
206}
207
208#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
209pub struct PostMeltQuoteBolt11Response {
210 pub quote: String,
211 pub amount: u64,
212 pub fee_reserve: u64,
213 pub paid: bool,
214 pub expiry: Option<u64>,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
218pub struct Bolt11MintQuote {
219 pub quote_id: Uuid,
220 pub payment_request: String,
221 pub expiry: u64,
222 pub paid: bool,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
226pub struct Bolt11MeltQuote {
227 pub quote_id: Uuid,
228 pub amount: u64,
229 pub fee_reserve: u64,
230 pub payment_request: String,
231 pub expiry: u64,
232 pub paid: bool,
233}
234
235impl From<Bolt11MeltQuote> for PostMeltQuoteBolt11Response {
236 fn from(quote: Bolt11MeltQuote) -> Self {
237 Self {
238 quote: quote.quote_id.to_string(),
239 amount: quote.amount,
240 fee_reserve: quote.fee_reserve,
241 expiry: Some(quote.expiry),
242 paid: quote.paid,
243 }
244 }
245}
246
247#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
248pub struct PostMeltBolt11Request {
249 pub quote: String,
250 pub inputs: Proofs,
251 pub outputs: Vec<BlindedMessage>,
252}
253
254#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
255pub struct PostMeltBolt11Response {
256 pub paid: bool,
257 pub payment_preimage: Option<String>,
258 pub change: Vec<BlindedSignature>,
259}
260
261#[skip_serializing_none]
262#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
263pub struct MintInfoResponse {
264 pub name: Option<String>,
265 #[schema(value_type = String)]
266 pub pubkey: PublicKey,
267 pub version: Option<String>,
268 pub description: Option<String>,
269 pub description_long: Option<String>,
270 pub contact: Option<Vec<Vec<String>>>,
271 pub motd: Option<String>,
272 pub nuts: Nuts,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
276pub struct OnchainMintQuote {
277 pub quote_id: Uuid,
278 pub address: String,
279 pub unit: CurrencyUnit,
280 pub amount: u64,
281 pub expiry: u64,
282 pub paid: bool,
283}
284
285impl From<OnchainMintQuote> for PostMintQuoteOnchainResponse {
286 fn from(quote: OnchainMintQuote) -> Self {
287 Self {
288 quote: quote.quote_id.to_string(),
289 address: quote.address,
290 paid: quote.paid,
291 expiry: quote.expiry,
292 }
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
297pub struct OnchainMeltQuote {
298 pub quote_id: Uuid,
299 pub amount: u64,
300 pub address: String,
301 pub fee_total: u64,
302 pub fee_sat_per_vbyte: u32,
303 pub expiry: u64,
304 pub paid: bool,
305}
306
307#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
308pub struct PostMintQuoteOnchainRequest {
309 pub amount: u64,
310 pub unit: CurrencyUnit,
311}
312
313#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
314pub struct PostMintQuoteOnchainResponse {
315 pub quote: String,
316 pub address: String,
317 pub paid: bool,
318 pub expiry: u64,
319}
320
321#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
322pub struct PostMintOnchainRequest {
323 pub quote: String,
324 pub outputs: Vec<BlindedMessage>,
325}
326
327#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
328pub struct PostMintOnchainResponse {
329 pub signatures: Vec<BlindedSignature>,
330}
331
332#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
333pub struct PostMeltQuoteOnchainRequest {
334 pub amount: u64,
335 pub address: String,
337 pub unit: CurrencyUnit,
338}
339
340#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
341pub struct PostMeltQuoteOnchainResponse {
342 pub quote: String,
343 pub description: String,
344 pub amount: u64,
345 pub fee: u64,
346 pub paid: bool,
347 pub expiry: u64,
348}
349
350#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
351pub struct PostMeltOnchainRequest {
352 pub quote: String,
353 pub inputs: Proofs,
354}
355
356#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
357pub struct PostMeltOnchainResponse {
358 pub paid: bool,
359 pub txid: String,
360}
361
362impl From<(String, OnchainMeltQuote)> for PostMeltQuoteOnchainResponse {
363 fn from((description, quote): (String, OnchainMeltQuote)) -> Self {
364 Self {
365 description,
366 quote: quote.quote_id.to_string(),
367 amount: quote.amount,
368 fee: quote.fee_total,
369 expiry: quote.expiry,
370 paid: quote.paid,
371 }
372 }
373}
374
375#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
376pub struct GetMeltOnchainResponse {
377 pub paid: bool,
378}
379
380#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
381pub struct Nuts {
382 #[serde(rename = "4")]
384 pub nut4: Nut4,
385
386 #[serde(rename = "5")]
388 pub nut5: Nut5,
389
390 #[serde(rename = "7")]
392 pub nut7: Nut7,
393
394 #[serde(rename = "8")]
396 pub nut8: Nut8,
397
398 #[serde(rename = "9")]
400 pub nut9: Nut9,
401
402 #[serde(rename = "10")]
404 pub nut10: Nut10,
405
406 #[serde(rename = "11")]
408 pub nut11: Nut11,
409
410 #[serde(rename = "12")]
411 pub nut12: Nut12,
413
414 #[serde(rename = "14", skip_serializing_if = "Option::is_none")]
416 pub nut14: Option<Nut14>,
418
419 #[serde(rename = "15", skip_serializing_if = "Option::is_none")]
420 pub nut15: Option<Nut15>,
422}
423
424#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
425pub struct Nut4 {
426 pub methods: Vec<(PaymentMethod, CurrencyUnit)>,
427 pub disabled: bool,
428}
429
430impl Default for Nut4 {
431 fn default() -> Self {
432 Self {
433 methods: vec![(PaymentMethod::Bolt11, CurrencyUnit::Sat)],
434 disabled: false,
435 }
436 }
437}
438
439#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
440pub struct Nut5 {
441 pub methods: Vec<(PaymentMethod, CurrencyUnit)>,
442}
443
444impl Default for Nut5 {
445 fn default() -> Self {
446 Self {
447 methods: vec![(PaymentMethod::Bolt11, CurrencyUnit::Sat)],
448 }
449 }
450}
451
452#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
453pub struct Nut7 {
454 pub supported: bool,
455}
456
457#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
458pub struct Nut8 {
459 pub supported: bool,
460}
461
462impl Default for Nut8 {
463 fn default() -> Self {
464 Self { supported: true }
465 }
466}
467
468#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
469pub struct Nut9 {
470 pub supported: bool,
471}
472
473#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
474pub struct Nut10 {
475 pub supported: bool,
476}
477
478#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
479pub struct Nut11 {
480 pub supported: bool,
481}
482
483#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default, ToSchema)]
484pub struct Nut12 {
485 pub supported: bool,
486}
487
488#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
489pub struct Nut14 {
490 pub supported: bool,
491 #[serde(rename = "methods")]
492 pub payment_methods: Vec<PaymentMethodConfig>,
493}
494
495#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
496pub struct PaymentMethodConfig {
497 pub payment_method: PaymentMethod,
498 pub unit: CurrencyUnit,
499 pub min_amount: u64,
500 pub max_amount: u64,
501}
502
503impl Default for Nut14 {
504 fn default() -> Self {
505 Self {
506 supported: true,
507 payment_methods: vec![PaymentMethodConfig {
508 payment_method: PaymentMethod::BtcOnchain,
509 unit: CurrencyUnit::Sat,
510 min_amount: 1_000,
511 max_amount: 1_000_000,
512 }],
513 }
514 }
515}
516
517#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
518pub struct Nut15 {
519 pub supported: bool,
520 #[serde(rename = "methods")]
521 pub payment_methods: Vec<PaymentMethodConfig>,
522}
523
524impl Default for Nut15 {
525 fn default() -> Self {
526 Self {
527 supported: true,
528 payment_methods: vec![PaymentMethodConfig {
529 payment_method: PaymentMethod::BtcOnchain,
530 unit: CurrencyUnit::Sat,
531 min_amount: 1_000,
532 max_amount: 1_000_000,
533 }],
534 }
535 }
536}
537
538#[cfg(test)]
539mod tests {
540 use pretty_assertions::assert_eq;
541
542 use crate::{
543 dhke::public_key_from_hex,
544 fixture::read_fixture,
545 primitives::{
546 KeyResponse, MintInfoResponse, MintLegacyInfoResponse, Nuts, Parameter,
547 PostSwapResponse,
548 },
549 };
550
551 #[test]
552 fn test_serialize_empty_swap_response() -> anyhow::Result<()> {
553 let response = PostSwapResponse::default();
554 let serialized = serde_json::to_string(&response)?;
555 assert_eq!(serialized, "{\"signatures\":[]}");
556 Ok(())
557 }
558
559 #[test]
560 fn test_serialize_keyresponse() -> anyhow::Result<()> {
561 let response = KeyResponse {
562 id: "test".to_string(),
563 unit: crate::primitives::CurrencyUnit::Sat,
564 keys: std::collections::HashMap::new(),
565 };
566 let serialized = serde_json::to_string(&response)?;
567 assert_eq!(serialized, "{\"id\":\"test\",\"unit\":\"sat\",\"keys\":{}}");
568 Ok(())
569 }
570
571 #[test]
572 fn test_deserialize_legacy_mint_info() -> anyhow::Result<()> {
573 let mint_info = MintLegacyInfoResponse {
574 name: Some("Bob's Cashu mint".to_string()),
575 pubkey: public_key_from_hex(
576 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
577 ),
578 version: Some("Nutshell/0.11.0".to_string()),
579 description: Some("The short mint description".to_string()),
580 description_long: Some("A description that can be a long piece of text.".to_string()),
581 contact: Some(vec![
582 vec!["email".to_string(), "contact@me.com".to_string()],
583 vec!["twitter".to_string(), "@me".to_string()],
584 vec!["nostr".to_string(), "npub...".to_string()],
585 ]),
586 nuts: vec![
587 "NUT-07".to_string(),
588 "NUT-08".to_string(),
589 "NUT-08".to_string(),
590 ],
591 motd: Some("Message to display to users.".to_string()),
592 parameter: Parameter {
593 peg_out_only: false,
594 },
595 };
596 let out = serde_json::to_string_pretty(&mint_info)?;
597 println!("{}", out);
598 assert!(!out.is_empty());
599
600 Ok(())
601 }
602
603 #[test]
604 fn test_deserialize_mint_info() -> anyhow::Result<()> {
605 let mint_info = MintInfoResponse {
606 name: Some("Bob's Cashu mint".to_string()),
607 pubkey: public_key_from_hex(
608 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
609 ),
610 version: Some("Nutshell/0.11.0".to_string()),
611 description: Some("The short mint description".to_string()),
612 description_long: Some("A description that can be a long piece of text.".to_string()),
613 contact: Some(vec![
614 vec!["email".to_string(), "contact@me.com".to_string()],
615 vec!["twitter".to_string(), "@me".to_string()],
616 vec!["nostr".to_string(), "npub...".to_string()],
617 ]),
618 nuts: Nuts::default(),
619 motd: Some("Message to display to users.".to_string()),
620 };
621 let out = serde_json::to_string_pretty(&mint_info)?;
622 println!("{}", out);
623 assert!(!out.is_empty());
624 Ok(())
627 }
628
629 #[test]
630 fn test_deserialize_nustash_mint_info() -> anyhow::Result<()> {
631 let mint_info = read_fixture("nutshell_mint_info.json")?;
632 let info = serde_json::from_str::<MintInfoResponse>(&mint_info);
633 assert!(info.is_ok());
634 let info = info?;
635 assert_eq!("Nutshell/0.15.0", info.version.unwrap());
636 Ok(())
637 }
638}