Skip to main content

cdk_common/wallet/saga/
issue.rs

1//! Issue (mint) saga types
2
3use cashu::BlindedMessage;
4use serde::{Deserialize, Serialize};
5
6use crate::Error;
7
8/// States specific to mint (issue) saga
9#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum IssueSagaState {
12    /// Pre-mint secrets created and counter incremented, ready to request signatures
13    SecretsPrepared,
14    /// Mint request sent to mint, awaiting signatures for new proofs
15    MintRequested,
16}
17
18impl std::fmt::Display for IssueSagaState {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            IssueSagaState::SecretsPrepared => write!(f, "secrets_prepared"),
22            IssueSagaState::MintRequested => write!(f, "mint_requested"),
23        }
24    }
25}
26
27impl std::str::FromStr for IssueSagaState {
28    type Err = Error;
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        match s {
31            "secrets_prepared" => Ok(IssueSagaState::SecretsPrepared),
32            "mint_requested" => Ok(IssueSagaState::MintRequested),
33            _ => Err(Error::InvalidOperationState),
34        }
35    }
36}
37
38/// Operation-specific data for Mint operations
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct MintOperationData {
41    /// Quote ID (for single mint, or first quote in batch)
42    quote_id: String,
43    /// Quote IDs for batch operations
44    ///
45    /// If present, this is a batch operation. The batch may have one or more quotes.
46    /// For backward compatibility with existing sagas, check this field first.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    quote_ids: Option<Vec<String>>,
49    /// Whether this is a batch operation
50    ///
51    /// True if this was created by batch_mint, false for single mint.
52    /// Used to determine which endpoint to use for replay.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub is_batch: Option<bool>,
55    /// Amount to mint (total for batch)
56    pub amount: crate::Amount,
57    /// Derivation counter start
58    pub counter_start: Option<u32>,
59    /// Derivation counter end
60    pub counter_end: Option<u32>,
61    /// Blinded messages for recovery
62    ///
63    /// Stored so that if a crash occurs after the mint accepts the request,
64    /// we can use these to query the mint for signatures and reconstruct proofs.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub blinded_messages: Option<Vec<BlindedMessage>>,
67}
68
69impl MintOperationData {
70    /// Create operation data for a single-quote mint operation.
71    pub fn new_single(
72        quote_id: String,
73        amount: crate::Amount,
74        counter_start: Option<u32>,
75        counter_end: Option<u32>,
76        blinded_messages: Option<Vec<BlindedMessage>>,
77    ) -> Self {
78        Self {
79            quote_ids: Some(vec![quote_id.clone()]),
80            quote_id,
81            is_batch: Some(false),
82            amount,
83            counter_start,
84            counter_end,
85            blinded_messages,
86        }
87    }
88
89    /// Create operation data for a batch mint operation.
90    pub fn new_batch(
91        quote_ids: Vec<String>,
92        amount: crate::Amount,
93        counter_start: Option<u32>,
94        counter_end: Option<u32>,
95        blinded_messages: Option<Vec<BlindedMessage>>,
96    ) -> Self {
97        let quote_id = quote_ids.first().cloned().unwrap_or_default();
98
99        Self {
100            quote_id,
101            quote_ids: Some(quote_ids),
102            is_batch: Some(true),
103            amount,
104            counter_start,
105            counter_end,
106            blinded_messages,
107        }
108    }
109
110    /// Get the representative quote ID for this operation.
111    pub fn primary_quote_id(&self) -> &str {
112        &self.quote_id
113    }
114
115    /// Get all quote IDs for this operation.
116    ///
117    /// Returns quote_ids if this is a batch, otherwise wraps quote_id in a vec.
118    pub fn quote_ids(&self) -> Vec<String> {
119        if let Some(ref ids) = self.quote_ids {
120            ids.clone()
121        } else {
122            vec![self.quote_id.clone()]
123        }
124    }
125
126    /// Check if this is a batch operation.
127    pub fn is_batch(&self) -> bool {
128        self.is_batch.unwrap_or(false)
129    }
130}