moksha_core/
primitives.rs

1//! This module contains all the request and response objects that are used for interacting between the Mint and Wallet in Cashu.
2//! All of these structs are serializable and deserializable using serde.
3
4use 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, // TODO use sha256::Hash
21}
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    /// fee in satoshis
41    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, // TODO use new type for keyset_id
127    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    /// payment request
204    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    /// onchain address
336    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    /// Minting tokens
383    #[serde(rename = "4")]
384    pub nut4: Nut4,
385
386    /// Melting tokens
387    #[serde(rename = "5")]
388    pub nut5: Nut5,
389
390    /// Token state check
391    #[serde(rename = "7")]
392    pub nut7: Nut7,
393
394    /// Overpaid Lightning fees
395    #[serde(rename = "8")]
396    pub nut8: Nut8,
397
398    /// Deterministic backup and restore
399    #[serde(rename = "9")]
400    pub nut9: Nut9,
401
402    /// Spending conditions
403    #[serde(rename = "10")]
404    pub nut10: Nut10,
405
406    /// Pay-To-Pubkey (P2PK)
407    #[serde(rename = "11")]
408    pub nut11: Nut11,
409
410    #[serde(rename = "12")]
411    /// DLEQ proofs
412    pub nut12: Nut12,
413
414    // TODO remove this if nut-14 and nut-15 are merged
415    #[serde(rename = "14", skip_serializing_if = "Option::is_none")]
416    /// minting tokens onchain
417    pub nut14: Option<Nut14>,
418
419    #[serde(rename = "15", skip_serializing_if = "Option::is_none")]
420    /// melting tokens onchain
421    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        // FIXME add asserts
625
626        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}