Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Bumped web-client version in package.json after merging main into next.
* Added support for getting specific vault and storage elements from `Store` along with their proofs ([#1164](https://github.com/0xMiden/miden-client/pull/1164)).
* Modified the RPC client to avoid reconnection when setting commitment header ([#1166](https://github.com/0xMiden/miden-client/pull/1166)).
* Added account creation from miden packages (#TBD).

## 0.11.3 (2025-09-08)

Expand Down
147 changes: 129 additions & 18 deletions bin/miden-cli/src/commands/new_account.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::PathBuf;
use std::vec;

use clap::{Parser, ValueEnum};
use miden_client::Client;
use miden_client::account::component::COMPONENT_TEMPLATE_EXTENSION;
use miden_client::account::component::{COMPONENT_TEMPLATE_EXTENSION, MIDEN_PACKAGE_EXTENSION};
use miden_client::account::{Account, AccountBuilder, AccountStorageMode, AccountType};
use miden_client::auth::{AuthSecretKey, TransactionAuthenticator};
use miden_client::crypto::SecretKey;
Expand All @@ -19,6 +20,7 @@ use miden_objects::account::{
InitStorageData,
StorageValueName,
};
use miden_objects::vm::Package;
use rand::RngCore;
use tracing::debug;

Expand Down Expand Up @@ -84,6 +86,10 @@ pub struct NewWalletCmd {
/// Optional list of files specifying additional components to add to the account.
#[arg(short, long)]
pub extra_components: Vec<PathBuf>,
/// Optional list of files containing a Miden Package in `.masp` form from which a
/// component template can be extracted.
#[arg(short, long)]
pub packages: Vec<PathBuf>,
/// Optional file path to a TOML file containing a list of key/values used for initializing
/// storage. Each of these keys should map to the templated storage values within the passed
/// list of component templates. The user will be prompted to provide values for any keys not
Expand Down Expand Up @@ -118,6 +124,7 @@ impl NewWalletCmd {
account_type,
self.storage_mode.into(),
&component_template_paths,
&self.packages,
self.init_storage_data_path.clone(),
self.deploy,
)
Expand Down Expand Up @@ -153,10 +160,15 @@ pub struct NewAccountCmd {
/// Account type to create.
#[arg(long, value_enum)]
pub account_type: CliAccountType,
/// Optional list of files specifying additional component template files to add to the
/// account.
/// List of files specifying component template files for the account.
/// At least one component template is required, either specified by
/// [[`NewAccountCmd::component_templates`]] or by [[`NewAccountCmd::packages`]].
#[arg(short, long)]
pub component_templates: Vec<PathBuf>,
/// List of files containing a Miden Package in `.masp` form from which a
/// component template is extracted.
#[arg(short, long)]
pub packages: Vec<PathBuf>,
/// Optional file path to a TOML file containing a list of key/values used for initializing
/// storage. Each of these keys should map to the templated storage values within the passed
/// list of component templates. The user will be prompted to provide values for any keys not
Expand All @@ -181,6 +193,7 @@ impl NewAccountCmd {
self.account_type.into(),
self.storage_mode.into(),
&self.component_templates,
&self.packages,
self.init_storage_data_path.clone(),
self.deploy,
)
Expand All @@ -200,28 +213,125 @@ impl NewAccountCmd {
// HELPERS
// ================================================================================================

/// Reads component templates from the given file paths.
// TODO: IO errors should have more context
fn load_component_templates(paths: &[PathBuf]) -> Result<Vec<AccountComponentTemplate>, CliError> {
type ComponentPath = PathBuf;
type PackagePath = PathBuf;
/// Reads component templates and [[`miden_core::vm::Package`]]s from the given file paths.
fn load_component_templates(
component_paths: &[ComponentPath],
package_paths: &[PackagePath],
) -> Result<Vec<AccountComponentTemplate>, CliError> {
let (cli_config, _) = load_config_file()?;
let components_base_dir = &cli_config.component_template_directory;
let mut templates = Vec::new();
for path in paths {
// Set extension to COMPONENT_TEMPLATE_EXTENSION in case user did not
let path = if path.extension().is_none() {
path.with_extension(COMPONENT_TEMPLATE_EXTENSION)
} else {
path.clone()
};
let bytes = fs::read(components_base_dir.join(path))?;

let components_base_dir = &cli_config.component_template_directory;
for path in component_paths {
let file_name = match path.extension() {
None => {
// Set extension to COMPONENT_TEMPLATE_EXTENSION in case user
// did not
Ok(path.with_extension(COMPONENT_TEMPLATE_EXTENSION))
},
Some(extension) => {
if extension == OsStr::new(COMPONENT_TEMPLATE_EXTENSION) {
Ok(path.clone())
} else {
let error = std::io::Error::new(
std::io::ErrorKind::InvalidFilename,
format!(
"{} has an invalid file extension: '{}'. \
Expected: {COMPONENT_TEMPLATE_EXTENSION}",
path.display(),
extension.display()
),
);
Err(CliError::AccountComponentError(
Box::new(error),
format!(
"failed to read account component template from {}",
path.display()
),
))
}
},
}?;
let path = components_base_dir.join(&file_name);

let bytes = fs::read(&path).map_err(|e| {
CliError::AccountComponentError(
Box::new(e),
format!("failed to read account component template from {}", path.display()),
)
})?;

let template = AccountComponentTemplate::read_from_bytes(&bytes).map_err(|e| {
CliError::AccountComponentError(
Box::new(e),
"failed to read account component template".into(),
format!("failed to deserialize account component template from {}", path.display()),
)
})?;

templates.push(template);
}

let packages_dir = &cli_config.package_directory;
for path in package_paths {
// If a user passes in a file with the `.masp` file extension, then we
// leave the passed in path as is; since it probably is a full path.
// This is the case with cargo-miden, which displays the full path to
// stdout after compilation finishes.
let path = match path.extension() {
None => {
let path = path.with_extension(MIDEN_PACKAGE_EXTENSION);
Ok(packages_dir.join(path))
},
Some(extension) => {
if extension == OsStr::new(MIDEN_PACKAGE_EXTENSION) {
Ok(path.clone())
} else {
let error = std::io::Error::new(
std::io::ErrorKind::InvalidFilename,
format!(
"{} has an invalid file extension: '{}'. \
Expected: {MIDEN_PACKAGE_EXTENSION}",
path.display(),
extension.display()
),
);
Err(CliError::AccountComponentError(
Box::new(error),
format!("failed to read Package from {}", path.display()),
))
}
},
}?;

let bytes = fs::read(&path).map_err(|e| {
CliError::AccountComponentError(
Box::new(e),
format!("failed to read Package from {}", path.display()),
)
})?;

let package = Package::read_from_bytes(&bytes).map_err(|e| {
CliError::AccountComponentError(
Box::new(e),
format!("failed to deserialize Package in {}", path.display()),
)
})?;

let template = AccountComponentTemplate::try_from(package).map_err(|e| {
CliError::AccountComponentError(
Box::new(e),
format!(
"failed to read account component template from Package in {}",
path.display()
),
)
})?;

templates.push(template);
}

Ok(templates)
}

Expand All @@ -248,18 +358,19 @@ async fn create_client_account<AUTH: TransactionAuthenticator + Sync + 'static>(
account_type: AccountType,
storage_mode: AccountStorageMode,
component_template_paths: &[PathBuf],
package_paths: &[PathBuf],
init_storage_data_path: Option<PathBuf>,
deploy: bool,
) -> Result<Account, CliError> {
if component_template_paths.is_empty() {
if component_template_paths.is_empty() && package_paths.is_empty() {
return Err(CliError::InvalidArgument(
"account must contain at least one component".into(),
));
}

// Load the component templates and initialization storage data.
debug!("Loading component templates...");
let component_templates = load_component_templates(component_template_paths)?;
let component_templates = load_component_templates(component_template_paths, package_paths)?;
debug!("Loaded {} component templates", component_templates.len());
debug!("Loading initialization storage data...");
let init_storage_data = load_init_storage_data(init_storage_data_path)?;
Expand Down
4 changes: 4 additions & 0 deletions bin/miden-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::errors::CliError;

const TOKEN_SYMBOL_MAP_FILEPATH: &str = "token_symbol_map.toml";
const DEFAULT_COMPONENT_TEMPLATE_DIR: &str = "./templates";
const DEFAULT_PACKAGES_DIR: &str = "./packages";

// CLI CONFIG
// ================================================================================================
Expand All @@ -32,6 +33,8 @@ pub struct CliConfig {
pub remote_prover_endpoint: Option<CliEndpoint>,
/// Path to the directory from where account component template files will be loaded.
pub component_template_directory: PathBuf,
/// Path to the directory from where [[`miden_core::vm::Package`]]s will be loaded.
pub package_directory: PathBuf,
/// Maximum number of blocks the client can be behind the network for transactions and account
/// proofs to be considered valid.
pub max_block_number_delta: Option<u32>,
Expand Down Expand Up @@ -69,6 +72,7 @@ impl Default for CliConfig {
token_symbol_map_filepath: Path::new(TOKEN_SYMBOL_MAP_FILEPATH).to_path_buf(),
remote_prover_endpoint: None,
component_template_directory: Path::new(DEFAULT_COMPONENT_TEMPLATE_DIR).to_path_buf(),
package_directory: Path::new(DEFAULT_PACKAGES_DIR).to_path_buf(),
max_block_number_delta: None,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/rust-client/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ use crate::store::{AccountRecord, AccountStatus};

pub mod component {
pub const COMPONENT_TEMPLATE_EXTENSION: &str = "mct";
pub const MIDEN_PACKAGE_EXTENSION: &str = "masp";

pub use miden_lib::account::auth::AuthRpoFalcon512;
pub use miden_lib::account::faucets::{BasicFungibleFaucet, FungibleFaucetExt};
Expand Down
Loading