fedimint_client_uniffi/
lib.rs1use 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}