Skip to main content

fedimint_client_module/module/
mod.rs

1use core::fmt;
2use std::any::Any;
3use std::collections::BTreeSet;
4use std::fmt::Debug;
5use std::pin::Pin;
6use std::sync::{Arc, Weak};
7use std::{ffi, marker, ops};
8
9use bitcoin::secp256k1::PublicKey;
10use fedimint_api_client::api::{DynGlobalApi, DynModuleApi};
11use fedimint_core::config::ClientConfig;
12use fedimint_core::core::{
13    Decoder, DynInput, DynOutput, IInput, IntoDynInstance, ModuleInstanceId, ModuleKind,
14    OperationId,
15};
16use fedimint_core::db::{Database, DatabaseTransaction, GlobalDBTxAccessToken, NonCommittable};
17use fedimint_core::invite_code::InviteCode;
18use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
19use fedimint_core::module::{AmountUnit, Amounts, CommonModuleInit, ModuleCommon, ModuleInit};
20use fedimint_core::task::{MaybeSend, MaybeSync};
21use fedimint_core::util::{BoxStream, FmtCompact as _};
22use fedimint_core::{
23    Amount, OutPoint, PeerId, apply, async_trait_maybe_send, dyn_newtype_define, maybe_add_send,
24    maybe_add_send_sync,
25};
26use fedimint_eventlog::{
27    DBTransactionEventLogExt, Event, EventKind, EventLogId, EventPersistence, PersistedLogEntry,
28};
29use fedimint_logging::LOG_CLIENT;
30use futures::{Stream, StreamExt};
31use serde::Serialize;
32use serde::de::DeserializeOwned;
33use tracing::warn;
34
35use self::init::ClientModuleInit;
36use crate::error::{
37    ClientModuleError, ModuleLookupError, OperationAlreadyExistsError, OperationLookupError,
38    OperationNotFoundError, TransactionSubmitError,
39};
40use crate::module::recovery::{DynModuleBackup, ModuleBackup};
41use crate::oplog::{IOperationLog, OperationLogEntry, UpdateStreamOrOutcome};
42use crate::sm::executor::{ActiveStateKey, IExecutor, InactiveStateKey};
43use crate::sm::{self, ActiveStateMeta, Context, DynContext, DynState, InactiveStateMeta, State};
44use crate::transaction::{
45    ClientInputBundle, ClientOutputBundle, FeeQuote, FeeQuoteRequest, TransactionBuilder,
46};
47use crate::{AddStateMachinesResult, InstancelessDynClientInputBundle, TransactionUpdates, oplog};
48
49pub mod init;
50pub mod recovery;
51
52pub type ClientModuleRegistry = ModuleRegistry<DynClientModule>;
53
54/// A fedimint-client interface exposed to client modules
55///
56/// To break the dependency of the client modules on the whole fedimint client
57/// and in particular the `fedimint-client` crate, the module gets access to an
58/// interface, that is implemented by the `Client`.
59///
60/// This allows lose coupling, less recompilation and better control and
61/// understanding of what functionality of the Client the modules get access to.
62#[apply(async_trait_maybe_send!)]
63pub trait ClientContextIface: MaybeSend + MaybeSync {
64    fn get_module(&self, instance: ModuleInstanceId) -> &maybe_add_send_sync!(dyn IClientModule);
65    fn api_clone(&self) -> DynGlobalApi;
66    fn decoders(&self) -> &ModuleDecoderRegistry;
67    async fn finalize_and_submit_transaction(
68        &self,
69        operation_id: OperationId,
70        operation_type: &str,
71        operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
72        tx_builder: TransactionBuilder,
73    ) -> Result<OutPointRange, TransactionSubmitError>;
74
75    async fn finalize_and_submit_transaction_dbtx(
76        &self,
77        dbtx: &mut DatabaseTransaction<'_>,
78        operation_id: OperationId,
79        operation_type: &str,
80        operation_meta_gen: Box<maybe_add_send_sync!(dyn Fn(OutPointRange) -> serde_json::Value)>,
81        tx_builder: TransactionBuilder,
82    ) -> Result<OutPointRange, TransactionSubmitError>;
83
84    // TODO: unify
85    async fn finalize_and_submit_transaction_inner(
86        &self,
87        dbtx: &mut DatabaseTransaction<'_>,
88        operation_id: OperationId,
89        tx_builder: TransactionBuilder,
90    ) -> Result<OutPointRange, TransactionSubmitError>;
91
92    /// Computes the fee finalizing and submitting a transaction with the
93    /// explicit items described by `request` would incur, without submitting
94    /// anything. See `Client::fee_quote`.
95    async fn fee_quote(
96        &self,
97        operation_id: OperationId,
98        request: FeeQuoteRequest,
99    ) -> Result<FeeQuote, TransactionSubmitError>;
100
101    /// The client's balance for `unit`, held by the primary module. See
102    /// `Client::get_balance_for_unit`.
103    async fn get_balance_for_unit(&self, unit: AmountUnit) -> Result<Amount, ModuleLookupError>;
104
105    async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates;
106
107    async fn await_primary_module_outputs(
108        &self,
109        operation_id: OperationId,
110        // TODO: make `impl Iterator<Item = ...>`
111        outputs: Vec<OutPoint>,
112    ) -> Result<(), TransactionSubmitError>;
113
114    fn operation_log(&self) -> &dyn IOperationLog;
115
116    async fn has_active_states(&self, operation_id: OperationId) -> bool;
117
118    async fn operation_exists(&self, operation_id: OperationId) -> bool;
119
120    async fn config(&self) -> ClientConfig;
121
122    fn db(&self) -> &Database;
123
124    fn executor(&self) -> &(maybe_add_send_sync!(dyn IExecutor + 'static));
125
126    async fn invite_code(&self, peer: PeerId) -> Option<InviteCode>;
127
128    fn get_internal_payment_markers(&self) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error>;
129
130    #[allow(clippy::too_many_arguments)]
131    async fn log_event_json(
132        &self,
133        dbtx: &mut DatabaseTransaction<'_, NonCommittable>,
134        module_kind: Option<ModuleKind>,
135        module_id: ModuleInstanceId,
136        kind: EventKind,
137        payload: serde_json::Value,
138        persist: EventPersistence,
139    );
140
141    async fn read_operation_active_states<'dbtx>(
142        &self,
143        operation_id: OperationId,
144        module_id: ModuleInstanceId,
145        dbtx: &'dbtx mut DatabaseTransaction<'_>,
146    ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (ActiveStateKey, ActiveStateMeta)> + 'dbtx)>>;
147
148    async fn read_operation_inactive_states<'dbtx>(
149        &self,
150        operation_id: OperationId,
151        module_id: ModuleInstanceId,
152        dbtx: &'dbtx mut DatabaseTransaction<'_>,
153    ) -> Pin<Box<maybe_add_send!(dyn Stream<Item = (InactiveStateKey, InactiveStateMeta)> + 'dbtx)>>;
154}
155
156/// A final, fully initialized client
157///
158/// Client modules need to be able to access a `Client` they are a part
159/// of. To break the circular dependency, the final `Client` is passed
160/// after `Client` was built via a shared state.
161#[derive(Clone, Default)]
162pub struct FinalClientIface(Arc<std::sync::OnceLock<Weak<dyn ClientContextIface>>>);
163
164impl FinalClientIface {
165    /// Get a temporary strong reference to [`ClientContextIface`]
166    ///
167    /// Care must be taken to not let the user take ownership of this value,
168    /// and not store it elsewhere permanently either, as it could prevent
169    /// the cleanup of the Client.
170    pub(crate) fn get(&self) -> Arc<dyn ClientContextIface> {
171        self.0
172            .get()
173            .expect("client must be already set")
174            .upgrade()
175            .expect("client module context must not be use past client shutdown")
176    }
177
178    pub fn set(&self, client: Weak<dyn ClientContextIface>) {
179        self.0.set(client).expect("FinalLazyClient already set");
180    }
181}
182
183impl fmt::Debug for FinalClientIface {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        f.write_str("FinalClientIface")
186    }
187}
188/// A Client context for a [`ClientModule`] `M`
189///
190/// Client modules can interact with the whole
191/// client through this struct.
192pub struct ClientContext<M> {
193    client: FinalClientIface,
194    module_instance_id: ModuleInstanceId,
195    global_dbtx_access_token: GlobalDBTxAccessToken,
196    module_db: Database,
197    _marker: marker::PhantomData<M>,
198}
199
200impl<M> Clone for ClientContext<M> {
201    fn clone(&self) -> Self {
202        Self {
203            client: self.client.clone(),
204            module_db: self.module_db.clone(),
205            module_instance_id: self.module_instance_id,
206            _marker: marker::PhantomData,
207            global_dbtx_access_token: self.global_dbtx_access_token,
208        }
209    }
210}
211
212/// A reference back to itself that the module cacn get from the
213/// [`ClientContext`]
214pub struct ClientContextSelfRef<'s, M> {
215    // we are OK storing `ClientStrong` here, because of the `'s` preventing `Self` from being
216    // stored permanently somewhere
217    client: Arc<dyn ClientContextIface>,
218    module_instance_id: ModuleInstanceId,
219    _marker: marker::PhantomData<&'s M>,
220}
221
222impl<M> ops::Deref for ClientContextSelfRef<'_, M>
223where
224    M: ClientModule,
225{
226    type Target = M;
227
228    fn deref(&self) -> &Self::Target {
229        self.client
230            .get_module(self.module_instance_id)
231            .as_any()
232            .downcast_ref::<M>()
233            .unwrap_or_else(|| panic!("Module is not of type {}", std::any::type_name::<M>()))
234    }
235}
236
237impl<M> fmt::Debug for ClientContext<M> {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        f.write_str("ClientContext")
240    }
241}
242
243impl<M> ClientContext<M>
244where
245    M: ClientModule,
246{
247    pub fn new(
248        client: FinalClientIface,
249        module_instance_id: ModuleInstanceId,
250        global_dbtx_access_token: GlobalDBTxAccessToken,
251        module_db: Database,
252    ) -> Self {
253        Self {
254            client,
255            module_instance_id,
256            global_dbtx_access_token,
257            module_db,
258            _marker: marker::PhantomData,
259        }
260    }
261
262    /// Get a reference back to client module from the [`Self`]
263    ///
264    /// It's often necessary for a client module to "move self"
265    /// by-value, especially due to async lifetimes issues.
266    /// Clients usually work with `&mut self`, which can't really
267    /// work in such context.
268    ///
269    /// Fortunately [`ClientContext`] is `Clone` and `Send, and
270    /// can be used to recover the reference to the module at later
271    /// time.
272    #[allow(clippy::needless_lifetimes)] // just for explicitiness
273    pub fn self_ref(&self) -> ClientContextSelfRef<'_, M> {
274        ClientContextSelfRef {
275            client: self.client.get(),
276            module_instance_id: self.module_instance_id,
277            _marker: marker::PhantomData,
278        }
279    }
280
281    /// Get a reference to a global Api handle
282    pub fn global_api(&self) -> DynGlobalApi {
283        self.client.get().api_clone()
284    }
285
286    /// Get a reference to a module Api handle
287    pub fn module_api(&self) -> DynModuleApi {
288        self.global_api().with_module(self.module_instance_id)
289    }
290
291    /// A set of all decoders of all modules of the client
292    pub fn decoders(&self) -> ModuleDecoderRegistry {
293        Clone::clone(self.client.get().decoders())
294    }
295
296    pub fn input_from_dyn<'i>(
297        &self,
298        input: &'i DynInput,
299    ) -> Option<&'i <M::Common as ModuleCommon>::Input> {
300        (input.module_instance_id() == self.module_instance_id).then(|| {
301            input
302                .as_any()
303                .downcast_ref::<<M::Common as ModuleCommon>::Input>()
304                .unwrap_or_else(|| {
305                    panic!("instance_id {} just checked", input.module_instance_id())
306                })
307        })
308    }
309
310    pub fn output_from_dyn<'o>(
311        &self,
312        output: &'o DynOutput,
313    ) -> Option<&'o <M::Common as ModuleCommon>::Output> {
314        (output.module_instance_id() == self.module_instance_id).then(|| {
315            output
316                .as_any()
317                .downcast_ref::<<M::Common as ModuleCommon>::Output>()
318                .unwrap_or_else(|| {
319                    panic!("instance_id {} just checked", output.module_instance_id())
320                })
321        })
322    }
323
324    pub fn map_dyn<'s, 'i, 'o, I>(
325        &'s self,
326        typed: impl IntoIterator<Item = I> + 'i,
327    ) -> impl Iterator<Item = <I as IntoDynInstance>::DynType> + 'o
328    where
329        I: IntoDynInstance,
330        'i: 'o,
331        's: 'o,
332    {
333        typed.into_iter().map(|i| self.make_dyn(i))
334    }
335
336    /// Turn a typed output into a dyn version
337    pub fn make_dyn_output(&self, output: <M::Common as ModuleCommon>::Output) -> DynOutput {
338        self.make_dyn(output)
339    }
340
341    /// Turn a typed input into a dyn version
342    pub fn make_dyn_input(&self, input: <M::Common as ModuleCommon>::Input) -> DynInput {
343        self.make_dyn(input)
344    }
345
346    /// Turn a `typed` into a dyn version
347    pub fn make_dyn<I>(&self, typed: I) -> <I as IntoDynInstance>::DynType
348    where
349        I: IntoDynInstance,
350    {
351        typed.into_dyn(self.module_instance_id)
352    }
353
354    /// Turn a typed [`ClientOutputBundle`] into a dyn version
355    pub fn make_client_outputs<O, S>(&self, output: ClientOutputBundle<O, S>) -> ClientOutputBundle
356    where
357        O: IntoDynInstance<DynType = DynOutput> + 'static,
358        S: IntoDynInstance<DynType = DynState> + 'static,
359    {
360        self.make_dyn(output)
361    }
362
363    /// Turn a typed [`ClientInputBundle`] into a dyn version
364    pub fn make_client_inputs<I, S>(&self, inputs: ClientInputBundle<I, S>) -> ClientInputBundle
365    where
366        I: IntoDynInstance<DynType = DynInput> + 'static,
367        S: IntoDynInstance<DynType = DynState> + 'static,
368    {
369        self.make_dyn(inputs)
370    }
371
372    pub fn make_dyn_state<S>(&self, sm: S) -> DynState
373    where
374        S: sm::IState + 'static,
375    {
376        DynState::from_typed(self.module_instance_id, sm)
377    }
378
379    pub async fn finalize_and_submit_transaction<F, Meta>(
380        &self,
381        operation_id: OperationId,
382        operation_type: &str,
383        operation_meta_gen: F,
384        tx_builder: TransactionBuilder,
385    ) -> Result<OutPointRange, TransactionSubmitError>
386    where
387        F: Fn(OutPointRange) -> Meta + Clone + MaybeSend + MaybeSync + 'static,
388        Meta: serde::Serialize + MaybeSend,
389    {
390        self.client
391            .get()
392            .finalize_and_submit_transaction(
393                operation_id,
394                operation_type,
395                Box::new(move |out_point_range| {
396                    serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
397                }),
398                tx_builder,
399            )
400            .await
401    }
402
403    pub async fn finalize_and_submit_transaction_dbtx<F, Meta>(
404        &self,
405        dbtx: &mut DatabaseTransaction<'_>,
406        operation_id: OperationId,
407        operation_type: &str,
408        operation_meta_gen: F,
409        tx_builder: TransactionBuilder,
410    ) -> Result<OutPointRange, TransactionSubmitError>
411    where
412        F: Fn(OutPointRange) -> Meta + MaybeSend + MaybeSync + 'static,
413        Meta: serde::Serialize + MaybeSend,
414    {
415        self.client
416            .get()
417            .finalize_and_submit_transaction_dbtx(
418                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
419                operation_id,
420                operation_type,
421                Box::new(move |out_point_range| {
422                    serde_json::to_value(operation_meta_gen(out_point_range)).expect("Can't fail")
423                }),
424                tx_builder,
425            )
426            .await
427    }
428
429    /// Computes the fee that finalizing and submitting a transaction with the
430    /// explicit items described by `request` would incur, as a dry-run over the
431    /// client's current funds, without submitting anything. See
432    /// `Client::fee_quote`.
433    ///
434    /// Summarize the explicit inputs/outputs the operation would submit (their
435    /// gross amounts and federation fees) in `request`; the returned breakdown
436    /// adds the change the primary module's balancing would produce.
437    pub async fn fee_quote(
438        &self,
439        operation_id: OperationId,
440        request: FeeQuoteRequest,
441    ) -> Result<FeeQuote, TransactionSubmitError> {
442        self.client.get().fee_quote(operation_id, request).await
443    }
444
445    /// The client's Bitcoin balance, held by the primary module. See
446    /// `Client::get_balance_for_btc`.
447    pub async fn get_balance_for_btc(&self) -> Result<Amount, ModuleLookupError> {
448        self.client
449            .get()
450            .get_balance_for_unit(AmountUnit::BITCOIN)
451            .await
452    }
453
454    pub async fn transaction_updates(&self, operation_id: OperationId) -> TransactionUpdates {
455        self.client.get().transaction_updates(operation_id).await
456    }
457
458    pub async fn await_primary_module_outputs(
459        &self,
460        operation_id: OperationId,
461        // TODO: make `impl Iterator<Item = ...>`
462        outputs: Vec<OutPoint>,
463    ) -> Result<(), TransactionSubmitError> {
464        self.client
465            .get()
466            .await_primary_module_outputs(operation_id, outputs)
467            .await
468    }
469
470    // TODO: unify with `Self::get_operation`
471    pub async fn get_operation(
472        &self,
473        operation_id: OperationId,
474    ) -> Result<oplog::OperationLogEntry, OperationLookupError> {
475        let operation = self
476            .client
477            .get()
478            .operation_log()
479            .get_operation(operation_id)
480            .await
481            .ok_or(OperationNotFoundError { operation_id })?;
482
483        if operation.operation_module_kind() != M::kind().as_str() {
484            return Err(OperationLookupError::WrongModuleKind {
485                operation_id,
486                expected: M::kind(),
487                found: operation.operation_module_kind().to_owned(),
488            });
489        }
490
491        Ok(operation)
492    }
493
494    /// Get global db.
495    ///
496    /// Only intended for internal use (private).
497    fn global_db(&self) -> fedimint_core::db::Database {
498        let db = Clone::clone(self.client.get().db());
499
500        db.ensure_global()
501            .expect("global_db must always return a global db");
502
503        db
504    }
505
506    pub fn module_db(&self) -> &Database {
507        self.module_db
508            .ensure_isolated()
509            .expect("module_db must always return isolated db");
510        &self.module_db
511    }
512
513    /// Read a portion of the client's event log, starting at `pos` (or the
514    /// beginning of the log if `None`) and returning up to `limit` entries.
515    pub async fn get_event_log(
516        &self,
517        pos: Option<EventLogId>,
518        limit: u64,
519    ) -> Vec<PersistedLogEntry> {
520        self.global_db()
521            .begin_transaction_nc()
522            .await
523            .get_event_log(pos, limit)
524            .await
525    }
526
527    pub async fn has_active_states(&self, op_id: OperationId) -> bool {
528        self.client.get().has_active_states(op_id).await
529    }
530
531    pub async fn operation_exists(&self, op_id: OperationId) -> bool {
532        self.client.get().operation_exists(op_id).await
533    }
534
535    pub async fn get_own_active_states(&self) -> Vec<(M::States, ActiveStateMeta)> {
536        self.client
537            .get()
538            .executor()
539            .get_active_states()
540            .await
541            .into_iter()
542            .filter(|s| s.0.module_instance_id() == self.module_instance_id)
543            .map(|s| {
544                (
545                    Clone::clone(
546                        s.0.as_any()
547                            .downcast_ref::<M::States>()
548                            .expect("incorrect output type passed to module plugin"),
549                    ),
550                    s.1,
551                )
552            })
553            .collect()
554    }
555
556    /// Returns this module's currently active state machines for the operation.
557    pub async fn get_own_operation_active_states(
558        &self,
559        operation_id: OperationId,
560    ) -> Vec<(M::States, ActiveStateMeta)> {
561        let db = self.global_db();
562        let mut dbtx = db.begin_transaction_nc().await;
563
564        self.client
565            .get()
566            .read_operation_active_states(operation_id, self.module_instance_id, &mut dbtx)
567            .await
568            .map(|(key, meta)| {
569                (
570                    Clone::clone(
571                        key.state
572                            .as_any()
573                            .downcast_ref::<M::States>()
574                            .expect("incorrect output type passed to module plugin"),
575                    ),
576                    meta,
577                )
578            })
579            .collect()
580            .await
581    }
582
583    /// Returns this module's previously active state machines for the
584    /// operation.
585    pub async fn get_own_operation_inactive_states(
586        &self,
587        operation_id: OperationId,
588    ) -> Vec<(M::States, InactiveStateMeta)> {
589        let db = self.global_db();
590        let mut dbtx = db.begin_transaction_nc().await;
591
592        self.client
593            .get()
594            .read_operation_inactive_states(operation_id, self.module_instance_id, &mut dbtx)
595            .await
596            .map(|(key, meta)| {
597                (
598                    Clone::clone(
599                        key.state
600                            .as_any()
601                            .downcast_ref::<M::States>()
602                            .expect("incorrect output type passed to module plugin"),
603                    ),
604                    meta,
605                )
606            })
607            .collect()
608            .await
609    }
610
611    pub async fn get_config(&self) -> ClientConfig {
612        self.client.get().config().await
613    }
614
615    /// Returns an invite code for the federation that points to an arbitrary
616    /// guardian server for fetching the config
617    pub async fn get_invite_code(&self) -> InviteCode {
618        let cfg = self.get_config().await.global;
619        self.client
620            .get()
621            .invite_code(
622                *cfg.api_endpoints
623                    .keys()
624                    .next()
625                    .expect("A federation always has at least one guardian"),
626            )
627            .await
628            .expect("The guardian we requested an invite code for exists")
629    }
630
631    pub fn get_internal_payment_markers(
632        &self,
633    ) -> Result<(PublicKey, u64), bitcoin::secp256k1::Error> {
634        self.client.get().get_internal_payment_markers()
635    }
636
637    /// This method starts n state machines with given operation id without a
638    /// corresponding transaction
639    pub async fn manual_operation_start(
640        &self,
641        operation_id: OperationId,
642        op_type: &str,
643        operation_meta: impl serde::Serialize + Debug,
644        sms: Vec<DynState>,
645    ) -> Result<(), TransactionSubmitError> {
646        let db = self.module_db();
647        let mut dbtx = db.begin_transaction().await;
648        {
649            let dbtx = &mut dbtx.global_dbtx(self.global_dbtx_access_token);
650
651            self.manual_operation_start_inner(
652                &mut dbtx.to_ref_nc(),
653                operation_id,
654                op_type,
655                operation_meta,
656                sms,
657            )
658            .await?;
659        }
660
661        dbtx.commit_tx_result().await?;
662
663        Ok(())
664    }
665
666    pub async fn manual_operation_start_dbtx(
667        &self,
668        dbtx: &mut DatabaseTransaction<'_>,
669        operation_id: OperationId,
670        op_type: &str,
671        operation_meta: impl serde::Serialize + Debug,
672        sms: Vec<DynState>,
673    ) -> Result<(), OperationAlreadyExistsError> {
674        self.manual_operation_start_inner(
675            &mut dbtx.global_dbtx(self.global_dbtx_access_token),
676            operation_id,
677            op_type,
678            operation_meta,
679            sms,
680        )
681        .await
682    }
683
684    /// See [`Self::manual_operation_start`], just inside a database
685    /// transaction.
686    async fn manual_operation_start_inner(
687        &self,
688        dbtx: &mut DatabaseTransaction<'_>,
689        operation_id: OperationId,
690        op_type: &str,
691        operation_meta: impl serde::Serialize + Debug,
692        sms: Vec<DynState>,
693    ) -> Result<(), OperationAlreadyExistsError> {
694        dbtx.ensure_global()
695            .expect("Must deal with global dbtx here");
696
697        if self
698            .client
699            .get()
700            .operation_log()
701            .get_operation_dbtx(&mut dbtx.to_ref_nc(), operation_id)
702            .await
703            .is_some()
704        {
705            return Err(OperationAlreadyExistsError { operation_id });
706        }
707
708        self.client
709            .get()
710            .operation_log()
711            .add_operation_log_entry_dbtx(
712                &mut dbtx.to_ref_nc(),
713                operation_id,
714                op_type,
715                serde_json::to_value(operation_meta).expect("Can't fail"),
716            )
717            .await;
718
719        self.client
720            .get()
721            .executor()
722            .add_state_machines_dbtx(&mut dbtx.to_ref_nc(), sms)
723            .await
724            .expect("State machine is valid");
725
726        Ok(())
727    }
728
729    /// Resolve an operation to its cached outcome, or to a caching update
730    /// stream built by `stream_gen`.
731    ///
732    /// `is_terminal` reports whether an update is a final state of the
733    /// operation; only terminal updates are cached as the durable outcome (a
734    /// stream that ends early on a non-terminal update caches nothing). The
735    /// same predicate also validates outcomes READ from the cache: a cached
736    /// outcome that fails to deserialize into `U` (e.g. one written by an
737    /// incompatible earlier version) or that is non-terminal (one frozen by a
738    /// previous version that cached whatever update a stream ended on) is
739    /// discarded with a warning and the state is rebuilt from the update
740    /// stream — whose terminal end then overwrites the stale cache — instead
741    /// of short-circuiting subscribers to it or panicking.
742    pub fn outcome_or_updates<U, S>(
743        &self,
744        operation: &OperationLogEntry,
745        operation_id: OperationId,
746        is_terminal: impl Fn(&U) -> bool + MaybeSend + MaybeSync + 'static,
747        stream_gen: impl FnOnce() -> S + MaybeSend + 'static,
748    ) -> UpdateStreamOrOutcome<U>
749    where
750        U: Clone + Serialize + DeserializeOwned + Debug + MaybeSend + MaybeSync + 'static,
751        S: Stream<Item = U> + MaybeSend + 'static,
752    {
753        use futures::StreamExt;
754        match operation.try_outcome::<U>() {
755            Ok(Some(outcome)) if is_terminal(&outcome) => {
756                return UpdateStreamOrOutcome::Outcome(outcome);
757            }
758            Ok(Some(_non_terminal)) => {
759                warn!(
760                    target: LOG_CLIENT,
761                    "Cached operation outcome is not a terminal update (cached by a previous \
762                     version); rebuilding it from the update stream"
763                );
764            }
765            Ok(None) => {}
766            Err(err) => {
767                warn!(
768                    target: LOG_CLIENT,
769                    err = %err.fmt_compact(),
770                    "Cached operation outcome failed to deserialize; rebuilding it from the update stream"
771                );
772            }
773        }
774        let stream = self
775            .client
776            .get()
777            .operation_log()
778            .caching_operation_update_stream(
779                operation_id,
780                Box::new(move || {
781                    let stream_gen = stream_gen();
782                    Box::pin(
783                        stream_gen.map(move |item| serde_json::to_value(item).expect("Can't fail")),
784                    )
785                }),
786                Box::new(move |update| {
787                    // The update was serialized from a `U` moments ago in this
788                    // process, so deserialization only fails if `U` is not
789                    // round-trip-safe; treat that conservatively as non-terminal
790                    // (skip caching) rather than panicking inside the stream.
791                    serde_json::from_value::<U>(update.clone())
792                        .map(|update| is_terminal(&update))
793                        .unwrap_or(false)
794                }),
795            );
796        UpdateStreamOrOutcome::UpdateStream(Box::pin(
797            stream.map(|u| serde_json::from_value::<U>(u).expect("Can't fail")),
798        ))
799    }
800
801    pub async fn claim_inputs<I, S>(
802        &self,
803        dbtx: &mut DatabaseTransaction<'_>,
804        inputs: ClientInputBundle<I, S>,
805        operation_id: OperationId,
806    ) -> Result<OutPointRange, TransactionSubmitError>
807    where
808        I: IInput + MaybeSend + MaybeSync + 'static,
809        S: sm::IState + MaybeSend + MaybeSync + 'static,
810    {
811        self.claim_inputs_dyn(dbtx, inputs.into_instanceless(), operation_id)
812            .await
813    }
814
815    async fn claim_inputs_dyn(
816        &self,
817        dbtx: &mut DatabaseTransaction<'_>,
818        inputs: InstancelessDynClientInputBundle,
819        operation_id: OperationId,
820    ) -> Result<OutPointRange, TransactionSubmitError> {
821        let tx_builder =
822            TransactionBuilder::new().with_inputs(inputs.into_dyn(self.module_instance_id));
823
824        self.client
825            .get()
826            .finalize_and_submit_transaction_inner(
827                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
828                operation_id,
829                tx_builder,
830            )
831            .await
832    }
833
834    pub async fn add_state_machines_dbtx(
835        &self,
836        dbtx: &mut DatabaseTransaction<'_>,
837        states: Vec<DynState>,
838    ) -> AddStateMachinesResult {
839        self.client
840            .get()
841            .executor()
842            .add_state_machines_dbtx(&mut dbtx.global_dbtx(self.global_dbtx_access_token), states)
843            .await
844    }
845
846    /// Reads an operation log entry within an ongoing database transaction.
847    ///
848    /// Unlike [`Self::get_operation`] this observes writes made earlier in
849    /// `dbtx`, which is what a caller needs to decide atomically whether it is
850    /// about to create an operation that already exists.
851    pub async fn get_operation_dbtx(
852        &self,
853        dbtx: &mut DatabaseTransaction<'_>,
854        operation_id: OperationId,
855    ) -> Option<oplog::OperationLogEntry> {
856        self.client
857            .get()
858            .operation_log()
859            .get_operation_dbtx(
860                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
861                operation_id,
862            )
863            .await
864    }
865
866    pub async fn add_operation_log_entry_dbtx(
867        &self,
868        dbtx: &mut DatabaseTransaction<'_>,
869        operation_id: OperationId,
870        operation_type: &str,
871        operation_meta: impl serde::Serialize,
872    ) {
873        self.client
874            .get()
875            .operation_log()
876            .add_operation_log_entry_dbtx(
877                &mut dbtx.global_dbtx(self.global_dbtx_access_token),
878                operation_id,
879                operation_type,
880                serde_json::to_value(operation_meta).expect("Can't fail"),
881            )
882            .await;
883    }
884
885    pub async fn log_event<E, Cap>(&self, dbtx: &mut DatabaseTransaction<'_, Cap>, event: E)
886    where
887        E: Event + Send,
888        Cap: Send,
889    {
890        if <E as Event>::MODULE != Some(<M as ClientModule>::kind()) {
891            warn!(
892                target: LOG_CLIENT,
893                module_kind = %<M as ClientModule>::kind(),
894                event_module = ?<E as Event>::MODULE,
895                "Client module logging events of different module than its own. This might become an error in the future."
896            );
897        }
898        self.client
899            .get()
900            .log_event_json(
901                &mut dbtx.global_dbtx(self.global_dbtx_access_token).to_ref_nc(),
902                <E as Event>::MODULE,
903                self.module_instance_id,
904                <E as Event>::KIND,
905                serde_json::to_value(event).expect("Can't fail"),
906                <E as Event>::PERSISTENCE,
907            )
908            .await;
909    }
910}
911
912/// Priority module priority (lower number is higher priority)
913#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
914pub struct PrimaryModulePriority(u64);
915
916impl PrimaryModulePriority {
917    pub const HIGH: Self = Self(100);
918    pub const LOW: Self = Self(10000);
919
920    pub fn custom(prio: u64) -> Self {
921        Self(prio)
922    }
923}
924/// Which amount units this module supports being primary for
925pub enum PrimaryModuleSupport {
926    /// Potentially any unit
927    Any { priority: PrimaryModulePriority },
928    /// Some units supported by the module
929    Selected {
930        priority: PrimaryModulePriority,
931        units: BTreeSet<AmountUnit>,
932    },
933    /// None
934    None,
935}
936
937impl PrimaryModuleSupport {
938    pub fn selected<const N: usize>(
939        priority: PrimaryModulePriority,
940        units: [AmountUnit; N],
941    ) -> Self {
942        Self::Selected {
943            priority,
944            units: BTreeSet::from(units),
945        }
946    }
947}
948
949/// Fedimint module client
950#[apply(async_trait_maybe_send!)]
951pub trait ClientModule: Debug + MaybeSend + MaybeSync + 'static {
952    type Init: ClientModuleInit;
953
954    /// Common module types shared between client and server
955    type Common: ModuleCommon;
956
957    /// Data stored in regular backups so that restoring doesn't have to start
958    /// from epoch 0
959    type Backup: ModuleBackup;
960
961    /// Data and API clients available to state machine transitions of this
962    /// module
963    type ModuleStateMachineContext: Context;
964
965    /// All possible states this client can submit to the executor
966    type States: State<ModuleContext = Self::ModuleStateMachineContext>
967        + IntoDynInstance<DynType = DynState>;
968
969    fn decoder() -> Decoder {
970        let mut decoder_builder = Self::Common::decoder_builder();
971        decoder_builder.with_decodable_type::<Self::States>();
972        decoder_builder.with_decodable_type::<Self::Backup>();
973        decoder_builder.build()
974    }
975
976    fn kind() -> ModuleKind {
977        <<<Self as ClientModule>::Init as ModuleInit>::Common as CommonModuleInit>::KIND
978    }
979
980    fn context(&self) -> Self::ModuleStateMachineContext;
981
982    /// Initialize client.
983    ///
984    /// Called by the core client code on start, after [`ClientContext`] is
985    /// fully initialized, so unlike during [`ClientModuleInit::init`],
986    /// access to global client is allowed.
987    async fn start(&self) {}
988
989    /// Runs a `fedimint-cli module` command, given as its arguments.
990    ///
991    /// # Errors
992    ///
993    /// The default body fails with [`ClientModuleError::Unsupported`], for a
994    /// module that has no commands.
995    async fn handle_cli_command(
996        &self,
997        _args: &[ffi::OsString],
998    ) -> Result<serde_json::Value, ClientModuleError> {
999        Err(ClientModuleError::Unsupported {
1000            kind: <Self as ClientModule>::kind(),
1001            operation: "handle_cli_command",
1002        })
1003    }
1004
1005    /// Serves a JSON request, such as one `fedimint-client-rpc` forwards.
1006    ///
1007    /// # Errors
1008    ///
1009    /// The stream yields the module's failures. The default body yields a
1010    /// single [`ClientModuleError::Unsupported`], for a module that serves no
1011    /// requests.
1012    async fn handle_rpc(
1013        &self,
1014        _method: String,
1015        _request: serde_json::Value,
1016    ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>> {
1017        Box::pin(futures::stream::once(std::future::ready(Err(
1018            ClientModuleError::Unsupported {
1019                kind: <Self as ClientModule>::kind(),
1020                operation: "handle_rpc",
1021            },
1022        ))))
1023    }
1024
1025    /// Returns the fee the processing of this input requires.
1026    ///
1027    /// If the semantics of a given input aren't known this function returns
1028    /// `None`, this only happens if a future version of Fedimint introduces a
1029    /// new input variant. For clients this should only be the case when
1030    /// processing transactions created by other users, so the result of
1031    /// this function can be `unwrap`ped whenever dealing with inputs
1032    /// generated by ourselves.
1033    fn input_fee(
1034        &self,
1035        amount: &Amounts,
1036        input: &<Self::Common as ModuleCommon>::Input,
1037    ) -> Option<Amounts>;
1038
1039    /// Returns the fee the processing of this output requires.
1040    ///
1041    /// If the semantics of a given output aren't known this function returns
1042    /// `None`, this only happens if a future version of Fedimint introduces a
1043    /// new output variant. For clients this should only be the case when
1044    /// processing transactions created by other users, so the result of
1045    /// this function can be `unwrap`ped whenever dealing with inputs
1046    /// generated by ourselves.
1047    fn output_fee(
1048        &self,
1049        amount: &Amounts,
1050        output: &<Self::Common as ModuleCommon>::Output,
1051    ) -> Option<Amounts>;
1052
1053    fn supports_backup(&self) -> bool {
1054        false
1055    }
1056
1057    /// The module's part of a client backup.
1058    ///
1059    /// # Errors
1060    ///
1061    /// The default body fails with [`ClientModuleError::Unsupported`]; a
1062    /// module whose [`Self::supports_backup`] is `true` must override it.
1063    async fn backup(&self) -> Result<Self::Backup, ClientModuleError> {
1064        Err(ClientModuleError::Unsupported {
1065            kind: <Self as ClientModule>::kind(),
1066            operation: "backup",
1067        })
1068    }
1069
1070    /// Does this module support being a primary module
1071    ///
1072    /// If it does it must implement:
1073    ///
1074    /// * [`Self::create_final_inputs_and_outputs`]
1075    /// * [`Self::await_primary_module_output`]
1076    /// * [`Self::get_balance`]
1077    /// * [`Self::subscribe_balance_changes`]
1078    fn supports_being_primary(&self) -> PrimaryModuleSupport {
1079        PrimaryModuleSupport::None
1080    }
1081
1082    /// Creates all inputs and outputs necessary to balance the transaction.
1083    ///
1084    /// A returned input also contains:
1085    /// * A set of private keys belonging to the input for signing the
1086    ///   transaction
1087    /// * A closure that generates states belonging to the input. This closure
1088    ///   takes the transaction id of the transaction in which the input was
1089    ///   used and the input index as input since these cannot be known at time
1090    ///   of calling `create_funding_input` and have to be injected later.
1091    ///
1092    /// A returned output also contains:
1093    /// * A closure that generates states belonging to the output. This closure
1094    ///   takes the transaction id of the transaction in which the output was
1095    ///   used and the output index as input since these cannot be known at time
1096    ///   of calling `create_change_output` and have to be injected later.
1097    ///
1098    /// # Errors
1099    ///
1100    /// A module whose funds cannot cover the transaction must fail with
1101    /// [`ClientModuleError::InsufficientBalance`]: the client reports that as
1102    /// [`TransactionSubmitError::InsufficientFunds`], and any other failure as
1103    /// [`TransactionSubmitError::PrimaryModule`]. The default body fails with
1104    /// [`ClientModuleError::Unsupported`].
1105    async fn create_final_inputs_and_outputs(
1106        &self,
1107        _dbtx: &mut DatabaseTransaction<'_>,
1108        _operation_id: OperationId,
1109        _unit: AmountUnit,
1110        _input_amount: Amount,
1111        _output_amount: Amount,
1112    ) -> Result<
1113        (
1114            ClientInputBundle<<Self::Common as ModuleCommon>::Input, Self::States>,
1115            ClientOutputBundle<<Self::Common as ModuleCommon>::Output, Self::States>,
1116        ),
1117        ClientModuleError,
1118    > {
1119        Err(ClientModuleError::Unsupported {
1120            kind: <Self as ClientModule>::kind(),
1121            operation: "create_final_inputs_and_outputs",
1122        })
1123    }
1124
1125    /// Waits for the funds from an output created by
1126    /// [`Self::create_final_inputs_and_outputs`] to become available. This
1127    /// function returning typically implies a change in the output of
1128    /// [`Self::get_balance`].
1129    ///
1130    /// # Errors
1131    ///
1132    /// Fails if the output does not become available, for example because
1133    /// the federation rejected its transaction. The default body fails with
1134    /// [`ClientModuleError::Unsupported`].
1135    async fn await_primary_module_output(
1136        &self,
1137        _operation_id: OperationId,
1138        _out_point: OutPoint,
1139    ) -> Result<(), ClientModuleError> {
1140        Err(ClientModuleError::Unsupported {
1141            kind: <Self as ClientModule>::kind(),
1142            operation: "await_primary_module_output",
1143        })
1144    }
1145
1146    /// Returns the balance held by this module and available for funding
1147    /// transactions.
1148    async fn get_balance(&self, _dbtx: &mut DatabaseTransaction<'_>, _unit: AmountUnit) -> Amount {
1149        unimplemented!()
1150    }
1151
1152    /// Returns the balance held by this module and available for funding
1153    /// transactions.
1154    async fn get_balances(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Amounts {
1155        unimplemented!()
1156    }
1157
1158    /// Returns a stream that will output the updated module balance each time
1159    /// it changes.
1160    async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1161        unimplemented!()
1162    }
1163
1164    /// Leave the federation
1165    ///
1166    /// While technically there's nothing stopping the client from just
1167    /// abandoning Federation at any point by deleting all the related
1168    /// local data, it is useful to make sure it's safe beforehand.
1169    ///
1170    /// This call indicates the desire of the caller client code
1171    /// to orderly and safely leave the Federation by this module instance.
1172    /// The goal of the implementations is to fulfil that wish,
1173    /// giving prompt and informative feedback if it's not yet possible.
1174    ///
1175    /// The client module implementation should handle the request
1176    /// and return as fast as possible avoiding blocking for longer than
1177    /// necessary. This would usually involve some combination of:
1178    ///
1179    /// * recording the state of being in process of leaving the Federation to
1180    ///   prevent initiating new conditions that could delay its completion;
1181    /// * performing any fast to complete cleanup/exit logic;
1182    /// * initiating any time-consuming logic (e.g. canceling outstanding
1183    ///   contracts), as background jobs, tasks machines, etc.
1184    /// * checking for any conditions indicating it might not be safe to leave
1185    ///   at the moment.
1186    ///
1187    /// This function should return `Ok` only if from the perspective
1188    /// of this module instance, it is safe to delete client data and
1189    /// stop using it, with no further actions (like background jobs) required
1190    /// to complete.
1191    ///
1192    /// This function should return an error if it's not currently possible
1193    /// to safely (e.g. without losing funds) leave the Federation.
1194    /// It should avoid running indefinitely trying to complete any cleanup
1195    /// actions necessary to reach a clean state, preferring spawning new
1196    /// state machines and returning an informative error about cleanup
1197    /// still in progress.
1198    ///
1199    /// If any internal task needs to complete, any user action is required,
1200    /// or even external condition needs to be met this function
1201    /// should return a `Err`.
1202    ///
1203    /// Notably modules should not disable interaction that might be necessary
1204    /// for the user (possibly through other modules) to leave the Federation.
1205    /// In particular a Mint module should retain ability to create new notes,
1206    /// and LN module should retain ability to send funds out.
1207    ///
1208    /// Calling code must NOT assume that a module that once returned `Ok`,
1209    /// will not return `Err` at later point. E.g. a Mint module might have
1210    /// no outstanding balance at first, but other modules winding down
1211    /// might "cash-out" to Ecash.
1212    ///
1213    /// Before leaving the Federation and deleting any state the calling code
1214    /// must collect a full round of `Ok` from all the modules.
1215    ///
1216    /// Calling code should allow the user to override and ignore any
1217    /// outstanding errors, after sufficient amount of warnings. Ideally,
1218    /// this should be done on per-module basis, to avoid mistakes.
1219    ///
1220    /// # Errors
1221    ///
1222    /// The default body fails with [`ClientModuleError::Unsupported`], since a
1223    /// module that does not implement this cannot tell whether leaving is
1224    /// safe.
1225    async fn leave(&self, _dbtx: &mut DatabaseTransaction<'_>) -> Result<(), ClientModuleError> {
1226        Err(ClientModuleError::Unsupported {
1227            kind: <Self as ClientModule>::kind(),
1228            operation: "leave",
1229        })
1230    }
1231}
1232
1233/// Type-erased version of [`ClientModule`]
1234#[apply(async_trait_maybe_send!)]
1235pub trait IClientModule: Debug {
1236    fn as_any(&self) -> &(maybe_add_send_sync!(dyn std::any::Any));
1237
1238    fn as_any_arc(self: Arc<Self>) -> Arc<maybe_add_send_sync!(dyn std::any::Any + 'static)>;
1239
1240    fn decoder(&self) -> Decoder;
1241
1242    fn context(&self, instance: ModuleInstanceId) -> DynContext;
1243
1244    async fn start(&self);
1245
1246    async fn handle_cli_command(
1247        &self,
1248        args: &[ffi::OsString],
1249    ) -> Result<serde_json::Value, ClientModuleError>;
1250
1251    async fn handle_rpc(
1252        &self,
1253        method: String,
1254        request: serde_json::Value,
1255    ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>>;
1256
1257    fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts>;
1258
1259    fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts>;
1260
1261    fn supports_backup(&self) -> bool;
1262
1263    async fn backup(
1264        &self,
1265        module_instance_id: ModuleInstanceId,
1266    ) -> Result<DynModuleBackup, ClientModuleError>;
1267
1268    fn supports_being_primary(&self) -> PrimaryModuleSupport;
1269
1270    async fn create_final_inputs_and_outputs(
1271        &self,
1272        module_instance: ModuleInstanceId,
1273        dbtx: &mut DatabaseTransaction<'_>,
1274        operation_id: OperationId,
1275        unit: AmountUnit,
1276        input_amount: Amount,
1277        output_amount: Amount,
1278    ) -> Result<(ClientInputBundle, ClientOutputBundle), ClientModuleError>;
1279
1280    async fn await_primary_module_output(
1281        &self,
1282        operation_id: OperationId,
1283        out_point: OutPoint,
1284    ) -> Result<(), ClientModuleError>;
1285
1286    async fn get_balance(
1287        &self,
1288        module_instance: ModuleInstanceId,
1289        dbtx: &mut DatabaseTransaction<'_>,
1290        unit: AmountUnit,
1291    ) -> Amount;
1292
1293    async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()>;
1294}
1295
1296#[apply(async_trait_maybe_send!)]
1297impl<T> IClientModule for T
1298where
1299    T: ClientModule,
1300{
1301    fn as_any(&self) -> &(maybe_add_send_sync!(dyn Any)) {
1302        self
1303    }
1304
1305    fn as_any_arc(self: Arc<Self>) -> Arc<maybe_add_send_sync!(dyn Any + 'static)> {
1306        self
1307    }
1308
1309    fn decoder(&self) -> Decoder {
1310        T::decoder()
1311    }
1312
1313    fn context(&self, instance: ModuleInstanceId) -> DynContext {
1314        DynContext::from_typed(instance, <T as ClientModule>::context(self))
1315    }
1316
1317    async fn start(&self) {
1318        <T as ClientModule>::start(self).await;
1319    }
1320
1321    async fn handle_cli_command(
1322        &self,
1323        args: &[ffi::OsString],
1324    ) -> Result<serde_json::Value, ClientModuleError> {
1325        <T as ClientModule>::handle_cli_command(self, args).await
1326    }
1327
1328    async fn handle_rpc(
1329        &self,
1330        method: String,
1331        request: serde_json::Value,
1332    ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>> {
1333        <T as ClientModule>::handle_rpc(self, method, request).await
1334    }
1335
1336    fn input_fee(&self, amount: &Amounts, input: &DynInput) -> Option<Amounts> {
1337        <T as ClientModule>::input_fee(
1338            self,
1339            amount,
1340            input
1341                .as_any()
1342                .downcast_ref()
1343                .expect("Dispatched to correct module"),
1344        )
1345    }
1346
1347    fn output_fee(&self, amount: &Amounts, output: &DynOutput) -> Option<Amounts> {
1348        <T as ClientModule>::output_fee(
1349            self,
1350            amount,
1351            output
1352                .as_any()
1353                .downcast_ref()
1354                .expect("Dispatched to correct module"),
1355        )
1356    }
1357
1358    fn supports_backup(&self) -> bool {
1359        <T as ClientModule>::supports_backup(self)
1360    }
1361
1362    async fn backup(
1363        &self,
1364        module_instance_id: ModuleInstanceId,
1365    ) -> Result<DynModuleBackup, ClientModuleError> {
1366        Ok(DynModuleBackup::from_typed(
1367            module_instance_id,
1368            <T as ClientModule>::backup(self).await?,
1369        ))
1370    }
1371
1372    fn supports_being_primary(&self) -> PrimaryModuleSupport {
1373        <T as ClientModule>::supports_being_primary(self)
1374    }
1375
1376    async fn create_final_inputs_and_outputs(
1377        &self,
1378        module_instance: ModuleInstanceId,
1379        dbtx: &mut DatabaseTransaction<'_>,
1380        operation_id: OperationId,
1381        unit: AmountUnit,
1382        input_amount: Amount,
1383        output_amount: Amount,
1384    ) -> Result<(ClientInputBundle, ClientOutputBundle), ClientModuleError> {
1385        let (inputs, outputs) = <T as ClientModule>::create_final_inputs_and_outputs(
1386            self,
1387            &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1388            operation_id,
1389            unit,
1390            input_amount,
1391            output_amount,
1392        )
1393        .await?;
1394
1395        let inputs = inputs.into_dyn(module_instance);
1396
1397        let outputs = outputs.into_dyn(module_instance);
1398
1399        Ok((inputs, outputs))
1400    }
1401
1402    async fn await_primary_module_output(
1403        &self,
1404        operation_id: OperationId,
1405        out_point: OutPoint,
1406    ) -> Result<(), ClientModuleError> {
1407        <T as ClientModule>::await_primary_module_output(self, operation_id, out_point).await
1408    }
1409
1410    async fn get_balance(
1411        &self,
1412        module_instance: ModuleInstanceId,
1413        dbtx: &mut DatabaseTransaction<'_>,
1414        unit: AmountUnit,
1415    ) -> Amount {
1416        <T as ClientModule>::get_balance(
1417            self,
1418            &mut dbtx.to_ref_with_prefix_module_id(module_instance).0,
1419            unit,
1420        )
1421        .await
1422    }
1423
1424    async fn subscribe_balance_changes(&self) -> BoxStream<'static, ()> {
1425        <T as ClientModule>::subscribe_balance_changes(self).await
1426    }
1427}
1428
1429dyn_newtype_define!(
1430    #[derive(Clone)]
1431    pub DynClientModule(Arc<IClientModule>)
1432);
1433
1434impl DynClientModule {
1435    pub fn as_any_arc(&self) -> Arc<maybe_add_send_sync!(dyn Any + 'static)> {
1436        self.inner.clone().as_any_arc()
1437    }
1438}
1439
1440impl AsRef<maybe_add_send_sync!(dyn IClientModule + 'static)> for DynClientModule {
1441    fn as_ref(&self) -> &maybe_add_send_sync!(dyn IClientModule + 'static) {
1442        self.inner.as_ref()
1443    }
1444}
1445
1446// Re-export types from fedimint_core
1447pub use fedimint_core::{IdxRange, OutPointRange, OutPointRangeIter};
1448
1449pub type StateGenerator<S> = Arc<maybe_add_send_sync!(dyn Fn(OutPointRange) -> Vec<S> + 'static)>;