use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::time::Duration;
use clap::Parser;
use tracing::info;
use crate::CLIENT_CONFIG_FILE_NAME;
use crate::config::{
CliConfig,
CliEndpoint,
MIDEN_DIR,
Network,
NoteTransportConfig,
get_global_miden_dir,
get_local_miden_dir,
};
use crate::errors::CliError;
const BASIC_WALLET_PACKAGE: (&str, &[u8]) = (
"basic-wallet.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/", "basic-wallet.masp")),
);
const FAUCET_PACKAGE: (&str, &[u8]) = (
"basic-fungible-faucet.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/", "basic-fungible-faucet.masp")),
);
const BASIC_AUTH_PACKAGE: (&str, &[u8]) = (
"auth/basic-auth.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/auth/", "basic-auth.masp")),
);
const ECDSA_AUTH_PACKAGE: (&str, &[u8]) = (
"auth/ecdsa-auth.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/auth/", "ecdsa-auth.masp")),
);
const ACL_AUTH_PACKAGE: (&str, &[u8]) = (
"auth/acl-auth.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/auth/", "acl-auth.masp")),
);
const NO_AUTH_PACKAGE: (&str, &[u8]) = (
"auth/no-auth.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/auth/", "no-auth.masp")),
);
const MULTISIG_AUTH_PACKAGE: (&str, &[u8]) = (
"auth/multisig-auth.masp",
include_bytes!(concat!(env!("OUT_DIR"), "/packages/auth/", "multisig-auth.masp")),
);
const DEFAULT_INCLUDED_PACKAGES: [(&str, &[u8]); 7] = [
BASIC_WALLET_PACKAGE,
FAUCET_PACKAGE,
BASIC_AUTH_PACKAGE,
ECDSA_AUTH_PACKAGE,
NO_AUTH_PACKAGE,
MULTISIG_AUTH_PACKAGE,
ACL_AUTH_PACKAGE,
];
#[derive(Debug, Clone, Parser, Default)]
#[command(
about = "Initialize the client. By default creates a global `.miden` directory in the home directory. \
Use --local to create a local `.miden` directory in the current working directory."
)]
pub struct InitCmd {
#[clap(long)]
local: bool,
#[clap(long, short)]
network: Option<Network>,
#[arg(long)]
store_path: Option<String>,
#[arg(long)]
remote_prover_endpoint: Option<String>,
#[arg(long)]
remote_prover_timeout_ms: Option<u64>,
#[arg(long)]
note_transport_endpoint: Option<String>,
#[clap(long)]
block_delta: Option<u32>,
}
impl InitCmd {
pub fn execute(&self) -> Result<(), CliError> {
let (target_miden_dir, config_type) = if self.local {
(get_local_miden_dir()?, "local")
} else {
(
get_global_miden_dir().map_err(|e| {
CliError::Config(Box::new(e), "Failed to determine home directory".to_string())
})?,
"global",
)
};
let config_file_path = target_miden_dir.join(CLIENT_CONFIG_FILE_NAME);
if config_file_path.exists() {
return Err(CliError::Config(
"Error with the configuration file".to_string().into(),
format!(
"The file \"{}\" already exists in the {} {} directory ({}). Please remove it first or use a different location.",
CLIENT_CONFIG_FILE_NAME,
config_type,
MIDEN_DIR,
target_miden_dir.display()
),
));
}
fs::create_dir_all(&target_miden_dir).map_err(|err| {
CliError::Config(
Box::new(err),
format!(
"failed to create {} {} directory in {}",
config_type,
MIDEN_DIR,
target_miden_dir.display()
),
)
})?;
let mut cli_config = CliConfig::default();
if let Some(network) = &self.network {
cli_config.rpc.endpoint = CliEndpoint::try_from(network.clone())?;
}
if let Some(path) = &self.store_path {
cli_config.store_filepath = PathBuf::from(path);
}
cli_config.remote_prover_endpoint = match &self.remote_prover_endpoint {
Some(rpc) => CliEndpoint::try_from(rpc.as_str()).ok(),
None => None,
};
if let Some(timeout) = self.remote_prover_timeout_ms {
cli_config.remote_prover_timeout = Duration::from_millis(timeout);
}
cli_config.note_transport = if let Some(rpc) = &self.note_transport_endpoint {
Some(NoteTransportConfig {
endpoint: rpc.clone(),
..Default::default()
})
} else {
match &self.network {
Some(Network::Testnet) => Some(NoteTransportConfig::default()),
Some(Network::Devnet) => Some(NoteTransportConfig::devnet()),
_ => None,
}
};
cli_config.max_block_number_delta = self.block_delta;
let config_as_toml_string = toml::to_string_pretty(&cli_config).map_err(|err| {
CliError::Config("failed to serialize config".to_string().into(), err.to_string())
})?;
let mut file_handle = File::options()
.write(true)
.create_new(true)
.open(&config_file_path)
.map_err(|err| {
CliError::Config("failed to create config file".to_string().into(), err.to_string())
})?;
let config_dir = config_file_path.parent().unwrap();
let resolved_package_dir = if cli_config.package_directory.is_relative() {
config_dir.join(&cli_config.package_directory)
} else {
cli_config.package_directory.clone()
};
write_packages_files(&resolved_package_dir)?;
file_handle.write_all(config_as_toml_string.as_bytes()).map_err(|err| {
CliError::Config("failed to write config file".to_string().into(), err.to_string())
})?;
println!(
"Config file successfully created at: {} ({})",
config_file_path.display(),
config_type
);
Ok(())
}
}
fn write_packages_files(packages_dir: &PathBuf) -> Result<(), CliError> {
fs::create_dir_all(packages_dir).map_err(|err| {
CliError::Config(
Box::new(err),
"failed to create account component templates directory".into(),
)
})?;
for package in DEFAULT_INCLUDED_PACKAGES {
let package_path = packages_dir.join(package.0);
if let Some(parent) = package_path.parent() {
fs::create_dir_all(parent).map_err(|err| {
CliError::Config(
Box::new(err),
format!("Failed to create directory {}", parent.display()),
)
})?;
}
let mut lib_file = File::create(&package_path).map_err(|err| {
CliError::Config(
Box::new(err),
format!("Failed to create file at {}", package_path.display()),
)
})?;
lib_file.write_all(package.1).map_err(|err| {
CliError::Config(
Box::new(err),
format!(
"Failed to write package {} into file {}",
package.0,
package_path.display()
),
)
})?;
}
info!("Packages files successfully created in: {:?}", packages_dir);
Ok(())
}