fedimint_walletv2_client/
send_sm.rs1use 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}