use std::path::PathBuf;
use std::sync::Arc;
use miden_client::crypto::RpoRandomCoin;
use miden_client::rpc::{Endpoint, GrpcClient, NodeRpcClient};
use miden_client::{Client, ExecutionOptions};
use miden_client_sqlite_store::SqliteStore;
use miden_objects::crypto::dsa::rpo_falcon512::SecretKey;
use miden_objects::{MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
use crate::client::MultisigClient;
use crate::error::{MultisigError, Result};
use crate::keystore::{EcdsaPsmKeyStore, KeyManager, PsmKeyStore};
pub struct MultisigClientBuilder {
miden_endpoint: Option<Endpoint>,
psm_endpoint: Option<String>,
account_dir: Option<PathBuf>,
key_manager: Option<Box<dyn KeyManager>>,
}
impl Default for MultisigClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl MultisigClientBuilder {
pub fn new() -> Self {
Self {
miden_endpoint: None,
psm_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 psm_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.psm_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: impl KeyManager + 'static) -> Self {
self.key_manager = Some(Box::new(key_manager));
self
}
pub fn with_secret_key(mut self, secret_key: SecretKey) -> Self {
self.key_manager = Some(Box::new(PsmKeyStore::new(secret_key)));
self
}
pub fn with_ecdsa_secret_key(
mut self,
secret_key: miden_objects::crypto::dsa::ecdsa_k256_keccak::SecretKey,
) -> Self {
self.key_manager = Some(Box::new(EcdsaPsmKeyStore::new(secret_key)));
self
}
pub fn generate_key(mut self) -> Self {
self.key_manager = Some(Box::new(PsmKeyStore::generate()));
self
}
pub fn generate_ecdsa_key(mut self) -> Self {
self.key_manager = Some(Box::new(EcdsaPsmKeyStore::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 psm_endpoint = self
.psm_endpoint
.ok_or_else(|| MultisigError::MissingConfig("psm_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::NoKeyManager)?;
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,
psm_endpoint,
account_dir,
miden_endpoint,
))
}
}
pub(crate) async fn create_miden_client(
account_dir: &std::path::Path,
endpoint: &Endpoint,
) -> Result<Client<()>> {
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(RpoRandomCoin::new(rng_seed.into()));
let exec_options = ExecutionOptions::new(
Some(MAX_TX_EXECUTION_CYCLES),
MIN_TX_EXECUTION_CYCLES,
true,
true,
)
.map_err(|e| MultisigError::MidenClient(format!("failed to build execution options: {}", e)))?;
let grpc_client = GrpcClient::new(endpoint, 20_000);
let rpc_client: Arc<dyn NodeRpcClient> = Arc::new(grpc_client);
Client::new(
rpc_client,
rng,
store,
None,
exec_options,
Some(20),
Some(256),
None,
None,
)
.await
.map_err(|e| MultisigError::MidenClient(format!("failed to create miden client: {}", e)))
}