Skip to main content

fedimint_walletv2_client/
send_sm.rs

1use fedimint_client_module::DynGlobalClientContext;
2use fedimint_client_module::sm::{ClientSMDatabaseTransaction, State, StateTransition};
3use fedimint_core::OutPoint;
4use fedimint_core::core::OperationId;
5use fedimint_core::encoding::{Decodable, Encodable};
6
7use crate::WalletClientContext;
8use crate::api::WalletFederationApi;
9use crate::events::{SendPaymentStatus, SendPaymentStatusEvent};
10
11#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
12pub struct SendStateMachine {
13    pub common: SendSMCommon,
14    pub state: SendSMState,
15}
16
17impl SendStateMachine {
18    pub fn update(&self, state: SendSMState) -> Self {
19        Self {
20            common: self.common.clone(),
21            state,
22        }
23    }
24}
25
26#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
27pub struct SendSMCommon {
28    pub operation_id: OperationId,
29    pub outpoint: OutPoint,
30    pub amount: bitcoin::Amount,
31    pub fee: bitcoin::Amount,
32}
33
34#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
35pub enum SendSMState {
36    Funding,
37    Success(bitcoin::Txid),
38    Aborted(String),
39    Failure,
40}
41
42impl State for SendStateMachine {
43    type ModuleContext = WalletClientContext;
44
45    fn transitions(
46        &self,
47        context: &Self::ModuleContext,
48        global_context: &DynGlobalClientContext,
49    ) -> Vec<StateTransition<Self>> {
50        let ctx = context.clone();
51
52        match &self.state {
53            SendSMState::Funding => {
54                vec![StateTransition::new(
55                    Self::await_funding(global_context.clone(), self.common.outpoint),
56                    move |dbtx, result, old_state| {
57                        Box::pin(Self::transition_funding(
58                            ctx.clone(),
59                            dbtx,
60                            result,
61                            old_state,
62                        ))
63                    },
64                )]
65            }
66            SendSMState::Success(_) | SendSMState::Aborted(_) | SendSMState::Failure => vec![],
67        }
68    }
69
70    fn operation_id(&self) -> OperationId {
71        self.common.operation_id
72    }
73}
74
75#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
76enum AwaitFundingResult {
77    Success(bitcoin::Txid),
78    Aborted(String),
79    Failure,
80}
81
82impl SendStateMachine {
83    async fn await_funding(
84        global_context: DynGlobalClientContext,
85        outpoint: OutPoint,
86    ) -> AwaitFundingResult {
87        if let Err(error) = global_context.await_tx_accepted(outpoint.txid).await {
88            return AwaitFundingResult::Aborted(error);
89        }
90
91        match global_context.module_api().tx_id(outpoint).await {
92            Some(txid) => AwaitFundingResult::Success(txid),
93            None => AwaitFundingResult::Failure,
94        }
95    }
96
97    async fn transition_funding(
98        context: WalletClientContext,
99        dbtx: &mut ClientSMDatabaseTransaction<'_, '_>,
100        result: AwaitFundingResult,
101        old_state: SendStateMachine,
102    ) -> SendStateMachine {
103        match result {
104            AwaitFundingResult::Success(txid) => {
105                context
106                    .client_ctx
107                    .log_event(
108                        &mut dbtx.module_tx(),
109                        SendPaymentStatusEvent {
110                            operation_id: old_state.common.operation_id,
111                            status: SendPaymentStatus::Success(txid),
112                        },
113                    )
114                    .await;
115
116                old_state.update(SendSMState::Success(txid))
117            }
118            AwaitFundingResult::Aborted(error) => {
119                context
120                    .client_ctx
121                    .log_event(
122                        &mut dbtx.module_tx(),
123                        SendPaymentStatusEvent {
124                            operation_id: old_state.common.operation_id,
125                            status: SendPaymentStatus::Aborted,
126                        },
127                    )
128                    .await;
129
130                old_state.update(SendSMState::Aborted(error))
131            }
132            AwaitFundingResult::Failure => old_state.update(SendSMState::Failure),
133        }
134    }
135}