fedimint_mintv2_client/
input.rs1use fedimint_client::DynGlobalClientContext;
2use fedimint_client::transaction::{ClientInput, ClientInputBundle};
3use fedimint_client_module::module::OutPointRange;
4use fedimint_client_module::sm::{ClientSMDatabaseTransaction, State, StateTransition};
5use fedimint_core::TransactionId;
6use fedimint_core::core::OperationId;
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::module::Amounts;
9use fedimint_mintv2_common::MintInput;
10
11use crate::{MintClientContext, SpendableNote};
12
13#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
14pub struct InputStateMachine {
15 pub common: InputSMCommon,
16 pub state: InputSMState,
17}
18
19#[derive(Debug, Clone, Eq, Hash, PartialEq, Decodable, Encodable)]
20pub struct InputSMCommon {
21 pub operation_id: OperationId,
22 pub txid: TransactionId,
23 pub spendable_notes: Vec<SpendableNote>,
24}
25
26#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
27pub enum InputSMState {
28 Pending,
29 Success,
30 Refunding(OutPointRange),
31}
32
33impl State for InputStateMachine {
34 type ModuleContext = MintClientContext;
35
36 fn transitions(
37 &self,
38 _context: &Self::ModuleContext,
39 global_context: &DynGlobalClientContext,
40 ) -> Vec<StateTransition<Self>> {
41 let gc = global_context.clone();
42
43 match &self.state {
44 InputSMState::Pending => {
45 vec![StateTransition::new(
46 Self::await_pending_transaction(gc.clone(), self.common.txid),
47 move |dbtx, result, old_state| {
48 Box::pin(Self::transition_pending_transaction(
49 gc.clone(),
50 dbtx,
51 result,
52 old_state,
53 ))
54 },
55 )]
56 }
57 InputSMState::Success | InputSMState::Refunding(..) => {
58 vec![]
59 }
60 }
61 }
62
63 fn operation_id(&self) -> OperationId {
64 self.common.operation_id
65 }
66}
67
68impl InputStateMachine {
69 async fn await_pending_transaction(
70 global_context: DynGlobalClientContext,
71 txid: TransactionId,
72 ) -> Result<(), String> {
73 global_context.await_tx_accepted(txid).await
74 }
75
76 async fn transition_pending_transaction(
77 global_context: DynGlobalClientContext,
78 dbtx: &mut ClientSMDatabaseTransaction<'_, '_>,
79 result: Result<(), String>,
80 old_state: InputStateMachine,
81 ) -> InputStateMachine {
82 if result.is_ok() {
83 return InputStateMachine {
84 common: old_state.common,
85 state: InputSMState::Success,
86 };
87 }
88
89 let inputs = old_state
90 .common
91 .spendable_notes
92 .iter()
93 .map(|spendable_note| ClientInput::<MintInput> {
94 input: MintInput::new_v0(spendable_note.note()),
95 keys: vec![spendable_note.keypair],
96 amounts: Amounts::new_bitcoin(spendable_note.amount()),
97 })
98 .collect();
99
100 let change_range = global_context
101 .claim_inputs(dbtx, ClientInputBundle::new_no_sm(inputs))
102 .await
103 .expect("Cannot claim input, additional funding needed");
104
105 InputStateMachine {
106 common: old_state.common,
107 state: InputSMState::Refunding(change_range),
108 }
109 }
110}