use std::path::PathBuf;
use std::sync::Arc;
use miden_client::DebugMode;
use miden_client::builder::ClientBuilder;
use miden_client::keystore::FilesystemKeyStore;
use miden_client::rpc::Endpoint;
use miden_client_sqlite_store::SqliteStore;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey as EcdsaSecretKey;
use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey;
use miden_protocol::crypto::rand::RandomCoin;
use crate::MidenSdkClient;
use crate::client::MultisigClient;
use crate::error::{MultisigError, Result};
use crate::keystore::{EcdsaGuardianKeyStore, GuardianKeyStore, KeyManager};
fn configured_client_builder(endpoint: &Endpoint) -> ClientBuilder<FilesystemKeyStore> {
if endpoint == &Endpoint::devnet() {
ClientBuilder::<FilesystemKeyStore>::for_devnet()
} else if endpoint == &Endpoint::testnet() {
ClientBuilder::<FilesystemKeyStore>::for_testnet()
} else if endpoint == &Endpoint::localhost() {
ClientBuilder::<FilesystemKeyStore>::for_localhost()
} else {
ClientBuilder::<FilesystemKeyStore>::new().grpc_client(endpoint, Some(20_000))
}
}
pub struct MultisigClientBuilder {
miden_endpoint: Option<Endpoint>,
guardian_endpoint: Option<String>,
account_dir: Option<PathBuf>,
key_manager: Option<Arc<dyn KeyManager>>,
}
impl Default for MultisigClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl MultisigClientBuilder {
pub fn new() -> Self {
Self {
miden_endpoint: None,
guardian_endpoint: None,
account_dir: None,
key_manager: None,
}
}
pub fn miden_endpoint(mut self, endpoint: Endpoint) -> Self {
self.miden_endpoint = Some(endpoint);
self
}
pub fn guardian_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.guardian_endpoint = Some(endpoint.into());
self
}
pub fn account_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.account_dir = Some(path.into());
self
}
pub fn key_manager(mut self, key_manager: Box<dyn KeyManager>) -> Self {
self.key_manager = Some(key_manager.into());
self
}
pub fn with_secret_key(mut self, secret_key: SecretKey) -> Self {
self.key_manager = Some(Arc::new(GuardianKeyStore::new(secret_key)));
self
}
pub fn with_ecdsa_secret_key(mut self, secret_key: EcdsaSecretKey) -> Self {
self.key_manager = Some(Arc::new(EcdsaGuardianKeyStore::new(secret_key)));
self
}
pub fn generate_key(mut self) -> Self {
self.key_manager = Some(Arc::new(GuardianKeyStore::generate()));
self
}
pub fn generate_ecdsa_key(mut self) -> Self {
self.key_manager = Some(Arc::new(EcdsaGuardianKeyStore::generate()));
self
}
pub async fn build(self) -> Result<MultisigClient> {
let miden_endpoint = self
.miden_endpoint
.ok_or_else(|| MultisigError::MissingConfig("miden_endpoint".to_string()))?;
let guardian_endpoint = self
.guardian_endpoint
.ok_or_else(|| MultisigError::MissingConfig("guardian_endpoint".to_string()))?;
let account_dir = self
.account_dir
.ok_or_else(|| MultisigError::MissingConfig("account_dir".to_string()))?;
let key_manager = self.key_manager.ok_or(MultisigError::NoSigner)?;
std::fs::create_dir_all(&account_dir).map_err(|e| {
MultisigError::MidenClient(format!("failed to create account dir: {}", e))
})?;
let miden_client = create_miden_client(&account_dir, &miden_endpoint).await?;
Ok(MultisigClient::new(
miden_client,
key_manager,
guardian_endpoint,
account_dir,
miden_endpoint,
))
}
}
pub(crate) async fn create_miden_client(
account_dir: &std::path::Path,
endpoint: &Endpoint,
) -> Result<MidenSdkClient> {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let random_suffix: u32 = rand::random();
let store_path = account_dir.join(format!(
"miden-client-{}-{}.sqlite",
timestamp, random_suffix
));
let store = SqliteStore::new(store_path)
.await
.map_err(|e| MultisigError::MidenClient(format!("failed to open SQLite store: {}", e)))?;
let store = Arc::new(store);
let rng_seed: [u32; 4] = rand::random();
let rng = Box::new(RandomCoin::new(rng_seed.into()));
configured_client_builder(endpoint)
.store(store)
.rng(rng)
.in_debug_mode(DebugMode::Enabled)
.tx_discard_delta(Some(20))
.max_block_number_delta(256)
.build()
.await
.map_err(|e| MultisigError::MidenClient(format!("failed to create miden client: {}", e)))
}