fedimint-client 0.12.0-beta.2

Library for sending transactions to the Fedimint federation.
Documentation
use std::sync::Arc;

use fedimint_client_module::OperationId;
use fedimint_client_module::oplog::OperationLogEntry;
use fedimint_core::config::{ClientConfig, FederationId};
use fedimint_core::invite_code::InviteCode;
use fedimint_core::module::AmountUnit;
use fedimint_core::util::ffi::UniffiError;
use fedimint_core::{Amount, PeerId};
use fedimint_eventlog::{EventLogId, PersistedLogEntry};
use futures::StreamExt;

use crate::db::ChronologicalOperationLogKey;
use crate::{Client, ClientHandle, client};

type Result<T> = std::result::Result<T, UniffiError>;

#[derive(uniffi::Record)]
pub struct ListOperationsResponse {
    pub last_seen: ChronologicalOperationLogKey,
    pub operations: OperationLogEntry,
}

#[uniffi::export(async_runtime = "tokio")]
impl ClientHandle {
    pub async fn get_balance(&self) -> Result<Amount> {
        let client = client_for_handle(self)?;
        Ok(client.get_balance_for_btc().await?)
    }

    #[uniffi::method(name = "subscribe_balance_changes")]
    pub fn subscribe_balance_changes_uniffi(
        &self,
        unit: AmountUnit,
        callback: Box<dyn BalanceChangeCallback>,
    ) -> Result<()> {
        let client = client_for_handle(self)?;
        let task_client = client.clone();
        let _ = client.spawn_cancellable("uniffi-subscribe-balance-changes", async move {
            let mut stream = task_client.subscribe_balance_changes(unit).await;
            while let Some(balance) = stream.next().await {
                callback.on_balance_change(balance);
            }
        });

        Ok(())
    }

    pub async fn get_config(&self) -> Result<ClientConfig> {
        let client = client_for_handle(self)?;
        Ok(client.config().await)
    }

    pub fn get_federation_id(&self) -> Result<FederationId> {
        let client = client_for_handle(self)?;
        Ok(client.federation_id())
    }

    pub async fn get_invite_code(&self, peer: PeerId) -> Result<Option<InviteCode>> {
        let client = client_for_handle(self)?;
        Ok(client.invite_code(peer).await)
    }

    pub async fn get_operation(
        &self,
        operation_id: OperationId,
    ) -> Result<Option<OperationLogEntry>> {
        let client = client_for_handle(self)?;
        let operation = client.operation_log().get_operation(operation_id).await;
        Ok(operation)
    }

    pub async fn list_operations(
        &self,
        limit: Option<u64>,
        last_seen: Option<ChronologicalOperationLogKey>,
    ) -> Result<Vec<ListOperationsResponse>> {
        let client = client_for_handle(self)?;
        let limit = limit
            .unwrap_or(client::DEFAULT_EVENT_LOG_PAGE_SIZE)
            .min(client::MAX_EVENT_LOG_PAGE_SIZE) as usize;
        let operations = client
            .operation_log()
            .paginate_operations_rev(limit, last_seen)
            .await;
        Ok(operations
            .into_iter()
            .map(|(last_seen, operations)| ListOperationsResponse {
                last_seen,
                operations,
            })
            .collect())
    }

    #[uniffi::method(name = "get_event_log")]
    pub async fn get_event_log_uniffi(
        &self,
        pos: Option<EventLogId>,
        limit: u64,
    ) -> Result<Vec<PersistedLogEntry>> {
        let client = client_for_handle(self)?;
        let limit = limit.min(client::MAX_EVENT_LOG_PAGE_SIZE);
        Ok(client.get_event_log(pos, limit).await)
    }

    pub async fn session_count(&self) -> Result<u64> {
        let client = client_for_handle(self)?;
        Ok(client.fetch_session_count().await?)
    }

    pub fn has_pending_recoveries(&self) -> Result<bool> {
        let client = client_for_handle(self)?;
        Ok(client.has_pending_recoveries())
    }

    #[uniffi::method(name = "wait_for_all_recoveries")]
    pub async fn wait_for_all_recoveries_uniffi(&self) -> Result<()> {
        let client = client_for_handle(self)?;
        client.wait_for_all_recoveries().await?;
        Ok(())
    }

    #[uniffi::method(name = "subscribe_to_recovery_progress")]
    pub fn subscribe_to_recovery_progress_uniffi(
        &self,
        callback: Box<dyn RecoveryProgressCallback>,
    ) -> Result<()> {
        let client = client_for_handle(self)?;
        let task_client = client.clone();
        let _ = client.spawn_cancellable("uniffi-subscribe-recovery-progress", async move {
            let mut stream = task_client.subscribe_to_recovery_progress();
            while let Some((module_id, progress)) = stream.next().await {
                callback.on_recovery_progress(module_id, progress.complete, progress.total);
            }
        });

        Ok(())
    }
}

#[cfg(feature = "uniffi")]
fn client_for_handle(handle: &ClientHandle) -> Result<Arc<Client>> {
    handle
        .inner_arc()
        .ok_or_else(|| UniffiError::General("Client handle is already shut down".to_owned()))
}

#[cfg(feature = "uniffi")]
#[uniffi::export(callback_interface)]
pub trait BalanceChangeCallback: Send + Sync {
    fn on_balance_change(&self, balance: Amount);
}

#[cfg(feature = "uniffi")]
#[uniffi::export(callback_interface)]
pub trait RecoveryProgressCallback: Send + Sync {
    fn on_recovery_progress(&self, module_id: u16, complete: u32, total: u32);
}