diff --git a/src/commands/vault.rs b/src/commands/vault.rs index a3fa4b2bb1c1ec50560251107b1886c1d8f0fb0b..07b40fcf5244172a9444eed482b3f1fc825cdf62 100644 --- a/src/commands/vault.rs +++ b/src/commands/vault.rs @@ -4,6 +4,7 @@ use crate::commands::cesium::compute_g1v1_public_key; use crate::entities::vault_account; use crate::entities::vault_account::{AccountTreeNode, ActiveModel, DbAccountId}; use crate::*; +use crate::keys::seed_from_cesium; use age::secrecy::Secret; use sea_orm::ActiveValue::Set; use sea_orm::{ConnectionTrait, TransactionTrait}; @@ -45,6 +46,30 @@ pub enum Subcommand { /// Crypto scheme to use (sr25519, ed25519) #[clap(short = 'c', long, required = false, default_value = CryptoScheme::Ed25519)] crypto_scheme: CryptoScheme, + + /// Substrate URI to import (non-interactive mode) + #[clap(short = 'u', long, required = false)] + uri: Option<String>, + + /// G1v1 ID (non-interactive mode for g1v1 format) + #[clap(long, required = false)] + g1v1_id: Option<String>, + + /// G1v1 password (non-interactive mode for g1v1 format) + #[clap(long, required = false)] + g1v1_password: Option<String>, + + /// Password for encrypting the key (non-interactive mode) + #[clap(short = 'p', long, required = false)] + password: Option<String>, + + /// Use empty password (non-interactive mode) + #[clap(long, required = false)] + no_password: bool, + + /// Name for the wallet entry (non-interactive mode) + #[clap(short = 'n', long, required = false)] + name: Option<String>, }, /// Add a derivation to an existing SS58 Address #[clap(long_about = "Add a derivation to an existing SS58 Address.\n\ @@ -250,9 +275,49 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE let mnemonic = bip39::Mnemonic::generate(12).unwrap(); println!("{mnemonic}"); } - Subcommand::Import { secret_format, crypto_scheme } => { - let vault_data_for_import = - prompt_secret_and_compute_vault_data_to_import(secret_format, crypto_scheme)?; + Subcommand::Import { secret_format, crypto_scheme, uri, g1v1_id, g1v1_password, password, no_password, name } => { + let vault_data_for_import = if let Some(uri_str) = uri { + // Non-interactive mode with provided URI + if secret_format != SecretFormat::Substrate { + return Err(GcliError::Input(format!( + "URI can only be provided directly with secret_format=substrate, got: {:?}", + secret_format + ))); + } + + // Create keypair from provided URI + let key_pair = compute_keypair(crypto_scheme, &uri_str)?; + + VaultDataToImport { + secret_format, + secret_suri: uri_str, + key_pair, + } + } else if let (Some(id), Some(pwd)) = (&g1v1_id, &g1v1_password) { + // Non-interactive mode with provided G1v1 ID and password + if secret_format != SecretFormat::G1v1 { + return Err(GcliError::Input(format!( + "G1v1 ID and password can only be provided directly with secret_format=g1v1, got: {:?}", + secret_format + ))); + } + + // Create keypair from provided G1v1 ID and password + let seed = seed_from_cesium(id, pwd); + let secret_suri = format!("0x{}", hex::encode(seed)); + + // G1v1 always uses Ed25519 + let key_pair = compute_keypair(CryptoScheme::Ed25519, &secret_suri)?; + + VaultDataToImport { + secret_format, + secret_suri, + key_pair, + } + } else { + // Interactive mode + prompt_secret_and_compute_vault_data_to_import(secret_format, crypto_scheme)? + }; //Extra check for SecretFormat::G1v1 (old cesium) - showing the G1v1 cesium public key for confirmation if secret_format == SecretFormat::G1v1 { @@ -260,17 +325,28 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE "The G1v1 public key for the provided secret is: '{}'", compute_g1v1_public_key(&vault_data_for_import.key_pair)? ); - let confirmed = inputs::confirm_action("Is it the correct one (if not, you should try again to input G1v1 id/password) ?".to_string())?; - if !confirmed { - return Ok(()); + + // Skip confirmation in non-interactive mode + let is_non_interactive_g1v1 = g1v1_id.is_some() && g1v1_password.is_some(); + if !is_non_interactive_g1v1 { + let confirmed = inputs::confirm_action("Is it the correct one (if not, you should try again to input G1v1 id/password) ?".to_string())?; + if !confirmed { + return Ok(()); + } } } let txn = db.begin().await?; - - println!(); + + // Handle password in non-interactive mode + let provided_password = if no_password { + Some(String::new()) // Empty password + } else { + password + }; + let _account = - create_base_account_for_vault_data_to_import(&txn, &vault_data_for_import, None, Some(crypto_scheme)) + create_base_account_for_vault_data_to_import(&txn, &vault_data_for_import, provided_password.as_ref(), Some(crypto_scheme), name) .await?; txn.commit().await?; @@ -489,6 +565,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE &vault_data_to_import, Some(&vault_data_from_file.password), Some(CryptoScheme::Ed25519), + None, ) .await; @@ -757,6 +834,7 @@ pub async fn create_base_account_for_vault_data_to_import<C>( vault_data: &VaultDataToImport, password_opt: Option<&String>, crypto_scheme: Option<CryptoScheme>, + name_opt: Option<String>, ) -> Result<vault_account::Model, GcliError> where C: ConnectionTrait, @@ -780,14 +858,16 @@ where existing_vault_account ))? { true => { - println!( - "(Optional) Enter a name for the vault entry (leave empty to remove the name)" - ); - let name = inputs::prompt_vault_name_and_check_availability( - db_tx, - existing_vault_account.name.as_ref(), - ) - .await?; + let name = if let Some(name) = name_opt { + Some(name) + } else { + println!("(Optional) Enter a name for the vault entry (leave empty to remove the name)"); + inputs::prompt_vault_name_and_check_availability( + db_tx, + existing_vault_account.name.as_ref(), + ) + .await? + }; // Since links are made based on address / parent(address) we can just edit the existing entry and it should be fine let mut vault_account: ActiveModel = existing_vault_account.into(); @@ -797,7 +877,7 @@ where map_secret_format_to_crypto_scheme(vault_data.secret_format, crypto_scheme).into(), )); vault_account.encrypted_suri = Set(Some(encrypted_suri)); - vault_account.name = Set(name.clone()); + vault_account.name = Set(name); vault_account.secret_format = Set(Some(vault_data.secret_format.into())); let updated_vault_account = vault_account::update_account(db_tx, vault_account).await?; @@ -815,8 +895,12 @@ where let encrypted_suri = compute_encrypted_suri(password, vault_data.secret_suri.clone())?; - println!("(Optional) Enter a name for the vault entry"); - let name = inputs::prompt_vault_name_and_check_availability(db_tx, None).await?; + let name = if let Some(name) = name_opt { + Some(name) + } else { + println!("(Optional) Enter a name for the vault entry"); + inputs::prompt_vault_name_and_check_availability(db_tx, None).await? + }; let crypto_scheme = map_secret_format_to_crypto_scheme(secret_format, crypto_scheme);