From a94ef7eced09756eecd2c8253758003106aa4d0e Mon Sep 17 00:00:00 2001
From: Nicolas80 <nicolas.pmail@protonmail.com>
Date: Fri, 2 May 2025 18:13:24 +0200
Subject: [PATCH] Adapting `vault inspect`

* Added non-interactive support when providing the `password` or `no-password`
* Adapted to support default human-readable and optional json format
* using log::warn! to be able to get the info of why we don't have a secret seed value if we need to; while not impacting the "standard output" of the command that can still be piped to say `jq`
---
 src/commands/vault.rs | 164 ++++++++++++++++++++++++++++--------------
 src/keys.rs           |  11 +++
 2 files changed, 122 insertions(+), 53 deletions(-)

diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index 16118a3..2f65659 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -119,6 +119,14 @@ pub enum Subcommand {
 	Inspect {
 		#[clap(flatten)]
 		address_or_vault_name: AddressOrVaultNameGroup,
+
+		/// Password to decrypt the <Base> account key (non-interactive mode)
+		#[clap(short = 'p', long, required = false, conflicts_with_all=["no_password"])]
+		password: Option<String>,
+
+		/// Use empty password to decrypt the <Base> account key (non-interactive mode)
+		#[clap(long, required = false)]
+		no_password: bool,
 	},
 	/// (deprecated) List available key files (needs to be migrated with command `vault migrate` in order to use them)
 	#[deprecated(
@@ -179,6 +187,17 @@ pub struct AddressOrVaultNameGroup {
 	vault_name: Option<String>,
 }
 
+/// vault inspect for JSON serialization
+#[derive(Serialize)]
+struct VaultInspectData {
+	substrate_uri: String,
+	crypto_scheme: CryptoScheme,
+	secret_seed: Option<String>,
+	public_key_hex: String,
+	ss58_address: String,
+	g1v1_public_key: Option<String>,
+}
+
 pub struct VaultDataToImport {
 	secret_format: SecretFormat,
 	secret_suri: String,
@@ -336,7 +355,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 
 			//This updated configuration will be picked up with next GCli execution
 			conf::save(&updated_cfg);
-			
+
 			match data.args.output_format {
 				OutputFormat::Human => {
 					println!("Configuration updated!");
@@ -619,7 +638,11 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 		}
 		Subcommand::Inspect {
 			address_or_vault_name,
+			password,
+			no_password,
 		} => {
+			let is_interactive = password.is_none() && !no_password;
+
 			let account_tree_node_to_inspect =
 				retrieve_account_tree_node(db, address_or_vault_name).await?;
 
@@ -628,73 +651,51 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 
 			let is_base_account =
 				Rc::ptr_eq(&account_tree_node_to_inspect, &base_account_tree_node);
-			if !is_base_account {
+
+			if is_interactive && !is_base_account {
 				let base_account = base_account_tree_node.borrow().account.clone();
 				println!("The linked <Base> account is {base_account}");
 			}
 
-			println!("Enter password to decrypt the <Base> account key");
-			let password = inputs::prompt_password()?;
+			// Handle password from non-interactive mode or ask for it
+			let password = if no_password {
+				String::new()
+			} else if let Some(password) = password {
+				password
+			} else {
+				println!("Enter password to decrypt the <Base> account key");
+				inputs::prompt_password()?
+			};
 
-			let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node(
-				&account_tree_node_to_inspect,
-				password,
-			)?;
+			let vault_inspect_data =
+				compute_vault_inspect_data(&account_tree_node_to_inspect, password)?;
 
 			match data.args.output_format {
 				OutputFormat::Human => {
-					println!("Substrate URI: '{account_to_derive_secret_suri}'");
-
-					let crypto_scheme: CryptoScheme = base_account_tree_node
-						.borrow()
-						.account
-						.crypto_scheme
-						.ok_or(GcliError::Logic(
-							"Base account without crypto_scheme".to_string(),
-						))?
-						.into();
-					println!("Crypto scheme: {}", <&'static str>::from(crypto_scheme));
-
-					match compute_pair_and_mini_secret_seed_from_suri_and_crypto_scheme(
-						&account_to_derive_secret_suri,
-						crypto_scheme,
-					) {
-						Err(e) => {
-							println!("Secret seed/mini-secret: cannot be computed: {}", e)
-						}
-						Ok((_computed_pair, seed)) => {
-							println!("Secret seed/mini-secret: '0x{}'", hex::encode(seed));
+					println!("Substrate URI: '{}'", vault_inspect_data.substrate_uri);
+					println!(
+						"Crypto scheme: {}",
+						<&'static str>::from(vault_inspect_data.crypto_scheme)
+					);
+					match vault_inspect_data.secret_seed {
+						None => println!("Secret seed/mini-secret: N/A"),
+						Some(secret_seed) => {
+							println!("Secret seed/mini-secret: '0x{}'", secret_seed)
 						}
 					}
-
-					let account_address: AccountId = account_tree_node_to_inspect
-						.borrow()
-						.account
-						.address
-						.clone()
-						.into();
-
-					println!("Public key (hex): '0x{}'", hex::encode(account_address.0));
-
-					println!("SS58 Address: '{account_address}'");
-
-					if CryptoScheme::Ed25519 == crypto_scheme && is_base_account {
-						println!(
-							"(potential G1v1 public key: '{}')",
-							cesium::compute_g1v1_public_key_from_ed25519_account_id(
-								&account_address
-							)
-						);
+					println!(
+						"Public key (hex): '0x{}'",
+						vault_inspect_data.public_key_hex
+					);
+					println!("SS58 Address: '{}'", vault_inspect_data.ss58_address);
+					if let Some(g1v1_public_key) = vault_inspect_data.g1v1_public_key {
+						println!("(potential G1v1 public key: '{}')", g1v1_public_key);
 					}
 				}
 				OutputFormat::Json => {
-					//FIXME handle same data as interactive
-					let json_view = serde_json::json!({
-						"substrate_uri": account_to_derive_secret_suri
-					});
 					println!(
 						"{}",
-						serde_json::to_string(&json_view).map_err(|e| anyhow!(e))?
+						serde_json::to_string(&vault_inspect_data).map_err(|e| anyhow!(e))?
 					);
 				}
 			}
@@ -788,6 +789,63 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 	Ok(())
 }
 
+fn compute_vault_inspect_data(
+	account_tree_node_to_inspect: &Rc<RefCell<AccountTreeNode>>,
+	password: String,
+) -> Result<VaultInspectData, GcliError> {
+	let base_account_tree_node =
+		vault_account::get_base_account_tree_node(account_tree_node_to_inspect);
+
+	let is_base_account = Rc::ptr_eq(account_tree_node_to_inspect, &base_account_tree_node);
+
+	let account_secret_suri =
+		vault_account::compute_suri_account_tree_node(account_tree_node_to_inspect, password)?;
+
+	let crypto_scheme: CryptoScheme = base_account_tree_node
+		.borrow()
+		.account
+		.crypto_scheme
+		.ok_or(GcliError::Logic(
+			"Base account without crypto_scheme".to_string(),
+		))?
+		.into();
+
+	let opt_seed = match compute_pair_and_mini_secret_seed_from_suri_and_crypto_scheme(
+		&account_secret_suri,
+		crypto_scheme,
+	) {
+		Err(e) => {
+			log::warn!("Secret seed/mini-secret cannot be computed: {}", e);
+			None
+		}
+		Ok((_computed_pair, seed)) => Some(hex::encode(seed)),
+	};
+
+	let account_address: AccountId = account_tree_node_to_inspect
+		.borrow()
+		.account
+		.address
+		.clone()
+		.into();
+
+	let opt_g1v1_pub_key = if CryptoScheme::Ed25519 == crypto_scheme && is_base_account {
+		let g1v1_public_key =
+			cesium::compute_g1v1_public_key_from_ed25519_account_id(&account_address);
+		Some(g1v1_public_key)
+	} else {
+		None
+	};
+
+	Ok(VaultInspectData {
+		substrate_uri: account_secret_suri,
+		crypto_scheme,
+		secret_seed: opt_seed,
+		public_key_hex: hex::encode(account_address.0),
+		ss58_address: account_address.to_string(),
+		g1v1_public_key: opt_g1v1_pub_key,
+	})
+}
+
 /// Method used to separate vault `name` part from optional `derivation` part in computed names that can be provided by users in the different `vault` commands using `AddressOrVaultNameGroup`
 fn parse_vault_name_and_derivation_path_from_user_input(
 	user_input_name: String,
diff --git a/src/keys.rs b/src/keys.rs
index 4db3ca2..88d0480 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -1,5 +1,6 @@
 use crate::commands::vault;
 use crate::*;
+use serde::Serializer;
 use sp_core::crypto::AddressUri;
 use sp_core::crypto::Pair as PairTrait;
 use sp_core::DeriveJunction;
@@ -64,6 +65,16 @@ pub enum CryptoScheme {
 	Sr25519,
 }
 
+impl Serialize for CryptoScheme {
+	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+	where
+		S: Serializer,
+	{
+		let as_str: &str = (*self).into(); // Use the `From<CryptoScheme>` implementation
+		serializer.serialize_str(as_str)
+	}
+}
+
 /// Setting a default to Ed25519
 ///
 /// required when used in Args struct inside main.rs; even though we still have to give a clap "default_value"
-- 
GitLab