cdk_common/
state.rs

1//! State transition rules
2
3use cashu::{MeltQuoteState, State};
4
5/// State transition Error
6#[derive(thiserror::Error, Debug)]
7pub enum Error {
8    /// Pending Token
9    #[error("Token already pending for another update")]
10    Pending,
11    /// Already spent
12    #[error("Token already spent")]
13    AlreadySpent,
14    /// Invalid transition
15    #[error("Invalid transition: From {0} to {1}")]
16    InvalidTransition(State, State),
17    /// Already paid
18    #[error("Quote already paid")]
19    AlreadyPaid,
20    /// Invalid transition
21    #[error("Invalid melt quote state transition: From {0} to {1}")]
22    InvalidMeltQuoteTransition(MeltQuoteState, MeltQuoteState),
23}
24
25#[inline]
26/// Check if the state transition is allowed
27pub fn check_state_transition(current_state: State, new_state: State) -> Result<(), Error> {
28    let is_valid_transition = match current_state {
29        State::Unspent => matches!(new_state, State::Pending | State::Spent),
30        State::Pending => matches!(new_state, State::Unspent | State::Spent),
31        // Any other state shouldn't be updated by the mint, and the wallet does not use this
32        // function
33        _ => false,
34    };
35
36    if !is_valid_transition {
37        Err(match current_state {
38            State::Pending => Error::Pending,
39            State::Spent => Error::AlreadySpent,
40            _ => Error::InvalidTransition(current_state, new_state),
41        })
42    } else {
43        Ok(())
44    }
45}
46
47#[inline]
48/// Check if the melt quote state transition is allowed
49///
50/// Valid transitions:
51/// - Unpaid -> Pending, Failed
52/// - Pending -> Unpaid, Paid, Failed
53/// - Paid -> (no transitions allowed)
54/// - Failed -> Pending
55pub fn check_melt_quote_state_transition(
56    current_state: MeltQuoteState,
57    new_state: MeltQuoteState,
58) -> Result<(), Error> {
59    let is_valid_transition = match current_state {
60        MeltQuoteState::Unpaid => {
61            matches!(new_state, MeltQuoteState::Pending | MeltQuoteState::Failed)
62        }
63        MeltQuoteState::Pending => matches!(
64            new_state,
65            MeltQuoteState::Unpaid | MeltQuoteState::Paid | MeltQuoteState::Failed
66        ),
67        MeltQuoteState::Failed => {
68            matches!(new_state, MeltQuoteState::Pending | MeltQuoteState::Unpaid)
69        }
70        MeltQuoteState::Paid => false,
71        MeltQuoteState::Unknown => true,
72    };
73
74    if !is_valid_transition {
75        Err(match current_state {
76            MeltQuoteState::Pending => Error::Pending,
77            MeltQuoteState::Paid => Error::AlreadyPaid,
78            _ => Error::InvalidMeltQuoteTransition(current_state, new_state),
79        })
80    } else {
81        Ok(())
82    }
83}