fedimint_client_uniffi/
lib.rs

1use std::sync::Arc;
2
3use fedimint_client_rpc::{RpcGlobalState, RpcRequest, RpcResponse, RpcResponseHandler};
4use fedimint_connectors::ConnectorRegistry;
5use fedimint_core::db::Database;
6
7uniffi::setup_scaffolding!();
8
9const DB_FILE_NAME: &str = "fedimint.redb";
10
11#[derive(Debug, thiserror::Error, uniffi::Error)]
12pub enum FedimintError {
13    #[error("Database initialization failed: {msg}")]
14    DatabaseError { msg: String },
15
16    #[error("Failed to initialize networking: {msg}")]
17    NetworkingError { msg: String },
18
19    #[error("Failed to create async runtime: {msg}")]
20    RuntimeError { msg: String },
21
22    #[error("Invalid request JSON: {msg}")]
23    InvalidRequest { msg: String },
24
25    #[error("General error: {msg}")]
26    General { msg: String },
27}
28
29#[uniffi::export(callback_interface)]
30pub trait RpcCallback: Send + Sync {
31    fn on_response(&self, response_json: String);
32}
33
34#[derive(uniffi::Object)]
35pub struct RpcHandler {
36    state: Arc<RpcGlobalState>,
37    runtime: tokio::runtime::Runtime,
38}
39
40#[uniffi::export]
41impl RpcHandler {
42    #[uniffi::constructor]
43    pub fn new(db_path: String) -> Result<Arc<Self>, FedimintError> {
44        let runtime = tokio::runtime::Runtime::new()
45            .map_err(|e| FedimintError::RuntimeError { msg: e.to_string() })?;
46
47        let state = runtime.block_on(async {
48            let connectors = ConnectorRegistry::build_from_client_env()
49                .map_err(|e| FedimintError::General { msg: e.to_string() })?
50                .bind()
51                .await
52                .map_err(|e| FedimintError::NetworkingError { msg: e.to_string() })?;
53            let db = create_database(&db_path)
54                .await
55                .map_err(|e| FedimintError::DatabaseError { msg: e.to_string() })?;
56
57            Ok(Arc::new(RpcGlobalState::new(connectors, db)))
58        })?;
59
60        Ok(Arc::new(Self { state, runtime }))
61    }
62
63    pub fn rpc(
64        &self,
65        request_json: String,
66        callback: Box<dyn RpcCallback>,
67    ) -> Result<(), FedimintError> {
68        let request: RpcRequest = serde_json::from_str(&request_json)
69            .map_err(|e| FedimintError::InvalidRequest { msg: e.to_string() })?;
70
71        let handled = self
72            .state
73            .clone()
74            .handle_rpc(request, CallbackWrapper(callback));
75
76        if let Some(task) = handled.task {
77            self.runtime.spawn(task);
78        }
79
80        Ok(())
81    }
82}
83
84struct CallbackWrapper(Box<dyn RpcCallback>);
85
86impl RpcResponseHandler for CallbackWrapper {
87    fn handle_response(&self, response: RpcResponse) {
88        let json = serde_json::to_string(&response).expect("Failed to serialize RPC response");
89        self.0.on_response(json);
90    }
91}
92
93async fn create_database(path: &str) -> anyhow::Result<Database> {
94    tokio::fs::create_dir_all(path).await?;
95
96    let db_path = std::path::Path::new(path).join(DB_FILE_NAME);
97    let db = fedimint_rocksdb::RocksDb::open(db_path).await?;
98
99    Ok(Database::new(db, Default::default()))
100}