From 027f0655afb6602412c729cabdd95d3d5bad53e3 Mon Sep 17 00:00:00 2001
From: poka <poka@p2p.legal>
Date: Wed, 12 Mar 2025 16:58:42 +0100
Subject: [PATCH 1/6] feat: Can choose between ed25519 ans sr25519

---
 src/commands/vault.rs         | 122 +++++++++++++++----------
 src/commands/vault/display.rs | 105 +++++++++++++++++++--
 src/keys.rs                   | 167 ++++++++++++++++++++++------------
 3 files changed, 278 insertions(+), 116 deletions(-)

diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index 8d6de10..a4e3594 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -41,14 +41,18 @@ pub enum Subcommand {
 		/// Secret key format (substrate, seed, g1v1)
 		#[clap(short = 'S', long, required = false, default_value = SecretFormat::Substrate)]
 		secret_format: SecretFormat,
+		
+		/// Crypto scheme to use (sr25519, ed25519)
+		#[clap(short = 'c', long, required = false, default_value = CryptoScheme::Ed25519)]
+		crypto_scheme: CryptoScheme,
 	},
 	/// Add a derivation to an existing SS58 Address
 	#[clap(long_about = "Add a derivation to an existing SS58 Address.\n\
 		\n\
-		Only \"sr25519\" crypto scheme is supported for derivations.\n\
+		Both \"sr25519\" and \"ed25519\" crypto schemes are supported
 		\n\
 		Use command `vault list base` to see available <Base> account and their crypto scheme\n\
-		And then use command 'vault list for' to find all accounts linked to that <Base> account.")]
+		And then use command 'vault list for' to find all accounts linked to that <Base> account")]
 	#[clap(alias = "deriv")]
 	#[clap(alias = "derivation")]
 	Derive {
@@ -87,18 +91,35 @@ pub enum Subcommand {
 	Where,
 }
 
-#[derive(Clone, Default, Debug, clap::Parser)]
+#[derive(Clone, Debug, clap::Parser)]
 pub enum ListChoice {
 	/// List all <Base> SS58 Addresses and their linked derivations in the vault
-	#[default]
-	All,
+	All {
+		/// Show G1v1 public keys
+		#[clap(long, default_value = "false")]
+		show_g1v1: bool,
+	},
 	/// List <Base> and Derivation SS58 Addresses linked to the selected one
 	For {
 		#[clap(flatten)]
 		address_or_vault_name: AddressOrVaultNameGroup,
+		
+		/// Show G1v1 public keys
+		#[clap(long, default_value = "false")]
+		show_g1v1: bool,
 	},
 	/// List all <Base> SS58 Addresses in the vault
-	Base,
+	Base {
+		/// Show G1v1 public keys
+		#[clap(long, default_value = "false")]
+		show_g1v1: bool,
+	},
+}
+
+impl Default for ListChoice {
+	fn default() -> Self {
+		ListChoice::All { show_g1v1: false }
+	}
 }
 
 #[derive(Debug, clap::Args, Clone)]
@@ -151,26 +172,27 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 	// match subcommand
 	match command {
 		Subcommand::List(choice) => match choice {
-			ListChoice::All => {
+			ListChoice::All { show_g1v1 } => {
 				let all_account_tree_node_hierarchies =
 					vault_account::fetch_all_base_account_tree_node_hierarchies(db).await?;
-				let table =
-					display::compute_vault_accounts_table(&all_account_tree_node_hierarchies)?;
+
+				let table = display::compute_vault_accounts_table_with_g1v1(&all_account_tree_node_hierarchies, show_g1v1)?;
 
 				println!("available SS58 Addresses:");
 				println!("{table}");
 			}
-			ListChoice::Base => {
+			ListChoice::Base { show_g1v1 } => {
 				let base_account_tree_nodes =
 					vault_account::fetch_only_base_account_tree_nodes(db).await?;
 
-				let table = display::compute_vault_accounts_table(&base_account_tree_nodes)?;
+				let table = display::compute_vault_accounts_table_with_g1v1(&base_account_tree_nodes, show_g1v1)?;
 
 				println!("available <Base> SS58 Addresses:");
 				println!("{table}");
 			}
 			ListChoice::For {
 				address_or_vault_name,
+				show_g1v1,
 			} => {
 				let account_tree_node =
 					retrieve_account_tree_node(db, address_or_vault_name).await?;
@@ -178,7 +200,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 				let base_account_tree_node =
 					vault_account::get_base_account_tree_node(&account_tree_node);
 
-				let table = display::compute_vault_accounts_table(&[base_account_tree_node])?;
+				let table = display::compute_vault_accounts_table_with_g1v1(&[base_account_tree_node], show_g1v1)?;
 
 				println!(
 					"available SS58 Addresses linked to {}:",
@@ -215,9 +237,9 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 			let mnemonic = bip39::Mnemonic::generate(12).unwrap();
 			println!("{mnemonic}");
 		}
-		Subcommand::Import { secret_format } => {
+		Subcommand::Import { secret_format, crypto_scheme } => {
 			let vault_data_for_import =
-				prompt_secret_and_compute_vault_data_to_import(secret_format)?;
+				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 {
@@ -235,7 +257,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 
 			println!();
 			let _account =
-				create_base_account_for_vault_data_to_import(&txn, &vault_data_for_import, None)
+				create_base_account_for_vault_data_to_import(&txn, &vault_data_for_import, None, Some(crypto_scheme))
 					.await?;
 
 			txn.commit().await?;
@@ -255,25 +277,6 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 
 			let base_account = &base_account_tree_node.borrow().account.clone();
 
-			if base_account.crypto_scheme.is_none() {
-				return Err(GcliError::DatabaseError(DbErr::Custom(format!("Crypto scheme is not set for the base account:{base_account} - should never happen"))));
-			}
-
-			if let Some(crypto_scheme) = base_account.crypto_scheme {
-				if CryptoScheme::from(crypto_scheme) == CryptoScheme::Ed25519 {
-					println!(
-						"Only \"{}\" crypto scheme is supported for derivations.",
-						Into::<&str>::into(CryptoScheme::Sr25519),
-					);
-					println!();
-					println!(
-                        "Use command `vault list base` to see available <Base> account and their crypto scheme\n\
-						And then use command 'vault list for' to find all accounts linked to that <Base> account"
-                    );
-					return Ok(());
-				}
-			}
-
 			println!("Adding derivation to: {account_to_derive}");
 
 			let base_parent_hierarchy_account_tree_node_to_derive =
@@ -307,8 +310,12 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 			let derivation_secret_suri =
 				format!("{account_to_derive_secret_suri}{derivation_path}");
 
+			let crypto_scheme = base_account.crypto_scheme
+				.map(CryptoScheme::from)
+				.unwrap_or(CryptoScheme::Ed25519); // Fallback to Ed25519 if not defined (should never happen)
+
 			let derivation_keypair =
-				compute_keypair(CryptoScheme::Sr25519, &derivation_secret_suri)?;
+				compute_keypair(crypto_scheme, &derivation_secret_suri)?;
 
 			let derivation_address: String = derivation_keypair.address().to_string();
 
@@ -468,6 +475,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 					&txn,
 					&vault_data_to_import,
 					Some(&vault_data_from_file.password),
+					None,
 				)
 				.await;
 
@@ -539,12 +547,24 @@ pub fn parse_prefix_and_derivation_path_from_suri(
 	Ok((address_uri.phrase.map(|s| s.to_string()), full_path))
 }
 
-fn map_secret_format_to_crypto_scheme(secret_format: SecretFormat) -> CryptoScheme {
-	match secret_format {
-		SecretFormat::Seed => CryptoScheme::Sr25519,
-		SecretFormat::Substrate => CryptoScheme::Sr25519,
-		SecretFormat::Predefined => CryptoScheme::Sr25519,
-		SecretFormat::G1v1 => CryptoScheme::Ed25519,
+fn map_secret_format_to_crypto_scheme(secret_format: SecretFormat, override_crypto_scheme: Option<CryptoScheme>) -> CryptoScheme {
+	// If a crypto_scheme is explicitly specified, use it except for G1v1 which must always use Ed25519
+	if let Some(scheme) = override_crypto_scheme {
+		if secret_format == SecretFormat::G1v1 {
+			// G1v1 must always use Ed25519
+			CryptoScheme::Ed25519
+		} else {
+			scheme
+		}
+	} else {
+		// Default behavior if no crypto_scheme is specified
+		match secret_format {
+			// All formats use Ed25519 by default
+			SecretFormat::Seed => CryptoScheme::Ed25519,
+			SecretFormat::Substrate => CryptoScheme::Ed25519,
+			SecretFormat::Predefined => CryptoScheme::Ed25519,
+			SecretFormat::G1v1 => CryptoScheme::Ed25519,
+		}
 	}
 }
 
@@ -675,13 +695,14 @@ where
 
 fn create_vault_data_to_import<F, P>(
 	secret_format: SecretFormat,
+	crypto_scheme: CryptoScheme,
 	prompt_fn: F,
 ) -> Result<VaultDataToImport, GcliError>
 where
-	F: Fn() -> (String, P),
+	F: Fn(CryptoScheme) -> (String, P),
 	P: Into<KeyPair>,
 {
-	let (secret, pair) = prompt_fn();
+	let (secret, pair) = prompt_fn(crypto_scheme);
 	let key_pair = pair.into();
 	Ok(VaultDataToImport {
 		secret_format,
@@ -692,19 +713,21 @@ where
 
 fn prompt_secret_and_compute_vault_data_to_import(
 	secret_format: SecretFormat,
+	crypto_scheme: CryptoScheme,
 ) -> Result<VaultDataToImport, GcliError> {
 	match secret_format {
 		SecretFormat::Substrate => {
-			create_vault_data_to_import(secret_format, prompt_secret_substrate_and_compute_keypair)
+			create_vault_data_to_import(secret_format, crypto_scheme, prompt_secret_substrate_and_compute_keypair)
 		}
 		SecretFormat::Seed => {
-			create_vault_data_to_import(secret_format, prompt_seed_and_compute_keypair)
+			create_vault_data_to_import(secret_format, crypto_scheme, prompt_seed_and_compute_keypair)
 		}
 		SecretFormat::G1v1 => {
-			create_vault_data_to_import(secret_format, prompt_secret_cesium_and_compute_keypair)
+			// G1v1 always uses Ed25519, ignore crypto_scheme
+			create_vault_data_to_import(secret_format, CryptoScheme::Ed25519, prompt_secret_cesium_and_compute_keypair)
 		}
 		SecretFormat::Predefined => {
-			create_vault_data_to_import(secret_format, prompt_predefined_and_compute_keypair)
+			create_vault_data_to_import(secret_format, crypto_scheme, prompt_predefined_and_compute_keypair)
 		}
 	}
 }
@@ -720,6 +743,7 @@ pub async fn create_base_account_for_vault_data_to_import<C>(
 	db_tx: &C,
 	vault_data: &VaultDataToImport,
 	password: Option<&String>,
+	crypto_scheme: Option<CryptoScheme>,
 ) -> Result<vault_account::Model, GcliError>
 where
 	C: ConnectionTrait,
@@ -793,7 +817,7 @@ where
 				vault_account.path = Set(None);
 				vault_account.parent = Set(None);
 				vault_account.crypto_scheme = Set(Some(
-					map_secret_format_to_crypto_scheme(vault_data.secret_format).into(),
+					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());
@@ -816,7 +840,7 @@ where
 		println!("(Optional) Enter a name for the vault entry");
 		let name = inputs::prompt_vault_name_and_check_availability(db_tx, None).await?;
 
-		let crypto_scheme = map_secret_format_to_crypto_scheme(secret_format);
+		let crypto_scheme = map_secret_format_to_crypto_scheme(secret_format, crypto_scheme);
 
 		let base_account = vault_account::create_base_account(
 			db_tx,
diff --git a/src/commands/vault/display.rs b/src/commands/vault/display.rs
index 021a84f..e9a9d2d 100644
--- a/src/commands/vault/display.rs
+++ b/src/commands/vault/display.rs
@@ -25,18 +25,26 @@ pub fn compute_vault_key_files_table(vault_key_addresses: &[String]) -> Result<T
 
 pub fn compute_vault_accounts_table(
 	account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
+) -> Result<Table, GcliError> {
+	// Appel to the new function with show_g1v1 = true to maintain compatibility
+	compute_vault_accounts_table_with_g1v1(account_tree_nodes, true)
+}
+
+pub fn compute_vault_accounts_table_with_g1v1(
+	account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
+	show_g1v1: bool,
 ) -> Result<Table, GcliError> {
 	let mut table = Table::new();
 	table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
 	table.set_header(vec![
-		"SS58 Address/G1v1 public key",
+		if show_g1v1 { "SS58 Address/G1v1 public key" } else { "SS58 Address" },
 		"Crypto",
 		"Path",
 		"Name",
 	]);
 
 	for account_tree_node in account_tree_nodes {
-		let _ = add_account_tree_node_to_table(&mut table, account_tree_node);
+		let _ = add_account_tree_node_to_table_with_g1v1(&mut table, account_tree_node, show_g1v1);
 	}
 
 	Ok(table)
@@ -46,13 +54,22 @@ fn add_account_tree_node_to_table(
 	table: &mut Table,
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
 ) -> Result<(), GcliError> {
-	let rows = compute_vault_accounts_row(account_tree_node)?;
+	// Appel to the new function with show_g1v1 = true to maintain compatibility
+	add_account_tree_node_to_table_with_g1v1(table, account_tree_node, true)
+}
+
+fn add_account_tree_node_to_table_with_g1v1(
+	table: &mut Table,
+	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
+	show_g1v1: bool,
+) -> Result<(), GcliError> {
+	let rows = compute_vault_accounts_row_with_g1v1(account_tree_node, show_g1v1)?;
 	rows.iter().for_each(|row| {
 		table.add_row(row.clone());
 	});
 
 	for child in &account_tree_node.borrow().children {
-		let _ = add_account_tree_node_to_table(table, child);
+		let _ = add_account_tree_node_to_table_with_g1v1(table, child, show_g1v1);
 	}
 
 	Ok(())
@@ -63,6 +80,17 @@ fn add_account_tree_node_to_table(
 /// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key
 pub fn compute_vault_accounts_row(
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
+) -> Result<Vec<Vec<Cell>>, GcliError> {
+	// Appel to the new function with show_g1v1 = true to maintain compatibility
+	compute_vault_accounts_row_with_g1v1(account_tree_node, true)
+}
+
+/// Computes one or more row of the table for selected account_tree_node
+///
+/// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key if show_g1v1 is true
+pub fn compute_vault_accounts_row_with_g1v1(
+	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
+	show_g1v1: bool,
 ) -> Result<Vec<Vec<Cell>>, GcliError> {
 	let empty_string = "".to_string();
 
@@ -93,9 +121,14 @@ pub fn compute_vault_accounts_row(
 		(path, empty_string.clone())
 	} else {
 		let crypto_scheme = CryptoScheme::from(account_tree_node.account.crypto_scheme.unwrap());
-
-		// Adding 2nd row for G1v1 public key
-		if CryptoScheme::Ed25519 == crypto_scheme {
+		let crypto_scheme_str: &str = crypto_scheme.into();
+		
+		// Check if it's an ed25519 key (for G1v1, we always use Ed25519)
+		// We don't have access to the secret_format field, so we rely only on the crypto_scheme
+		let is_ed25519 = crypto_scheme == CryptoScheme::Ed25519;
+		
+		// Adding 2nd row for G1v1 public key only if show_g1v1 is true and it's an Ed25519 key
+		if show_g1v1 && is_ed25519 {
 			rows.push(vec![Cell::new(format!(
 				"â”” G1v1: {}",
 				cesium::compute_g1v1_public_key_from_ed25519_account_id(
@@ -104,7 +137,6 @@ pub fn compute_vault_accounts_row(
 			))]);
 		}
 
-		let crypto_scheme_str: &str = crypto_scheme.into();
 		(
 			format!("<{}>", account_tree_node.account.account_type()),
 			crypto_scheme_str.to_string(),
@@ -128,12 +160,13 @@ pub fn compute_vault_accounts_row(
 #[cfg(test)]
 mod tests {
 	mod vault_accounts_table_tests {
-		use crate::commands::vault::display::compute_vault_accounts_table;
+		use crate::commands::vault::display::{compute_vault_accounts_table, compute_vault_accounts_table_with_g1v1};
 		use crate::entities::vault_account::tests::account_tree_node_tests::{
 			mother_account_tree_node, mother_g1v1_account_tree_node,
 		};
 		use indoc::indoc;
 
+		// Tests for compute_vault_accounts_table (old function)
 		#[test]
 		fn test_compute_vault_accounts_table_empty() {
 			let table = compute_vault_accounts_table(&[]).unwrap();
@@ -191,5 +224,59 @@ mod tests {
 
 			assert_eq!(table.to_string(), expected_table);
 		}
+
+		// Tests for compute_vault_accounts_table_with_g1v1
+		#[test]
+		fn test_compute_vault_accounts_table_with_g1v1_empty() {
+			// Test with show_g1v1 = true (default behavior)
+			let table = compute_vault_accounts_table_with_g1v1(&[], true).unwrap();
+			println!("Table with show_g1v1=true:\n{}", table.to_string());
+			let expected_table = table.to_string();
+			assert_eq!(table.to_string(), expected_table);
+
+			// Test with show_g1v1 = false
+			let table = compute_vault_accounts_table_with_g1v1(&[], false).unwrap();
+			println!("Table with show_g1v1=false:\n{}", table.to_string());
+			let expected_table = table.to_string();
+			assert_eq!(table.to_string(), expected_table);
+		}
+
+		#[test]
+		fn test_compute_vault_accounts_table_with_g1v1() {
+			let account_tree_node = mother_account_tree_node();
+			let g1v1_account_tree_node = mother_g1v1_account_tree_node();
+			let account_tree_nodes = vec![account_tree_node, g1v1_account_tree_node];
+
+			// Test with show_g1v1 = true (default behavior)
+			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
+			println!("Table with show_g1v1=true:\n{}", table.to_string());
+			let expected_table = table.to_string();
+			assert_eq!(table.to_string(), expected_table);
+
+			// Test with show_g1v1 = false
+			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
+			println!("Table with show_g1v1=false:\n{}", table.to_string());
+			let expected_table = table.to_string();
+			assert_eq!(table.to_string(), expected_table);
+		}
+
+		#[test]
+		fn test_compute_vault_accounts_table_with_g1v1_partial() {
+			let mother = mother_account_tree_node();
+			let child1 = mother.borrow().children[0].clone();
+			let account_tree_nodes = vec![child1];
+
+			// Test with show_g1v1 = true (default behavior)
+			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
+			println!("Table with show_g1v1=true:\n{}", table.to_string());
+			let expected_table = table.to_string();
+			assert_eq!(table.to_string(), expected_table);
+
+			// Test with show_g1v1 = false
+			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
+			println!("Table with show_g1v1=false:\n{}", table.to_string());
+			let expected_table = table.to_string();
+			assert_eq!(table.to_string(), expected_table);
+		}
 	}
 }
diff --git a/src/keys.rs b/src/keys.rs
index 7b7babd..a81f2bf 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -139,7 +139,7 @@ pub fn get_keypair(
 ) -> Result<KeyPair, GcliError> {
 	match (secret_format, secret) {
 		(SecretFormat::Predefined, Some(deriv)) => pair_from_predefined(deriv).map(|v| v.into()),
-		(secret_format, None) => Ok(prompt_secret(secret_format)),
+		(secret_format, None) => Ok(prompt_secret(secret_format, None)),
 		(_, Some(secret)) => Ok(pair_from_secret(secret_format, secret)?.into()),
 	}
 }
@@ -214,79 +214,130 @@ pub fn seed_from_cesium(id: &str, pwd: &str) -> [u8; 32] {
 
 /// ask user to input a secret
 pub fn prompt_secret_substrate() -> sr25519::Pair {
-	// Only interested in the keypair which is the second element of the tuple
-	prompt_secret_substrate_and_compute_keypair().1
-}
-
-pub fn prompt_secret_substrate_and_compute_keypair() -> (String, sr25519::Pair) {
-	loop {
-		println!("Substrate URI can be a mnemonic or a mini-secret ('0x' prefixed seed) together with optional derivation path");
-		let substrate_suri = inputs::prompt_password_query("Substrate URI: ").unwrap();
-		match pair_from_sr25519_str(&substrate_suri) {
-			Ok(pair) => return (substrate_suri, pair),
-			Err(_) => println!("Invalid secret"),
-		}
-	}
+    // Only interested in the keypair which is the second element of the tuple
+    match prompt_secret_substrate_and_compute_keypair(CryptoScheme::Sr25519).1 {
+        KeyPair::Sr25519(pair) => pair,
+        _ => panic!("Expected Sr25519 keypair"),
+    }
+}
+
+pub fn prompt_secret_substrate_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
+    loop {
+        println!("Substrate URI can be a mnemonic or a mini-secret ('0x' prefixed seed) together with optional derivation path");
+        let substrate_suri = inputs::prompt_password_query("Substrate URI: ").unwrap();
+        match crypto_scheme {
+            CryptoScheme::Sr25519 => {
+                match pair_from_sr25519_str(&substrate_suri) {
+                    Ok(pair) => return (substrate_suri, pair.into()),
+                    Err(_) => println!("Invalid secret"),
+                }
+            },
+            CryptoScheme::Ed25519 => {
+                match pair_from_ed25519_str(&substrate_suri) {
+                    Ok(pair) => return (substrate_suri, pair.into()),
+                    Err(_) => println!("Invalid secret"),
+                }
+            }
+        }
+    }
 }
 
 /// ask user pass (Cesium format)
 pub fn prompt_secret_cesium() -> ed25519::Pair {
-	// Only interested in the keypair which is the second element of the tuple
-	prompt_secret_cesium_and_compute_keypair().1
+    // Only interested in the keypair which is the second element of the tuple
+    match prompt_secret_cesium_and_compute_keypair(CryptoScheme::Ed25519).1 {
+        KeyPair::Ed25519(pair) => pair,
+        _ => panic!("Expected Ed25519 keypair"),
+    }
 }
 
-pub fn prompt_secret_cesium_and_compute_keypair() -> (String, ed25519::Pair) {
-	let id = inputs::prompt_password_query("G1v1 id: ").unwrap();
-	let pwd = inputs::prompt_password_query("G1v1 password: ").unwrap();
+pub fn prompt_secret_cesium_and_compute_keypair(_crypto_scheme: CryptoScheme) -> (String, KeyPair) {
+    let id = inputs::prompt_password_query("G1v1 id: ").unwrap();
+    let pwd = inputs::prompt_password_query("G1v1 password: ").unwrap();
 
-	let seed = seed_from_cesium(&id, &pwd);
-	let secret_suri = format!("0x{}", hex::encode(seed));
+    let seed = seed_from_cesium(&id, &pwd);
+    let secret_suri = format!("0x{}", hex::encode(seed));
 
-	match pair_from_ed25519_str(&secret_suri) {
-		Ok(pair) => (secret_suri, pair),
-		Err(_) => panic!("Could not compute KeyPair from G1v1 id/pwd"),
-	}
+    // G1v1 always uses Ed25519, ignore crypto_scheme
+    match pair_from_ed25519_str(&secret_suri) {
+        Ok(pair) => (secret_suri, pair.into()),
+        Err(_) => panic!("Could not compute KeyPair from G1v1 id/pwd"),
+    }
 }
 
 /// ask user to input a seed
 pub fn prompt_seed() -> sr25519::Pair {
-	// Only interested in the keypair which is the second element of the tuple
-	prompt_seed_and_compute_keypair().1
-}
-
-pub fn prompt_seed_and_compute_keypair() -> (String, sr25519::Pair) {
-	loop {
-		let seed_str = inputs::prompt_seed().unwrap();
-		let secret_suri = format!("0x{}", seed_str);
-
-		match pair_from_sr25519_str(&secret_suri) {
-			Ok(pair) => return (secret_suri, pair),
-			Err(_) => println!("Invalid seed"),
-		}
-	}
+    // Only interested in the keypair which is the second element of the tuple
+    match prompt_seed_and_compute_keypair(CryptoScheme::Sr25519).1 {
+        KeyPair::Sr25519(pair) => pair,
+        _ => panic!("Expected Sr25519 keypair"),
+    }
+}
+
+pub fn prompt_seed_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
+    loop {
+        let seed_str = inputs::prompt_seed().unwrap();
+        let secret_suri = format!("0x{}", seed_str);
+
+        match crypto_scheme {
+            CryptoScheme::Sr25519 => {
+                match pair_from_sr25519_str(&secret_suri) {
+                    Ok(pair) => return (secret_suri, pair.into()),
+                    Err(_) => println!("Invalid seed"),
+                }
+            },
+            CryptoScheme::Ed25519 => {
+                match pair_from_ed25519_str(&secret_suri) {
+                    Ok(pair) => return (secret_suri, pair.into()),
+                    Err(_) => println!("Invalid seed"),
+                }
+            }
+        }
+    }
 }
 
 /// ask user pass (Cesium format)
 pub fn prompt_predefined() -> sr25519::Pair {
-	// Only interested in the keypair which is the second element of the tuple
-	prompt_predefined_and_compute_keypair().1
-}
-
-pub fn prompt_predefined_and_compute_keypair() -> (String, sr25519::Pair) {
-	let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap();
-	(
-		predefined_mnemonic(&deriv),
-		pair_from_predefined(&deriv).expect("invalid secret"),
-	)
-}
-
-/// ask user secret in relevant format
-pub fn prompt_secret(secret_format: SecretFormat) -> KeyPair {
+    // Only interested in the keypair which is the second element of the tuple
+    match prompt_predefined_and_compute_keypair(CryptoScheme::Sr25519).1 {
+        KeyPair::Sr25519(pair) => pair,
+        _ => panic!("Expected Sr25519 keypair"),
+    }
+}
+
+pub fn prompt_predefined_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
+    let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap();
+    let mnemonic = predefined_mnemonic(&deriv);
+    
+    match crypto_scheme {
+        CryptoScheme::Sr25519 => {
+            match pair_from_sr25519_str(&mnemonic) {
+                Ok(pair) => (mnemonic, pair.into()),
+                Err(e) => panic!("Invalid secret: {}", e),
+            }
+        },
+        CryptoScheme::Ed25519 => {
+            match pair_from_ed25519_str(&mnemonic) {
+                Ok(pair) => (mnemonic, pair.into()),
+                Err(e) => panic!("Invalid secret: {}", e),
+            }
+        }
+    }
+}
+
+pub fn prompt_secret(secret_format: SecretFormat, crypto_scheme: Option<CryptoScheme>) -> KeyPair {
+	let default_scheme = match secret_format {
+		SecretFormat::G1v1 => CryptoScheme::Ed25519, // G1v1 always uses Ed25519
+		_ => CryptoScheme::Ed25519, // All formats use Ed25519 by default
+	};
+	
+	let scheme = crypto_scheme.unwrap_or(default_scheme);
+	
 	match secret_format {
-		SecretFormat::Substrate => prompt_secret_substrate().into(),
-		SecretFormat::G1v1 => prompt_secret_cesium().into(),
-		SecretFormat::Seed => prompt_seed().into(),
-		SecretFormat::Predefined => prompt_predefined().into(),
+		SecretFormat::Substrate => prompt_secret_substrate_and_compute_keypair(scheme).1,
+		SecretFormat::G1v1 => prompt_secret_cesium_and_compute_keypair(CryptoScheme::Ed25519).1, // G1v1 always uses Ed25519
+		SecretFormat::Seed => prompt_seed_and_compute_keypair(scheme).1,
+		SecretFormat::Predefined => prompt_predefined_and_compute_keypair(scheme).1,
 	}
 }
 
@@ -309,7 +360,7 @@ pub async fn fetch_or_get_keypair(
 	}
 	// at the moment, there is no way to confg gcli to use an other kind of secret
 	// without telling explicitly each time
-	Ok(prompt_secret(SecretFormat::Substrate))
+	Ok(prompt_secret(SecretFormat::Substrate, None))
 }
 
 // catch known addresses
-- 
GitLab


From 28e8ba8d7ee19def1a2c7cfa88ac5d65d561d349 Mon Sep 17 00:00:00 2001
From: poka <poka@p2p.legal>
Date: Thu, 13 Mar 2025 13:38:44 +0100
Subject: [PATCH 2/6] apply nico review

---
 src/commands/identity.rs      |  4 +-
 src/commands/vault.rs         |  2 +-
 src/commands/vault/display.rs | 97 +++++++++++++++++++++++++++--------
 src/data.rs                   |  4 +-
 src/keys.rs                   | 65 ++++++++++++++++++-----
 5 files changed, 133 insertions(+), 39 deletions(-)

diff --git a/src/commands/identity.rs b/src/commands/identity.rs
index 0cc8f4f..6a64121 100644
--- a/src/commands/identity.rs
+++ b/src/commands/identity.rs
@@ -154,7 +154,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 			secret_format,
 			secret,
 		} => {
-			let keypair = get_keypair(secret_format, secret.as_deref())?;
+			let keypair = get_keypair(secret_format, secret.as_deref(), None)?;
 			let address = keypair.address();
 			data = data.fetch_idty_index().await?; // idty index required for payload
 			link_account(&data, address, keypair).await?;
@@ -163,7 +163,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 			secret_format,
 			secret,
 		} => {
-			let keypair = get_keypair(secret_format, secret.as_deref())?;
+			let keypair = get_keypair(secret_format, secret.as_deref(), None)?;
 			let address = keypair.address();
 			data = data.fetch_idty_index().await?; // idty index required for payload
 			change_owner_key(&data, address, keypair).await?;
diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index a4e3594..ce9942b 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -475,7 +475,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 					&txn,
 					&vault_data_to_import,
 					Some(&vault_data_from_file.password),
-					None,
+					Some(CryptoScheme::Ed25519),
 				)
 				.await;
 
diff --git a/src/commands/vault/display.rs b/src/commands/vault/display.rs
index e9a9d2d..e51560a 100644
--- a/src/commands/vault/display.rs
+++ b/src/commands/vault/display.rs
@@ -230,15 +230,27 @@ mod tests {
 		fn test_compute_vault_accounts_table_with_g1v1_empty() {
 			// Test with show_g1v1 = true (default behavior)
 			let table = compute_vault_accounts_table_with_g1v1(&[], true).unwrap();
-			println!("Table with show_g1v1=true:\n{}", table.to_string());
-			let expected_table = table.to_string();
-			assert_eq!(table.to_string(), expected_table);
+			
+			let expected_table_with_g1v1 = indoc! {r#"
+			┌─────────────────────────────────────────────────────┐
+			│ SS58 Address/G1v1 public key   Crypto   Path   Name │
+			╞═════════════════════════════════════════════════════╡
+			└─────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table.to_string(), expected_table_with_g1v1);
 
 			// Test with show_g1v1 = false
 			let table = compute_vault_accounts_table_with_g1v1(&[], false).unwrap();
-			println!("Table with show_g1v1=false:\n{}", table.to_string());
-			let expected_table = table.to_string();
-			assert_eq!(table.to_string(), expected_table);
+			
+			let expected_table_without_g1v1 = indoc! {r#"
+			┌─────────────────────────────────────┐
+			│ SS58 Address   Crypto   Path   Name │
+			╞═════════════════════════════════════╡
+			└─────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table.to_string(), expected_table_without_g1v1);
 		}
 
 		#[test]
@@ -248,16 +260,41 @@ mod tests {
 			let account_tree_nodes = vec![account_tree_node, g1v1_account_tree_node];
 
 			// Test with show_g1v1 = true (default behavior)
-			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
-			println!("Table with show_g1v1=true:\n{}", table.to_string());
-			let expected_table = table.to_string();
-			assert_eq!(table.to_string(), expected_table);
+			let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
+			
+			let expected_table_with_g1v1 = indoc! {r#"
+			┌──────────────────────────────────────────────────────────────────────────────────────────┐
+			│ SS58 Address/G1v1 public key                           Crypto    Path     Name           │
+			╞══════════════════════════════════════════════════════════════════════════════════════════╡
+			│ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV       sr25519   <Base>   Mother         │
+			│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH               //0      Child 1        │
+			│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d             //0      Grandchild 1   │
+			│ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o               //1      <Mother//1>    │
+			│ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT             //1      <Mother//1//1> │
+			│ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4       ed25519   <Base>   MotherG1v1     │
+			│ └ G1v1: 86pW1doyJPVH3jeDPZNQa1UZFBo5zcdvHERcaeE758W7                                     │
+			└──────────────────────────────────────────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1);
 
 			// Test with show_g1v1 = false
-			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
-			println!("Table with show_g1v1=false:\n{}", table.to_string());
-			let expected_table = table.to_string();
-			assert_eq!(table.to_string(), expected_table);
+			let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
+			
+			let expected_table_without_g1v1 = indoc! {r#"
+			┌──────────────────────────────────────────────────────────────────────────────────────────┐
+			│ SS58 Address                                           Crypto    Path     Name           │
+			╞══════════════════════════════════════════════════════════════════════════════════════════╡
+			│ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV       sr25519   <Base>   Mother         │
+			│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH               //0      Child 1        │
+			│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d             //0      Grandchild 1   │
+			│ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o               //1      <Mother//1>    │
+			│ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT             //1      <Mother//1//1> │
+			│ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4       ed25519   <Base>   MotherG1v1     │
+			└──────────────────────────────────────────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1);
 		}
 
 		#[test]
@@ -267,16 +304,32 @@ mod tests {
 			let account_tree_nodes = vec![child1];
 
 			// Test with show_g1v1 = true (default behavior)
-			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
-			println!("Table with show_g1v1=true:\n{}", table.to_string());
-			let expected_table = table.to_string();
-			assert_eq!(table.to_string(), expected_table);
+			let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
+			
+			let expected_table_with_g1v1 = indoc! {r#"
+			┌─────────────────────────────────────────────────────────────────────────────────────┐
+			│ SS58 Address/G1v1 public key                           Crypto   Path   Name         │
+			╞═════════════════════════════════════════════════════════════════════════════════════╡
+			│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH              //0    Child 1      │
+			│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d            //0    Grandchild 1 │
+			└─────────────────────────────────────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1);
 
 			// Test with show_g1v1 = false
-			let table = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
-			println!("Table with show_g1v1=false:\n{}", table.to_string());
-			let expected_table = table.to_string();
-			assert_eq!(table.to_string(), expected_table);
+			let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
+			
+			let expected_table_without_g1v1 = indoc! {r#"
+			┌─────────────────────────────────────────────────────────────────────────────────────┐
+			│ SS58 Address                                           Crypto   Path   Name         │
+			╞═════════════════════════════════════════════════════════════════════════════════════╡
+			│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH              //0    Child 1      │
+			│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d            //0    Grandchild 1 │
+			└─────────────────────────────────────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1);
 		}
 	}
 }
diff --git a/src/data.rs b/src/data.rs
index 1e877c0..40f9918 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -123,7 +123,7 @@ impl Data {
 		match self.keypair.clone() {
 			Some(keypair) => keypair,
 			None => loop {
-				match fetch_or_get_keypair(self, self.cfg.address.clone()).await {
+				match fetch_or_get_keypair(self, self.cfg.address.clone(), None).await {
 					Ok(pair) => return pair,
 					Err(e) => {
 						//Adapted code to still be able to go out of the loop when user hit "Esc" key or "ctrl+c" when prompted for a value
@@ -207,7 +207,7 @@ impl Data {
 		}
 		// secret format and value
 		if let Some(secret_format) = self.args.secret_format {
-			let keypair = get_keypair(secret_format, self.args.secret.as_deref())?;
+			let keypair = get_keypair(secret_format, self.args.secret.as_deref(), None)?;
 			self.cfg.address = Some(keypair.address());
 			self.keypair = Some(keypair);
 		}
diff --git a/src/keys.rs b/src/keys.rs
index a81f2bf..cdbfb44 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -136,11 +136,48 @@ pub enum Signature {
 pub fn get_keypair(
 	secret_format: SecretFormat,
 	secret: Option<&str>,
+	crypto_scheme: Option<CryptoScheme>,
 ) -> Result<KeyPair, GcliError> {
 	match (secret_format, secret) {
-		(SecretFormat::Predefined, Some(deriv)) => pair_from_predefined(deriv).map(|v| v.into()),
-		(secret_format, None) => Ok(prompt_secret(secret_format, None)),
-		(_, Some(secret)) => Ok(pair_from_secret(secret_format, secret)?.into()),
+		(SecretFormat::Predefined, Some(deriv)) => {
+			match crypto_scheme {
+				Some(CryptoScheme::Ed25519) => pair_from_ed25519_str(&predefined_suri(deriv)).map(|v| v.into()),
+				_ => pair_from_predefined(deriv).map(|v| v.into()), // Default to Sr25519 for backward compatibility
+			}
+		},
+		(secret_format, None) => Ok(prompt_secret(secret_format, crypto_scheme)),
+		(_, Some(secret)) => {
+			match crypto_scheme {
+				Some(CryptoScheme::Ed25519) => pair_from_secret_with_scheme(secret_format, secret, CryptoScheme::Ed25519),
+				_ => Ok(pair_from_secret(secret_format, secret)?.into()), // Default to Sr25519 for backward compatibility
+			}
+		},
+	}
+}
+
+/// get keypair from given secret with specified crypto scheme
+/// if secret is predefined, secret should contain the predefined value
+pub fn pair_from_secret_with_scheme(
+	secret_format: SecretFormat,
+	secret: &str,
+	crypto_scheme: CryptoScheme,
+) -> Result<KeyPair, GcliError> {
+	match (secret_format, crypto_scheme) {
+		(SecretFormat::G1v1, _) => Err(GcliError::Logic(
+			"G1v1 format incompatible with single secret".to_string(),
+		)),
+		(_, CryptoScheme::Ed25519) => match secret_format {
+			SecretFormat::Substrate => pair_from_ed25519_str(secret).map(|v| v.into()),
+			SecretFormat::Predefined => pair_from_ed25519_str(secret).map(|v| v.into()),
+			SecretFormat::Seed => {
+				let mut seed = [0; 32];
+				hex::decode_to_slice(secret, &mut seed)
+					.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
+				Ok(ed25519::Pair::from_seed(&seed).into())
+			},
+			SecretFormat::G1v1 => unreachable!(), // Already handled above
+		},
+		(_, CryptoScheme::Sr25519) => pair_from_secret(secret_format, secret).map(|v| v.into()),
 	}
 }
 
@@ -195,13 +232,13 @@ pub fn pair_from_ed25519_seed(secret: &str) -> Result<ed25519::Pair, GcliError>
 }
 
 /// get mnemonic from predefined derivation path
-pub fn predefined_mnemonic(deriv: &str) -> String {
+pub fn predefined_suri(deriv: &str) -> String {
 	format!("{SUBSTRATE_MNEMONIC}//{deriv}")
 }
 
 /// get keypair from predefined secret
 pub fn pair_from_predefined(deriv: &str) -> Result<sr25519::Pair, GcliError> {
-	pair_from_sr25519_str(&predefined_mnemonic(deriv))
+	pair_from_sr25519_str(&predefined_suri(deriv))
 }
 
 /// get seed from G1v1 id/pwd (old "cesium")
@@ -307,18 +344,18 @@ pub fn prompt_predefined() -> sr25519::Pair {
 
 pub fn prompt_predefined_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
     let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap();
-    let mnemonic = predefined_mnemonic(&deriv);
+    let suri = predefined_suri(&deriv);
     
     match crypto_scheme {
         CryptoScheme::Sr25519 => {
-            match pair_from_sr25519_str(&mnemonic) {
-                Ok(pair) => (mnemonic, pair.into()),
+            match pair_from_sr25519_str(&suri) {
+                Ok(pair) => (suri, pair.into()),
                 Err(e) => panic!("Invalid secret: {}", e),
             }
         },
         CryptoScheme::Ed25519 => {
-            match pair_from_ed25519_str(&mnemonic) {
-                Ok(pair) => (mnemonic, pair.into()),
+            match pair_from_ed25519_str(&suri) {
+                Ok(pair) => (suri, pair.into()),
                 Err(e) => panic!("Invalid secret: {}", e),
             }
         }
@@ -345,12 +382,16 @@ pub fn prompt_secret(secret_format: SecretFormat, crypto_scheme: Option<CryptoSc
 pub async fn fetch_or_get_keypair(
 	data: &Data,
 	address: Option<AccountId>,
+	crypto_scheme: Option<CryptoScheme>,
 ) -> Result<KeyPair, GcliError> {
 	if let Some(address) = address {
 		// if address corresponds to predefined, (for example saved to config)
 		// keypair is already known (useful for dev mode)
 		if let Some(d) = catch_known(&address.to_string()) {
-			return Ok(pair_from_predefined(d).unwrap().into());
+			match crypto_scheme {
+				Some(CryptoScheme::Ed25519) => return pair_from_ed25519_str(&predefined_suri(d)).map(|v| v.into()),
+				_ => return Ok(pair_from_predefined(d).unwrap().into()), // Default to Sr25519 for backward compatibility
+			}
 		};
 
 		// look for corresponding KeyPair in keystore
@@ -360,7 +401,7 @@ pub async fn fetch_or_get_keypair(
 	}
 	// at the moment, there is no way to confg gcli to use an other kind of secret
 	// without telling explicitly each time
-	Ok(prompt_secret(SecretFormat::Substrate, None))
+	Ok(prompt_secret(SecretFormat::Substrate, crypto_scheme))
 }
 
 // catch known addresses
-- 
GitLab


From e9cd6a99fd0b7a0a8dec00f8a00df842abc9e70e Mon Sep 17 00:00:00 2001
From: poka <poka@p2p.legal>
Date: Thu, 13 Mar 2025 18:28:31 +0100
Subject: [PATCH 3/6] Add secret format in database and display

---
 src/commands/vault.rs         | 167 ++++++++++++++--------------------
 src/commands/vault/display.rs | 156 ++++++++++++++++++++++++-------
 src/entities/vault_account.rs |  51 +++++++++++
 3 files changed, 240 insertions(+), 134 deletions(-)

diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index ce9942b..10dcdb1 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -91,34 +91,46 @@ pub enum Subcommand {
 	Where,
 }
 
-#[derive(Clone, Debug, clap::Parser)]
+/// List subcommands
+#[derive(Clone, Debug, clap::Subcommand)]
 pub enum ListChoice {
-	/// List all <Base> SS58 Addresses and their linked derivations in the vault
+	/// List all accounts
+	#[clap(alias = "a")]
 	All {
-		/// Show G1v1 public keys
-		#[clap(long, default_value = "false")]
+		/// Show G1v1 public key for ed25519 keys
+		#[clap(long)]
+		show_g1v1: bool,
+		/// Show wallet type (g1v1 or mnemonic)
+		#[clap(long)]
+		show_type: bool,
+	},
+	/// List only base accounts
+	#[clap(alias = "b")]
+	Base {
+		/// Show G1v1 public key for ed25519 keys
+		#[clap(long)]
 		show_g1v1: bool,
+		/// Show wallet type (g1v1 or mnemonic)
+		#[clap(long)]
+		show_type: bool,
 	},
-	/// List <Base> and Derivation SS58 Addresses linked to the selected one
+	/// List accounts for a specific address
+	#[clap(alias = "f")]
 	For {
 		#[clap(flatten)]
 		address_or_vault_name: AddressOrVaultNameGroup,
-		
-		/// Show G1v1 public keys
-		#[clap(long, default_value = "false")]
-		show_g1v1: bool,
-	},
-	/// List all <Base> SS58 Addresses in the vault
-	Base {
-		/// Show G1v1 public keys
-		#[clap(long, default_value = "false")]
+		/// Show G1v1 public key for ed25519 keys
+		#[clap(long)]
 		show_g1v1: bool,
+		/// Show wallet type (g1v1 or mnemonic)
+		#[clap(long)]
+		show_type: bool,
 	},
 }
 
 impl Default for ListChoice {
 	fn default() -> Self {
-		ListChoice::All { show_g1v1: false }
+		ListChoice::All { show_g1v1: false, show_type: false }
 	}
 }
 
@@ -172,20 +184,20 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 	// match subcommand
 	match command {
 		Subcommand::List(choice) => match choice {
-			ListChoice::All { show_g1v1 } => {
+			ListChoice::All { show_g1v1, show_type } => {
 				let all_account_tree_node_hierarchies =
 					vault_account::fetch_all_base_account_tree_node_hierarchies(db).await?;
 
-				let table = display::compute_vault_accounts_table_with_g1v1(&all_account_tree_node_hierarchies, show_g1v1)?;
+				let table = display::compute_vault_accounts_table_with_g1v1(&all_account_tree_node_hierarchies, show_g1v1, show_type)?;
 
 				println!("available SS58 Addresses:");
 				println!("{table}");
 			}
-			ListChoice::Base { show_g1v1 } => {
+			ListChoice::Base { show_g1v1, show_type } => {
 				let base_account_tree_nodes =
 					vault_account::fetch_only_base_account_tree_nodes(db).await?;
 
-				let table = display::compute_vault_accounts_table_with_g1v1(&base_account_tree_nodes, show_g1v1)?;
+				let table = display::compute_vault_accounts_table_with_g1v1(&base_account_tree_nodes, show_g1v1, show_type)?;
 
 				println!("available <Base> SS58 Addresses:");
 				println!("{table}");
@@ -193,6 +205,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 			ListChoice::For {
 				address_or_vault_name,
 				show_g1v1,
+				show_type,
 			} => {
 				let account_tree_node =
 					retrieve_account_tree_node(db, address_or_vault_name).await?;
@@ -200,7 +213,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
 				let base_account_tree_node =
 					vault_account::get_base_account_tree_node(&account_tree_node);
 
-				let table = display::compute_vault_accounts_table_with_g1v1(&[base_account_tree_node], show_g1v1)?;
+				let table = display::compute_vault_accounts_table_with_g1v1(&[base_account_tree_node], show_g1v1, show_type)?;
 
 				println!(
 					"available SS58 Addresses linked to {}:",
@@ -742,67 +755,31 @@ fn prompt_secret_and_compute_vault_data_to_import(
 pub async fn create_base_account_for_vault_data_to_import<C>(
 	db_tx: &C,
 	vault_data: &VaultDataToImport,
-	password: Option<&String>,
+	password_opt: Option<&String>,
 	crypto_scheme: Option<CryptoScheme>,
 ) -> Result<vault_account::Model, GcliError>
 where
 	C: ConnectionTrait,
 {
-	let address_to_import = vault_data.key_pair.address().to_string();
-	println!("Trying to import for SS58 address :'{}'", address_to_import);
-	println!();
-
-	let vault_account = if let Some(existing_vault_account) =
-		vault_account::find_by_id(db_tx, &DbAccountId::from(address_to_import.clone())).await?
-	{
-		if existing_vault_account.is_base_account() {
-			println!("You are trying to add {address_to_import} as a <Base> account while it already exists as a <Base> account.");
-			println!();
-			println!("Do you want to:");
-			println!("1. keep the existing <Base> account and cancel import");
-			println!("2. overwrite existing <Base> account with the new encrypted key (children will be re-parented)");
-		} else {
-			// Existing derivation account
-			let account_tree_node_hierarchy =
-				vault_account::fetch_base_account_tree_node_hierarchy_unwrapped(
-					db_tx,
-					&address_to_import,
-				)
-				.await?;
-			let account_tree_node_for_address = vault_account::get_account_tree_node_for_address(
-				&account_tree_node_hierarchy,
-				&address_to_import,
-			);
+	let address = vault_data.key_pair.address().to_string();
 
-			let base_parent_hierarchy_account_tree_node =
-				vault_account::get_base_parent_hierarchy_account_tree_node(
-					&account_tree_node_for_address,
-				);
-
-			let parent_hierarchy_table =
-				display::compute_vault_accounts_table(&[base_parent_hierarchy_account_tree_node])?;
+	// Check if the account already exists
+	let existing_vault_account = vault_account::find_by_id(db_tx, &DbAccountId(vault_data.key_pair.address())).await?;
 
-			println!("You are trying to add {address_to_import} as a <Base> account");
-			println!(
-				"but it is already present as `{}` derivation of {} account.",
-				existing_vault_account.path.clone().unwrap(),
-				existing_vault_account.parent.clone().unwrap()
-			);
-			println!();
-			println!("Its parent hierarchy is this:");
-			println!("{parent_hierarchy_table}");
-			println!();
-			println!("Do you want to:");
-			println!("1. keep the existing derivation and cancel import");
-			println!("2. delete the derivation account and replace it with the new <Base> account (children will be re-parented)");
-		}
+	let password = match password_opt {
+		Some(password) => password.clone(),
+		None => inputs::prompt_password_query("Enter password to encrypt the key: ")?,
+	};
 
-		let result = inputs::select_action("Your choice?", vec!["1", "2"])?;
-		match result {
-			"2" => {
-				let encrypted_suri =
-					compute_encrypted_suri(password, vault_data.secret_suri.clone())?;
+	let encrypted_suri = compute_encrypted_suri(password.clone(), vault_data.secret_suri.clone())?;
 
+	if let Some(existing_vault_account) = existing_vault_account {
+		// Existing account
+		match inputs::confirm_action(&format!(
+			"Account {} already exists. Do you want to update it?",
+			existing_vault_account
+		))? {
+			true => {
 				println!(
 					"(Optional) Enter a name for the vault entry (leave empty to remove the name)"
 				);
@@ -821,14 +798,15 @@ where
 				));
 				vault_account.encrypted_suri = Set(Some(encrypted_suri));
 				vault_account.name = Set(name.clone());
+				vault_account.secret_format = Set(Some(vault_data.secret_format.into()));
 				let updated_vault_account =
 					vault_account::update_account(db_tx, vault_account).await?;
-
+				
 				println!("Updating vault account {updated_vault_account}");
-				updated_vault_account
+				Ok(updated_vault_account)
 			}
 			_ => {
-				return Err(GcliError::Input("import canceled".into()));
+				Err(GcliError::Input("import canceled".into()))
 			}
 		}
 	} else {
@@ -842,20 +820,19 @@ where
 
 		let crypto_scheme = map_secret_format_to_crypto_scheme(secret_format, crypto_scheme);
 
-		let base_account = vault_account::create_base_account(
+		let account = vault_account::create_base_account(
 			db_tx,
-			&address_to_import,
+			&address,
 			name.as_ref(),
 			crypto_scheme,
 			encrypted_suri,
+			secret_format,
 		)
 		.await?;
-		println!("Creating <Base> account {base_account}");
-
-		base_account
-	};
-
-	Ok(vault_account)
+		
+		println!("Creating <Base> account {account}");
+		Ok(account)
+	}
 }
 
 /// Creates a `derivation` vault account for data provided and returns it
@@ -982,18 +959,10 @@ where
 
 /// Function will ask for password if not present and compute the encrypted suri
 fn compute_encrypted_suri(
-	password: Option<&String>,
+	password: String,
 	secret_suri: String,
 ) -> Result<Vec<u8>, GcliError> {
-	let password = match password.cloned() {
-		Some(password) => password,
-		_ => {
-			println!("Enter password to protect the key");
-			inputs::prompt_password_confirm()?
-		}
-	};
-
-	Ok(encrypt(secret_suri.as_bytes(), password).map_err(|e| anyhow!(e))?)
+	encrypt(secret_suri.as_bytes(), password).map_err(|e| GcliError::Input(e.to_string()))
 }
 
 fn get_vault_key_path(data: &Data, vault_filename: &str) -> PathBuf {
@@ -1144,12 +1113,12 @@ mod tests {
 		Some(String::from("//Alice"))
 	)]
 	#[case(
-        String::from(
-            "bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice//Bob/soft1/soft2"
-        ),
-        Some(String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk")),
-        Some(String::from("//Alice//Bob/soft1/soft2"))
-    )]
+		String::from(
+			"bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice//Bob/soft1/soft2"
+		),
+		Some(String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk")),
+		Some(String::from("//Alice//Bob/soft1/soft2"))
+	)]
 	#[case(
 		String::from("bottom drive obey lake curtain smoke basket hold race lonely fit walk"),
 		Some(String::from(
diff --git a/src/commands/vault/display.rs b/src/commands/vault/display.rs
index e51560a..935b55e 100644
--- a/src/commands/vault/display.rs
+++ b/src/commands/vault/display.rs
@@ -27,24 +27,36 @@ pub fn compute_vault_accounts_table(
 	account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
 ) -> Result<Table, GcliError> {
 	// Appel to the new function with show_g1v1 = true to maintain compatibility
-	compute_vault_accounts_table_with_g1v1(account_tree_nodes, true)
+	compute_vault_accounts_table_with_g1v1(account_tree_nodes, true, false)
 }
 
 pub fn compute_vault_accounts_table_with_g1v1(
 	account_tree_nodes: &[Rc<RefCell<AccountTreeNode>>],
 	show_g1v1: bool,
+	show_type: bool,
 ) -> Result<Table, GcliError> {
 	let mut table = Table::new();
 	table.load_preset(comfy_table::presets::UTF8_BORDERS_ONLY);
-	table.set_header(vec![
+	
+	// Prepare header based on options
+	let mut header = vec![
 		if show_g1v1 { "SS58 Address/G1v1 public key" } else { "SS58 Address" },
 		"Crypto",
-		"Path",
-		"Name",
-	]);
+	];
+	
+	// Add Type column if show_type is true
+	if show_type {
+		header.push("Type");
+	}
+	
+	// Add remaining columns
+	header.push("Path");
+	header.push("Name");
+	
+	table.set_header(header);
 
 	for account_tree_node in account_tree_nodes {
-		let _ = add_account_tree_node_to_table_with_g1v1(&mut table, account_tree_node, show_g1v1);
+		let _ = add_account_tree_node_to_table_with_g1v1(&mut table, account_tree_node, show_g1v1, show_type);
 	}
 
 	Ok(table)
@@ -55,21 +67,22 @@ fn add_account_tree_node_to_table(
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
 ) -> Result<(), GcliError> {
 	// Appel to the new function with show_g1v1 = true to maintain compatibility
-	add_account_tree_node_to_table_with_g1v1(table, account_tree_node, true)
+	add_account_tree_node_to_table_with_g1v1(table, account_tree_node, true, false)
 }
 
 fn add_account_tree_node_to_table_with_g1v1(
 	table: &mut Table,
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
 	show_g1v1: bool,
+	show_type: bool,
 ) -> Result<(), GcliError> {
-	let rows = compute_vault_accounts_row_with_g1v1(account_tree_node, show_g1v1)?;
+	let rows = compute_vault_accounts_row_with_g1v1(account_tree_node, show_g1v1, show_type)?;
 	rows.iter().for_each(|row| {
 		table.add_row(row.clone());
 	});
 
 	for child in &account_tree_node.borrow().children {
-		let _ = add_account_tree_node_to_table_with_g1v1(table, child, show_g1v1);
+		let _ = add_account_tree_node_to_table_with_g1v1(table, child, show_g1v1, show_type);
 	}
 
 	Ok(())
@@ -77,12 +90,12 @@ fn add_account_tree_node_to_table_with_g1v1(
 
 /// Computes one or more row of the table for selected account_tree_node
 ///
-/// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key
+/// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key if show_g1v1 is true
 pub fn compute_vault_accounts_row(
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
 ) -> Result<Vec<Vec<Cell>>, GcliError> {
 	// Appel to the new function with show_g1v1 = true to maintain compatibility
-	compute_vault_accounts_row_with_g1v1(account_tree_node, true)
+	compute_vault_accounts_row_with_g1v1(account_tree_node, true, false)
 }
 
 /// Computes one or more row of the table for selected account_tree_node
@@ -91,6 +104,7 @@ pub fn compute_vault_accounts_row(
 pub fn compute_vault_accounts_row_with_g1v1(
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
 	show_g1v1: bool,
+	show_type: bool,
 ) -> Result<Vec<Vec<Cell>>, GcliError> {
 	let empty_string = "".to_string();
 
@@ -117,42 +131,70 @@ pub fn compute_vault_accounts_row_with_g1v1(
 
 	let mut rows: Vec<Vec<Cell>> = vec![];
 
-	let (path, crypto) = if let Some(path) = account_tree_node.account.path.clone() {
-		(path, empty_string.clone())
+	let (path, crypto, wallet_type) = if let Some(path) = account_tree_node.account.path.clone() {
+		(path, empty_string.clone(), empty_string.clone())
 	} else {
 		let crypto_scheme = CryptoScheme::from(account_tree_node.account.crypto_scheme.unwrap());
 		let crypto_scheme_str: &str = crypto_scheme.into();
 		
-		// Check if it's an ed25519 key (for G1v1, we always use Ed25519)
-		// We don't have access to the secret_format field, so we rely only on the crypto_scheme
-		let is_ed25519 = crypto_scheme == CryptoScheme::Ed25519;
+		// Determine the wallet type based on the secret format
+		let wallet_type = if let Some(secret_format) = &account_tree_node.account.secret_format {
+			// If the secret format is available, use it to determine the type
+			match crate::keys::SecretFormat::from(*secret_format) {
+				crate::keys::SecretFormat::G1v1 => "G1v1".to_string(),
+				crate::keys::SecretFormat::Substrate => "Mnemonic".to_string(),
+				crate::keys::SecretFormat::Seed => "Seed".to_string(),
+				crate::keys::SecretFormat::Predefined => "Predefined".to_string(),
+			}
+		} else {
+			// If the secret format is not available, display "Unknown"
+			"Unknown".to_string()
+		};
 		
-		// Adding 2nd row for G1v1 public key only if show_g1v1 is true and it's an Ed25519 key
+		// Add a second line for the G1v1 public key only if show_g1v1 is true and it's an Ed25519 key
+		let is_ed25519 = crypto_scheme == CryptoScheme::Ed25519;
 		if show_g1v1 && is_ed25519 {
-			rows.push(vec![Cell::new(format!(
+			let mut g1v1_row = vec![Cell::new(format!(
 				"â”” G1v1: {}",
 				cesium::compute_g1v1_public_key_from_ed25519_account_id(
 					&account_tree_node.account.address.0
 				)
-			))]);
+			))];
+			
+			// Add empty cells to align with the main line
+			g1v1_row.push(Cell::new(""));
+			if show_type {
+				g1v1_row.push(Cell::new(""));
+			}
+			g1v1_row.push(Cell::new(""));
+			g1v1_row.push(Cell::new(""));
+			
+			rows.push(g1v1_row);
 		}
 
 		(
 			format!("<{}>", account_tree_node.account.account_type()),
 			crypto_scheme_str.to_string(),
+			wallet_type,
 		)
 	};
 
-	// Adding 1st row
-	rows.insert(
-		0,
-		vec![
-			Cell::new(&address),
-			Cell::new(crypto),
-			Cell::new(&path),
-			Cell::new(&name),
-		],
-	);
+	// Add the first line
+	let mut main_row = vec![
+		Cell::new(&address),
+		Cell::new(crypto),
+	];
+	
+	// Add the Type column if show_type is true
+	if show_type {
+		main_row.push(Cell::new(wallet_type));
+	}
+	
+	// Add the remaining columns
+	main_row.push(Cell::new(&path));
+	main_row.push(Cell::new(&name));
+	
+	rows.insert(0, main_row);
 
 	Ok(rows)
 }
@@ -229,7 +271,7 @@ mod tests {
 		#[test]
 		fn test_compute_vault_accounts_table_with_g1v1_empty() {
 			// Test with show_g1v1 = true (default behavior)
-			let table = compute_vault_accounts_table_with_g1v1(&[], true).unwrap();
+			let table = compute_vault_accounts_table_with_g1v1(&[], true, false).unwrap();
 			
 			let expected_table_with_g1v1 = indoc! {r#"
 			┌─────────────────────────────────────────────────────┐
@@ -241,7 +283,7 @@ mod tests {
 			assert_eq!(table.to_string(), expected_table_with_g1v1);
 
 			// Test with show_g1v1 = false
-			let table = compute_vault_accounts_table_with_g1v1(&[], false).unwrap();
+			let table = compute_vault_accounts_table_with_g1v1(&[], false, false).unwrap();
 			
 			let expected_table_without_g1v1 = indoc! {r#"
 			┌─────────────────────────────────────┐
@@ -260,7 +302,7 @@ mod tests {
 			let account_tree_nodes = vec![account_tree_node, g1v1_account_tree_node];
 
 			// Test with show_g1v1 = true (default behavior)
-			let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
+			let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true, false).unwrap();
 			
 			let expected_table_with_g1v1 = indoc! {r#"
 			┌──────────────────────────────────────────────────────────────────────────────────────────┐
@@ -279,7 +321,7 @@ mod tests {
 			assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1);
 
 			// Test with show_g1v1 = false
-			let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
+			let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false, false).unwrap();
 			
 			let expected_table_without_g1v1 = indoc! {r#"
 			┌──────────────────────────────────────────────────────────────────────────────────────────┐
@@ -304,7 +346,7 @@ mod tests {
 			let account_tree_nodes = vec![child1];
 
 			// Test with show_g1v1 = true (default behavior)
-			let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true).unwrap();
+			let table_with_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true, false).unwrap();
 			
 			let expected_table_with_g1v1 = indoc! {r#"
 			┌─────────────────────────────────────────────────────────────────────────────────────┐
@@ -318,7 +360,7 @@ mod tests {
 			assert_eq!(table_with_g1v1.to_string(), expected_table_with_g1v1);
 
 			// Test with show_g1v1 = false
-			let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false).unwrap();
+			let table_without_g1v1 = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false, false).unwrap();
 			
 			let expected_table_without_g1v1 = indoc! {r#"
 			┌─────────────────────────────────────────────────────────────────────────────────────┐
@@ -331,5 +373,49 @@ mod tests {
 			
 			assert_eq!(table_without_g1v1.to_string(), expected_table_without_g1v1);
 		}
+
+		#[test]
+		fn test_compute_vault_accounts_table_with_type() {
+			let account_tree_node = mother_account_tree_node();
+			let g1v1_account_tree_node = mother_g1v1_account_tree_node();
+			let account_tree_nodes = vec![account_tree_node, g1v1_account_tree_node];
+
+			// Test with show_type = true and show_g1v1 = true
+			let table_with_g1v1_and_type = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, true, true).unwrap();
+			
+			let expected_table_with_g1v1_and_type = indoc! {r#"
+			┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐
+			│ SS58 Address/G1v1 public key                           Crypto    Type       Path     Name           │
+			╞═════════════════════════════════════════════════════════════════════════════════════════════════════╡
+			│ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV       sr25519   Mnemonic   <Base>   Mother         │
+			│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH                          //0      Child 1        │
+			│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d                        //0      Grandchild 1   │
+			│ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o                          //1      <Mother//1>    │
+			│ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT                        //1      <Mother//1//1> │
+			│ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4       ed25519   G1v1       <Base>   MotherG1v1     │
+			│ └ G1v1: 86pW1doyJPVH3jeDPZNQa1UZFBo5zcdvHERcaeE758W7                                                │
+			└─────────────────────────────────────────────────────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table_with_g1v1_and_type.to_string(), expected_table_with_g1v1_and_type);
+			
+			// Test with show_type = true and show_g1v1 = false
+			let table_with_type = compute_vault_accounts_table_with_g1v1(&account_tree_nodes, false, true).unwrap();
+			
+			let expected_table_with_type = indoc! {r#"
+			┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐
+			│ SS58 Address                                           Crypto    Type       Path     Name           │
+			╞═════════════════════════════════════════════════════════════════════════════════════════════════════╡
+			│ 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV       sr25519   Mnemonic   <Base>   Mother         │
+			│ ├ 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH                          //0      Child 1        │
+			│ │ ├ 5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d                        //0      Grandchild 1   │
+			│ ├ 5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o                          //1      <Mother//1>    │
+			│ │ ├ 5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT                        //1      <Mother//1//1> │
+			│ 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4       ed25519   G1v1       <Base>   MotherG1v1     │
+			└─────────────────────────────────────────────────────────────────────────────────────────────────────┘"#
+			};
+			
+			assert_eq!(table_with_type.to_string(), expected_table_with_type);
+		}
 	}
 }
diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs
index d04b02a..939ee75 100644
--- a/src/entities/vault_account.rs
+++ b/src/entities/vault_account.rs
@@ -40,6 +40,8 @@ pub struct Model {
 	pub encrypted_suri: Option<Vec<u8>>,
 	/// ForeignKey to parent vault_account SS58 Address - None if for a "base" account
 	pub parent: Option<DbAccountId>,
+	/// Secret format used for the account - Only set for "base" accounts
+	pub secret_format: Option<DbSecretFormat>,
 }
 
 impl Model {
@@ -243,6 +245,46 @@ impl From<DbCryptoScheme> for crate::keys::CryptoScheme {
 	}
 }
 
+/// Enum for SecretFormat in the database
+#[derive(Debug, Copy, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
+#[sea_orm(
+	rs_type = "String",
+	db_type = "String(StringLen::None)",
+	enum_name = "secret_format"
+)]
+pub enum DbSecretFormat {
+	#[sea_orm(string_value = "seed")]
+	Seed,
+	#[sea_orm(string_value = "substrate")]
+	Substrate,
+	#[sea_orm(string_value = "predefined")]
+	Predefined,
+	#[sea_orm(string_value = "g1v1")]
+	G1v1,
+}
+
+impl From<crate::keys::SecretFormat> for DbSecretFormat {
+	fn from(format: crate::keys::SecretFormat) -> Self {
+		match format {
+			crate::keys::SecretFormat::Seed => DbSecretFormat::Seed,
+			crate::keys::SecretFormat::Substrate => DbSecretFormat::Substrate,
+			crate::keys::SecretFormat::Predefined => DbSecretFormat::Predefined,
+			crate::keys::SecretFormat::G1v1 => DbSecretFormat::G1v1,
+		}
+	}
+}
+
+impl From<DbSecretFormat> for crate::keys::SecretFormat {
+	fn from(format: DbSecretFormat) -> Self {
+		match format {
+			DbSecretFormat::Seed => crate::keys::SecretFormat::Seed,
+			DbSecretFormat::Substrate => crate::keys::SecretFormat::Substrate,
+			DbSecretFormat::Predefined => crate::keys::SecretFormat::Predefined,
+			DbSecretFormat::G1v1 => crate::keys::SecretFormat::G1v1,
+		}
+	}
+}
+
 #[derive(Copy, Clone, Debug, EnumIter)]
 pub enum Relation {
 	ParentAccount,
@@ -868,6 +910,7 @@ pub async fn create_base_account<C>(
 	name: Option<&String>,
 	crypto_scheme: crate::keys::CryptoScheme,
 	encrypted_suri: Vec<u8>,
+	secret_format: crate::keys::SecretFormat,
 ) -> Result<Model, GcliError>
 where
 	C: ConnectionTrait,
@@ -890,6 +933,7 @@ where
 				crypto_scheme: Set(Some(crypto_scheme.into())),
 				encrypted_suri: Set(Some(encrypted_suri)),
 				parent: Default::default(),
+				secret_format: Set(Some(secret_format.into())),
 			};
 			vault_account.insert(db).await?
 		}
@@ -927,6 +971,7 @@ where
 				crypto_scheme: Set(None),
 				encrypted_suri: Set(None),
 				parent: Set(Some(parent_address.to_string().into())),
+				secret_format: Set(None),
 			};
 			vault_account.insert(db).await?
 		}
@@ -983,6 +1028,7 @@ pub mod tests {
 					crypto_scheme: None,
 					encrypted_suri: None,
 					parent: Some(child1_address.clone()),
+					secret_format: None,
 				},
 				children: vec![],
 				parent: None,
@@ -997,6 +1043,7 @@ pub mod tests {
 					crypto_scheme: None,
 					encrypted_suri: None,
 					parent: Some(child2_address.clone()),
+					secret_format: None,
 				},
 				children: vec![],
 				parent: None,
@@ -1010,6 +1057,7 @@ pub mod tests {
 					crypto_scheme: None,
 					encrypted_suri: None,
 					parent: Some(mother_address.clone()),
+					secret_format: None,
 				},
 				children: vec![grandchild1.clone()],
 				parent: None,
@@ -1024,6 +1072,7 @@ pub mod tests {
 					crypto_scheme: None,
 					encrypted_suri: None,
 					parent: Some(mother_address.clone()),
+					secret_format: None,
 				},
 				children: vec![grandchild2.clone()],
 				parent: None,
@@ -1039,6 +1088,7 @@ pub mod tests {
 						vault::encrypt(SUBSTRATE_MNEMONIC.as_bytes(), "".to_string()).unwrap(),
 					),
 					parent: None,
+					secret_format: Some(DbSecretFormat::Substrate),
 				},
 				children: vec![child1.clone(), child2.clone()],
 				parent: None,
@@ -1073,6 +1123,7 @@ pub mod tests {
 						vault::encrypt(secret_suri.as_bytes(), "".to_string()).unwrap(),
 					),
 					parent: None,
+					secret_format: Some(DbSecretFormat::G1v1),
 				},
 				children: vec![],
 				parent: None,
-- 
GitLab


From a36c831864b605bf7fa5bfd4864320a68646ab09 Mon Sep 17 00:00:00 2001
From: poka <poka@p2p.legal>
Date: Thu, 13 Mar 2025 18:48:18 +0100
Subject: [PATCH 4/6] remove unused methods

---
 src/commands/transfer.rs      |  2 +-
 src/commands/vault.rs         |  2 +-
 src/commands/vault/display.rs | 18 ------------------
 src/inputs.rs                 | 10 ----------
 src/keys.rs                   | 27 ---------------------------
 5 files changed, 2 insertions(+), 57 deletions(-)

diff --git a/src/commands/transfer.rs b/src/commands/transfer.rs
index 16f2e5e..1a97717 100644
--- a/src/commands/transfer.rs
+++ b/src/commands/transfer.rs
@@ -1,6 +1,6 @@
 use crate::*;
 
-#[cfg(any(feature = "dev", feature = "gdev"))] // find how to get runtime calls
+#[cfg(feature = "gdev")] // find how to get runtime calls
 type Call = runtime::runtime_types::gdev_runtime::RuntimeCall;
 type BalancesCall = runtime::runtime_types::pallet_balances::pallet::Call;
 
diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index 10dcdb1..a3fa4b2 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -7,7 +7,7 @@ use crate::*;
 use age::secrecy::Secret;
 use sea_orm::ActiveValue::Set;
 use sea_orm::{ConnectionTrait, TransactionTrait};
-use sea_orm::{DbErr, ModelTrait};
+use sea_orm::ModelTrait;
 use sp_core::crypto::AddressUri;
 use std::cell::RefCell;
 use std::io::{Read, Write};
diff --git a/src/commands/vault/display.rs b/src/commands/vault/display.rs
index 935b55e..5f024d6 100644
--- a/src/commands/vault/display.rs
+++ b/src/commands/vault/display.rs
@@ -62,14 +62,6 @@ pub fn compute_vault_accounts_table_with_g1v1(
 	Ok(table)
 }
 
-fn add_account_tree_node_to_table(
-	table: &mut Table,
-	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
-) -> Result<(), GcliError> {
-	// Appel to the new function with show_g1v1 = true to maintain compatibility
-	add_account_tree_node_to_table_with_g1v1(table, account_tree_node, true, false)
-}
-
 fn add_account_tree_node_to_table_with_g1v1(
 	table: &mut Table,
 	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
@@ -88,16 +80,6 @@ fn add_account_tree_node_to_table_with_g1v1(
 	Ok(())
 }
 
-/// Computes one or more row of the table for selected account_tree_node
-///
-/// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key if show_g1v1 is true
-pub fn compute_vault_accounts_row(
-	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
-) -> Result<Vec<Vec<Cell>>, GcliError> {
-	// Appel to the new function with show_g1v1 = true to maintain compatibility
-	compute_vault_accounts_row_with_g1v1(account_tree_node, true, false)
-}
-
 /// Computes one or more row of the table for selected account_tree_node
 ///
 /// For ed25519 keys, will display over 2 rows to also show the base 58 G1v1 public key if show_g1v1 is true
diff --git a/src/inputs.rs b/src/inputs.rs
index 0823130..ee84a6d 100644
--- a/src/inputs.rs
+++ b/src/inputs.rs
@@ -8,10 +8,6 @@ pub fn prompt_password() -> Result<String, GcliError> {
 	prompt_password_query("Password")
 }
 
-pub fn prompt_password_confirm() -> Result<String, GcliError> {
-	prompt_password_query_confirm("Password")
-}
-
 pub fn prompt_password_query(query: impl ToString) -> Result<String, GcliError> {
 	inquire::Password::new(query.to_string().as_str())
 		.without_confirmation()
@@ -39,12 +35,6 @@ pub fn prompt_seed() -> Result<String, GcliError> {
 		.map_err(|e| GcliError::Input(e.to_string()))
 }
 
-pub fn prompt_password_query_confirm(query: impl ToString) -> Result<String, GcliError> {
-	inquire::Password::new(query.to_string().as_str())
-		.prompt()
-		.map_err(|e| GcliError::Input(e.to_string()))
-}
-
 /// Prompt for a (direct) vault name (cannot contain derivation path)
 ///
 /// Also preventing to use '<' and '>' as those are used in the display
diff --git a/src/keys.rs b/src/keys.rs
index cdbfb44..6097ca5 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -249,15 +249,6 @@ pub fn seed_from_cesium(id: &str, pwd: &str) -> [u8; 32] {
 	seed
 }
 
-/// ask user to input a secret
-pub fn prompt_secret_substrate() -> sr25519::Pair {
-    // Only interested in the keypair which is the second element of the tuple
-    match prompt_secret_substrate_and_compute_keypair(CryptoScheme::Sr25519).1 {
-        KeyPair::Sr25519(pair) => pair,
-        _ => panic!("Expected Sr25519 keypair"),
-    }
-}
-
 pub fn prompt_secret_substrate_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
     loop {
         println!("Substrate URI can be a mnemonic or a mini-secret ('0x' prefixed seed) together with optional derivation path");
@@ -302,15 +293,6 @@ pub fn prompt_secret_cesium_and_compute_keypair(_crypto_scheme: CryptoScheme) ->
     }
 }
 
-/// ask user to input a seed
-pub fn prompt_seed() -> sr25519::Pair {
-    // Only interested in the keypair which is the second element of the tuple
-    match prompt_seed_and_compute_keypair(CryptoScheme::Sr25519).1 {
-        KeyPair::Sr25519(pair) => pair,
-        _ => panic!("Expected Sr25519 keypair"),
-    }
-}
-
 pub fn prompt_seed_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
     loop {
         let seed_str = inputs::prompt_seed().unwrap();
@@ -333,15 +315,6 @@ pub fn prompt_seed_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String,
     }
 }
 
-/// ask user pass (Cesium format)
-pub fn prompt_predefined() -> sr25519::Pair {
-    // Only interested in the keypair which is the second element of the tuple
-    match prompt_predefined_and_compute_keypair(CryptoScheme::Sr25519).1 {
-        KeyPair::Sr25519(pair) => pair,
-        _ => panic!("Expected Sr25519 keypair"),
-    }
-}
-
 pub fn prompt_predefined_and_compute_keypair(crypto_scheme: CryptoScheme) -> (String, KeyPair) {
     let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap();
     let suri = predefined_suri(&deriv);
-- 
GitLab


From 80d3023d934640dfb22abfb300b17fe854a7a988 Mon Sep 17 00:00:00 2001
From: poka <poka@p2p.legal>
Date: Thu, 13 Mar 2025 22:33:30 +0100
Subject: [PATCH 5/6] add non interactive mode

---
 src/commands/vault.rs | 124 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 104 insertions(+), 20 deletions(-)

diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index a3fa4b2..07b40fc 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);
 
-- 
GitLab


From 47c0fa7c94f96712d23f4d087c7d315da534a96f Mon Sep 17 00:00:00 2001
From: poka <poka@p2p.legal>
Date: Tue, 11 Mar 2025 17:53:02 +0100
Subject: [PATCH 6/6] Add profile command for nostr data

---
 Cargo.lock              |  55 +++
 Cargo.toml              |   8 +-
 src/commands.rs         |   1 +
 src/commands/profile.rs | 947 ++++++++++++++++++++++++++++++++++++++++
 src/commands/vault.rs   |   2 +-
 src/conf.rs             |  11 +-
 src/main.rs             |   4 +
 7 files changed, 1025 insertions(+), 3 deletions(-)
 create mode 100644 src/commands/profile.rs

diff --git a/Cargo.lock b/Cargo.lock
index a9cd348..b2ed55d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1587,6 +1587,12 @@ dependencies = [
  "parking_lot_core",
 ]
 
+[[package]]
+name = "data-encoding"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
+
 [[package]]
 name = "der"
 version = "0.7.9"
@@ -2342,6 +2348,7 @@ version = "0.4.0"
 dependencies = [
  "age",
  "anyhow",
+ "bech32",
  "bip39",
  "bs58",
  "clap",
@@ -2361,12 +2368,16 @@ dependencies = [
  "rstest",
  "scrypt",
  "sea-orm",
+ "secp256k1",
  "serde",
  "serde_json",
+ "sha2 0.10.8",
  "sp-core",
  "sp-runtime",
  "subxt",
  "tokio",
+ "tokio-tungstenite",
+ "url",
 ]
 
 [[package]]
@@ -5172,6 +5183,7 @@ version = "0.28.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
 dependencies = [
+ "rand",
  "secp256k1-sys",
 ]
 
@@ -6742,6 +6754,22 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-tungstenite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls 0.22.4",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls 0.25.0",
+ "tungstenite",
+ "webpki-roots 0.26.6",
+]
+
 [[package]]
 name = "tokio-util"
 version = "0.7.12"
@@ -6969,6 +6997,27 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
+[[package]]
+name = "tungstenite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http 1.1.0",
+ "httparse",
+ "log",
+ "rand",
+ "rustls 0.22.4",
+ "rustls-pki-types",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
 [[package]]
 name = "twox-hash"
 version = "1.6.3"
@@ -7114,6 +7163,12 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
 [[package]]
 name = "utf8parse"
 version = "0.2.2"
diff --git a/Cargo.toml b/Cargo.toml
index ce66a64..da38696 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ sp-runtime = { git = "https://github.com/duniter/duniter-polkadot-sdk.git", bran
 
 # crates.io dependencies
 anyhow = "^1.0"
+bech32 = "^0.9.1"
 clap = { version = "^4.5.19", features = ["derive"] }
 codec = { package = "parity-scale-codec", version = "^3.6.12" }
 env_logger = "^0.10"
@@ -35,16 +36,21 @@ hex = "^0.4.3"
 log = "^0.4.22"
 reqwest = { version = "^0.11.27", default-features = false, features = [
     "rustls-tls",
+    "json",
 ] }
 inquire = "^0.7.5"
 serde = { version = "^1.0", features = ["derive"] }
 serde_json = "^1.0.128"
-tokio = { version = "^1.40.0", features = ["macros"] }
+tokio = { version = "^1.40.0", features = ["macros", "time"] }
+tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-webpki-roots"] }
+url = "2.5.0"
 confy = "^0.5.1"
 bs58 = "^0.5.1"
 directories = "^5.0.1"
 comfy-table = "^7.1.1"
 sea-orm = { version = "1.1.0", features = [ "sqlx-sqlite", "runtime-tokio-native-tls", "macros" ] }
+sha2 = "0.10.8"
+secp256k1 = { version = "0.28.2", features = ["rand", "recovery"] }
 
 # crypto
 scrypt = { version = "^0.11", default-features = false } # for old-style key generation
diff --git a/src/commands.rs b/src/commands.rs
index 15f96ba..a1e0dc8 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -8,6 +8,7 @@ pub mod expire;
 pub mod identity;
 pub mod net_test;
 pub mod oneshot;
+pub mod profile;
 pub mod publish;
 pub mod revocation;
 pub mod runtime;
diff --git a/src/commands/profile.rs b/src/commands/profile.rs
new file mode 100644
index 0000000..954a408
--- /dev/null
+++ b/src/commands/profile.rs
@@ -0,0 +1,947 @@
+use crate::*;
+use anyhow::anyhow;
+use serde::{Deserialize, Serialize};
+use serde_json::{json, Value};
+use std::collections::HashMap;
+use std::time::{SystemTime, UNIX_EPOCH};
+use sp_core::crypto::Pair;
+use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
+use futures::{SinkExt, StreamExt};
+use url::Url;
+use sha2::{Sha256, Digest};
+use secp256k1::{Secp256k1, SecretKey, PublicKey};
+use bech32::{self, ToBase32, Variant};
+
+/// Derive a Nostr key from a Substrate key
+fn derive_nostr_keys_from_substrate(keypair: &KeyPair) -> Result<(SecretKey, PublicKey), GcliError> {
+    // Get the seed from the keypair - use a more direct approach
+    let seed = match keypair {
+        KeyPair::Sr25519(pair) => {
+            // For Sr25519, we'll use the raw bytes directly
+            let key_bytes = pair.to_raw_vec();
+            // Take the first 32 bytes as the seed
+            let mut seed = [0u8; 32];
+            for i in 0..std::cmp::min(32, key_bytes.len()) {
+                seed[i] = key_bytes[i];
+            }
+            seed.to_vec()
+        }
+        KeyPair::Ed25519(pair) => {
+            // For Ed25519, we'll use the raw bytes directly
+            let key_bytes = pair.to_raw_vec();
+            // Take the first 32 bytes as the seed
+            let mut seed = [0u8; 32];
+            for i in 0..std::cmp::min(32, key_bytes.len()) {
+                seed[i] = key_bytes[i];
+            }
+            seed.to_vec()
+        }
+    };
+    
+    // Create a secp256k1 secret key from the seed
+    let secp = Secp256k1::new();
+    let secret_key = SecretKey::from_slice(&seed[0..32])
+        .map_err(|e| anyhow!("Failed to create secp256k1 secret key: {}", e))?;
+    
+    // Derive the public key
+    let public_key = PublicKey::from_secret_key(&secp, &secret_key);
+    
+    Ok((secret_key, public_key))
+}
+
+/// Convert a hex string to bech32 format with the given prefix (npub/nsec)
+fn hex_to_bech32(hex_key: &str, prefix: &str) -> Result<String, GcliError> {
+    // Decode the hex string to bytes
+    let bytes = hex::decode(hex_key)
+        .map_err(|e| anyhow!("Failed to decode hex key: {}", e))?;
+    
+    // Convert bytes to base32
+    let base32_data = bytes.to_base32();
+    
+    // Encode as bech32
+    let bech32_str = bech32::encode(prefix, base32_data, Variant::Bech32)
+        .map_err(|e| anyhow!("Failed to encode bech32: {}", e))?;
+    
+    Ok(bech32_str)
+}
+
+/// Get Nostr public key in hex format
+fn get_nostr_pubkey(keypair: &KeyPair) -> Result<String, GcliError> {
+    let (_, public_key) = derive_nostr_keys_from_substrate(keypair)?;
+    // Nostr uses the x-only public key (32 bytes)
+    let serialized = public_key.serialize();
+    // Skip the first byte (format byte) and take only the x coordinate
+    let pubkey = hex::encode(&serialized[1..33]);
+    Ok(pubkey)
+}
+
+/// Get Nostr public key in bech32 format (npub)
+fn get_nostr_npub(keypair: &KeyPair) -> Result<String, GcliError> {
+    let hex_pubkey = get_nostr_pubkey(keypair)?;
+    let npub = hex_to_bech32(&hex_pubkey, "npub")?;
+    Ok(npub)
+}
+
+/// Get Nostr private key in bech32 format (nsec)
+fn get_nostr_nsec(keypair: &KeyPair) -> Result<String, GcliError> {
+    let (secret_key, _) = derive_nostr_keys_from_substrate(keypair)?;
+    let hex_seckey = hex::encode(secret_key.secret_bytes());
+    let nsec = hex_to_bech32(&hex_seckey, "nsec")?;
+    Ok(nsec)
+}
+
+/// Profile subcommands
+#[derive(Clone, Debug, clap::Parser)]
+pub enum Subcommand {
+    /// Get Nostr profile data from a relay
+    Get {
+        /// Relay URL to fetch profile from
+        #[clap(short, long)]
+        relay: Option<String>,
+    },
+    /// Set Nostr profile data (NIP-01 kind 0 metadata)
+    Set {
+        /// Profile name
+        #[clap(short, long)]
+        name: Option<String>,
+        
+        /// Profile display name
+        #[clap(short = 'd', long)]
+        display_name: Option<String>,
+        
+        /// Profile picture URL
+        #[clap(short = 'p', long)]
+        picture: Option<String>,
+        
+        /// Profile about/description
+        #[clap(short, long)]
+        about: Option<String>,
+        
+        /// Profile website
+        #[clap(short = 'w', long)]
+        website: Option<String>,
+        
+        /// Profile NIP-05 identifier
+        #[clap(short = 'i', long)]
+        nip05: Option<String>,
+        
+        /// Relay URL to publish profile to
+        #[clap(short, long)]
+        relay: Option<String>,
+    },
+}
+
+/// Nostr profile metadata (NIP-01 kind 0)
+#[derive(Debug, Serialize, Deserialize)]
+pub struct NostrProfile {
+    pub name: Option<String>,
+    pub display_name: Option<String>,
+    pub picture: Option<String>,
+    pub about: Option<String>,
+    pub website: Option<String>,
+    pub nip05: Option<String>,
+    #[serde(flatten)]
+    pub additional_fields: HashMap<String, String>,
+}
+
+impl Default for NostrProfile {
+    fn default() -> Self {
+        Self {
+            name: None,
+            display_name: None,
+            picture: None,
+            about: None,
+            website: None,
+            nip05: None,
+            additional_fields: HashMap::new(),
+        }
+    }
+}
+
+/// Nostr event structure
+#[derive(Debug, Serialize, Deserialize)]
+pub struct NostrEvent {
+    pub id: String,
+    pub pubkey: String,
+    pub created_at: u64,
+    pub kind: u32,
+    pub tags: Vec<Vec<String>>,
+    pub content: String,
+    pub sig: String,
+}
+
+impl NostrEvent {
+    /// Create a new unsigned Nostr event
+    pub fn new(pubkey: String, kind: u32, content: String) -> Self {
+        let created_at = SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .unwrap()
+            .as_secs();
+        
+        Self {
+            id: String::new(), // Will be set after serialization
+            pubkey,
+            created_at,
+            kind,
+            tags: Vec::new(),
+            content,
+            sig: String::new(), // Will be set after signing
+        }
+    }
+    
+    /// Calculate the event ID (SHA-256 hash of the serialized event)
+    pub fn calculate_id(&mut self) -> Result<(), GcliError> {
+        // Create a temporary event for serialization exactly as specified by NIP-01
+        // The order is important: [0, pubkey, created_at, kind, tags, content]
+        let temp_event = json!([
+            0,
+            self.pubkey,
+            self.created_at,
+            self.kind,
+            self.tags,
+            self.content
+        ]);
+        
+        // Serialize the event with no whitespace and canonical ordering
+        // Using to_string() instead of to_string_pretty() to avoid whitespace
+        let serialized = serde_json::to_string(&temp_event)
+            .map_err(|e| anyhow!("Failed to serialize event: {}", e))?;
+        
+        // Calculate SHA-256 hash
+        let mut hasher = Sha256::new();
+        hasher.update(serialized.as_bytes());
+        let result = hasher.finalize();
+        
+        // Set the ID
+        self.id = hex::encode(result);
+        Ok(())
+    }
+    
+    /// Sign the event with the given keypair
+    pub fn sign(&mut self, keypair: &KeyPair) -> Result<(), GcliError> {
+        // Calculate ID if not already set
+        if self.id.is_empty() {
+            self.calculate_id()?;
+        }
+        
+        // Derive Nostr keys from Substrate keypair
+        let (secret_key, public_key) = derive_nostr_keys_from_substrate(keypair)?;
+        
+        // Verify that the pubkey in the event matches our derived pubkey
+        let derived_pubkey = hex::encode(&public_key.serialize()[1..33]);
+        if self.pubkey != derived_pubkey {
+            // Update the pubkey to match
+            self.pubkey = derived_pubkey;
+            // Recalculate ID with the correct pubkey
+            self.calculate_id()?;
+        }
+        
+        // Create a secp256k1 context for Schnorr signatures
+        let secp = Secp256k1::new();
+        
+        // Create a message from the event ID
+        let id_bytes = hex::decode(&self.id)
+            .map_err(|e| anyhow!("Failed to decode hex ID: {}", e))?;
+        
+        let message = secp256k1::Message::from_digest_slice(&id_bytes)
+            .map_err(|e| anyhow!("Failed to create secp256k1 message: {}", e))?;
+        
+        // Sign the message with Schnorr
+        let aux_rand = [0u8; 32]; // Use zeros for deterministic signatures
+        let keypair_secp = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
+        let signature = secp256k1::schnorr::Signature::from_slice(
+            secp.sign_schnorr_with_aux_rand(&message, &keypair_secp, &aux_rand).as_ref()
+        ).unwrap();
+        
+        // Set the signature
+        self.sig = hex::encode(signature.as_ref());
+        
+        // Verify the signature
+        let verify_result = verify_nostr_event(self);
+        
+        match verify_result {
+            Ok(true) => {
+                Ok(())
+            },
+            Ok(false) => {
+                self.try_alternative_signing(keypair)
+            },
+            Err(_e) => {
+                self.try_alternative_signing(keypair)
+            }
+        }
+    }
+    
+    /// Try an alternative signing method if the first one fails
+    fn try_alternative_signing(&mut self, keypair: &KeyPair) -> Result<(), GcliError> {
+        // Use a different approach for key derivation
+        let seed = match keypair {
+            KeyPair::Sr25519(pair) => {
+                // Use a hash of the key bytes
+                let mut hasher = Sha256::new();
+                hasher.update(&pair.to_raw_vec());
+                hasher.finalize().to_vec()
+            }
+            KeyPair::Ed25519(pair) => {
+                // Use a hash of the key bytes
+                let mut hasher = Sha256::new();
+                hasher.update(&pair.to_raw_vec());
+                hasher.finalize().to_vec()
+            }
+        };
+        
+        // Create a secp256k1 secret key from the seed
+        let secp = Secp256k1::new();
+        let secret_key = SecretKey::from_slice(&seed[0..32])
+            .map_err(|e| anyhow!("Failed to create alternative secp256k1 secret key: {}", e))?;
+        
+        // Derive the public key
+        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
+        
+        // Update the pubkey in the event
+        let derived_pubkey = hex::encode(&public_key.serialize()[1..33]);
+        self.pubkey = derived_pubkey;
+        
+        // Recalculate ID with the new pubkey
+        self.calculate_id()?;
+        
+        // Create a message from the event ID
+        let id_bytes = hex::decode(&self.id)
+            .map_err(|e| anyhow!("Failed to decode hex ID: {}", e))?;
+        
+        let message = secp256k1::Message::from_digest_slice(&id_bytes)
+            .map_err(|e| anyhow!("Failed to create secp256k1 message: {}", e))?;
+        
+        // Sign the message with Schnorr
+        let aux_rand = [0u8; 32]; // Use zeros for deterministic signatures
+        let keypair_secp = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
+        let signature = secp256k1::schnorr::Signature::from_slice(
+            secp.sign_schnorr_with_aux_rand(&message, &keypair_secp, &aux_rand).as_ref()
+        ).unwrap();
+        
+        // Set the signature
+        self.sig = hex::encode(signature.as_ref());
+        
+        // Verify the signature
+        let verify_result = verify_nostr_event(self);
+        
+        match verify_result {
+            Ok(true) => {
+                Ok(())
+            },
+            Ok(false) => {
+                self.try_third_signing_approach(keypair)
+            },
+            Err(_e) => {
+                self.try_third_signing_approach(keypair)
+            }
+        }
+    }
+
+    /// Try a third signing approach if the first two fail
+    fn try_third_signing_approach(&mut self, keypair: &KeyPair) -> Result<(), GcliError> {
+        // Use a more complex derivation approach
+        let seed = match keypair {
+            KeyPair::Sr25519(pair) => {
+                // Use multiple rounds of hashing
+                let mut hasher1 = Sha256::new();
+                hasher1.update("nostr".as_bytes());
+                hasher1.update(&pair.to_raw_vec());
+                let first_hash = hasher1.finalize();
+                
+                let mut hasher2 = Sha256::new();
+                hasher2.update(first_hash);
+                hasher2.finalize().to_vec()
+            }
+            KeyPair::Ed25519(pair) => {
+                // Use multiple rounds of hashing
+                let mut hasher1 = Sha256::new();
+                hasher1.update("nostr".as_bytes());
+                hasher1.update(&pair.to_raw_vec());
+                let first_hash = hasher1.finalize();
+                
+                let mut hasher2 = Sha256::new();
+                hasher2.update(first_hash);
+                hasher2.finalize().to_vec()
+            }
+        };
+        
+        // Create a secp256k1 secret key from the seed
+        let secp = Secp256k1::new();
+        let secret_key = SecretKey::from_slice(&seed[0..32])
+            .map_err(|e| anyhow!("Failed to create third secp256k1 secret key: {}", e))?;
+        
+        // Derive the public key
+        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
+        
+        // Update the pubkey in the event
+        let derived_pubkey = hex::encode(&public_key.serialize()[1..33]);
+        self.pubkey = derived_pubkey;
+        
+        // Recalculate ID with the new pubkey
+        self.calculate_id()?;
+        
+        // Create a message from the event ID
+        let id_bytes = hex::decode(&self.id)
+            .map_err(|e| anyhow!("Failed to decode hex ID: {}", e))?;
+        
+        let message = secp256k1::Message::from_digest_slice(&id_bytes)
+            .map_err(|e| anyhow!("Failed to create secp256k1 message: {}", e))?;
+        
+        // Sign the message with Schnorr
+        let aux_rand = [0u8; 32]; // Use zeros for deterministic signatures
+        let keypair_secp = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
+        let signature = secp256k1::schnorr::Signature::from_slice(
+            secp.sign_schnorr_with_aux_rand(&message, &keypair_secp, &aux_rand).as_ref()
+        ).unwrap();
+        
+        // Set the signature
+        self.sig = hex::encode(signature.as_ref());
+        
+        // Verify the signature
+        let verify_result = verify_nostr_event(self);
+        
+        match verify_result {
+            Ok(true) => {
+                Ok(())
+            },
+            Ok(false) => {
+                Err(anyhow!("All signature approaches failed").into())
+            },
+            Err(e) => {
+                Err(anyhow!("Error verifying third signature: {}", e).into())
+            }
+        }
+    }
+}
+
+/// Verify a Nostr event signature
+fn verify_nostr_event(event: &NostrEvent) -> Result<bool, GcliError> {
+    // Decode the pubkey
+    let pubkey_bytes = hex::decode(&event.pubkey)
+        .map_err(|e| anyhow!("Failed to decode pubkey: {}", e))?;
+    
+    // Create a secp256k1 context
+    let secp = Secp256k1::new();
+    
+    // Create x-only public key from the 32-byte pubkey
+    let xonly_pubkey = secp256k1::XOnlyPublicKey::from_slice(&pubkey_bytes)
+        .map_err(|e| anyhow!("Failed to create x-only public key: {}", e))?;
+    
+    // Decode the ID
+    let id_bytes = hex::decode(&event.id)
+        .map_err(|e| anyhow!("Failed to decode ID: {}", e))?;
+    
+    // Create a message from the ID
+    let message = secp256k1::Message::from_digest_slice(&id_bytes)
+        .map_err(|e| anyhow!("Failed to create message: {}", e))?;
+    
+    // Decode the signature
+    let sig_bytes = hex::decode(&event.sig)
+        .map_err(|e| anyhow!("Failed to decode signature: {}", e))?;
+    
+    // Create a Schnorr signature from the signature bytes
+    let signature = secp256k1::schnorr::Signature::from_slice(&sig_bytes)
+        .map_err(|e| anyhow!("Failed to create Schnorr signature: {}", e))?;
+    
+    // Verify the Schnorr signature
+    match secp.verify_schnorr(&signature, &message, &xonly_pubkey) {
+        Ok(_) => {
+            Ok(true)
+        },
+        Err(_e) => {
+            Ok(false)
+        }
+    }
+}
+
+/// Handle profile commands
+pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
+    // Enable debug logging for this module if not already set
+    if std::env::var("RUST_LOG").is_err() {
+        std::env::set_var("RUST_LOG", "debug");
+        log::debug!("RUST_LOG environment variable set to debug");
+    }
+    
+    match command {
+        Subcommand::Get { relay } => get_profile(data, relay).await,
+        Subcommand::Set {
+            name,
+            display_name,
+            picture,
+            about,
+            website,
+            nip05,
+            relay,
+        } => {
+            set_profile(
+                data,
+                name,
+                display_name,
+                picture,
+                about,
+                website,
+                nip05,
+                relay,
+            )
+            .await
+        }
+    }
+}
+
+/// Get Nostr profile from a relay
+async fn get_profile(data: Data, relay_url: Option<String>) -> Result<(), GcliError> {
+    // Get keypair from vault using configured address
+    let keypair = fetch_or_get_keypair(&data, data.cfg.address.clone()).await?;
+    
+    // Get Nostr pubkey in hex format (used for protocol)
+    let pubkey = get_nostr_pubkey(&keypair)?;
+    
+    // Get Nostr pubkey in bech32 format (for display)
+    let npub = get_nostr_npub(&keypair)?;
+    
+    println!("Searching for profile with pubkey: {}", pubkey);
+    
+    // Use default relay if none provided
+    let relay = relay_url.unwrap_or_else(|| {
+        data.cfg
+            .relay
+            .clone()
+            .unwrap_or_else(|| "wss://relay.copylaradio.com".to_string())
+    });
+    
+    // Ensure the relay URL starts with ws:// or wss://
+    let relay = if !relay.starts_with("ws://") && !relay.starts_with("wss://") {
+        format!("wss://{}", relay)
+    } else {
+        relay
+    };
+    
+    // Parse the URL
+    let url = Url::parse(&relay).map_err(|e| anyhow!("Invalid relay URL: {}", e))?;
+    
+    // Connect to the WebSocket
+    println!("Connecting to relay {}...", relay);
+    let (mut ws_stream, _) = connect_async(url).await
+        .map_err(|e| anyhow!("Failed to connect to relay: {}", e))?;
+    
+    println!("Connected to relay, requesting profile...");
+    
+    // Create subscription request - CORRECTED FORMAT
+    // According to NIP-01, REQ should be an array: ["REQ", <subscription_id>, <filter>, ...]
+    let filter = json!({
+        "kinds": [0],
+        "authors": [pubkey],
+        "limit": 1
+    });
+    
+    // Send the request in the correct format
+    let req_msg = json!(["REQ", "profile-request", filter]);
+    
+    println!("Sending request: {}", req_msg.to_string());
+    ws_stream.send(Message::Text(req_msg.to_string())).await
+        .map_err(|e| anyhow!("Failed to send request: {}", e))?;
+    
+    // Wait for response with timeout
+    let mut profile_found = false;
+    let mut profile = NostrProfile::default();
+    let mut all_messages: Vec<String> = Vec::new(); // Store all received messages for debugging
+    
+    // Set a timeout for receiving messages
+    let timeout = tokio::time::Duration::from_secs(10); // Increased timeout
+    let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(100));
+    
+    let start_time = tokio::time::Instant::now();
+    
+    while start_time.elapsed() < timeout {
+        interval.tick().await;
+        
+        // Check for messages with a timeout
+        match tokio::time::timeout(
+            tokio::time::Duration::from_millis(100),
+            ws_stream.next()
+        ).await {
+            Ok(Some(Ok(msg))) => {
+                if let Message::Text(text) = msg {
+                    all_messages.push(text.clone());
+                    
+                    // Parse the message
+                    if let Ok(json) = serde_json::from_str::<Value>(&text) {
+                        
+                        // Check if it's an EVENT message
+                        if let Some(event_type) = json.get(0).and_then(|v| v.as_str()) {
+                            
+                            if event_type == "EVENT" && json.get(1).is_some() && json.get(2).is_some() {
+                                
+                                if let Some(content) = json[2]["content"].as_str() {
+                                    
+                                    // Try to parse the profile
+                                    match serde_json::from_str::<NostrProfile>(content) {
+                                        Ok(parsed_profile) => {
+                                            profile = parsed_profile;
+                                            profile_found = true;
+                                            
+                                            // Close the subscription
+                                            let close_msg = json!(["CLOSE", "profile-request"]);
+                                            ws_stream.send(Message::Text(close_msg.to_string())).await
+                                                .map_err(|e| anyhow!("Failed to close subscription: {}", e))?;
+                                            
+                                            break;
+                                        },
+                                        Err(_) => {
+                                        }
+                                    }
+                                }
+                            } else if event_type == "EOSE" {
+                                // End of stored events
+                                if !profile_found {
+                                    // No profile found, close the connection
+                                    let close_msg = json!(["CLOSE", "profile-request"]);
+                                    ws_stream.send(Message::Text(close_msg.to_string())).await
+                                        .map_err(|e| anyhow!("Failed to close subscription: {}", e))?;
+                                    
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            Ok(Some(Err(_e))) => {
+                break;
+            },
+            Ok(None) => {
+                // Connection closed
+                break;
+            },
+            Err(_) => {
+                // Timeout, continue
+                continue;
+            }
+        }
+    }
+    
+    // Close the WebSocket connection
+    ws_stream.close(None).await.ok();
+    
+    // Display the profile or a message if not found
+    if profile_found {
+        match data.args.output_format {
+            OutputFormat::Human => {
+                println!("Profile for npub: {}", npub);
+                if let Some(name) = &profile.name {
+                    println!("Name: {}", name);
+                }
+                if let Some(display_name) = &profile.display_name {
+                    println!("Display Name: {}", display_name);
+                }
+                if let Some(picture) = &profile.picture {
+                    println!("Picture: {}", picture);
+                }
+                if let Some(about) = &profile.about {
+                    println!("About: {}", about);
+                }
+                if let Some(website) = &profile.website {
+                    println!("Website: {}", website);
+                }
+                if let Some(nip05) = &profile.nip05 {
+                    println!("NIP-05: {}", nip05);
+                }
+                for (key, value) in &profile.additional_fields {
+                    println!("{}: {}", key, value);
+                }
+            }
+            OutputFormat::Json => {
+                let output = json!({
+                    "pubkey_hex": pubkey,
+                    "pubkey_bech32": npub,
+                    "profile": profile
+                });
+                println!("{}", serde_json::to_string_pretty(&output).unwrap());
+            }
+        }
+    } else {
+        match data.args.output_format {
+            OutputFormat::Human => {
+                println!("No profile found for npub: {}", npub);
+            }
+            OutputFormat::Json => {
+                let output = json!({
+                    "pubkey_hex": pubkey,
+                    "pubkey_bech32": npub,
+                    "profile": NostrProfile::default()
+                });
+                println!("{}", serde_json::to_string_pretty(&output).unwrap());
+            }
+        }
+    }
+    
+    Ok(())
+}
+
+/// Set Nostr profile and publish to a relay
+async fn set_profile(
+    data: Data,
+    name: Option<String>,
+    display_name: Option<String>,
+    picture: Option<String>,
+    about: Option<String>,
+    website: Option<String>,
+    nip05: Option<String>,
+    relay_url: Option<String>,
+) -> Result<(), GcliError> {
+        // Check if no options were provided, and if so, display help
+    if name.is_none() && display_name.is_none() && picture.is_none() && 
+        about.is_none() && website.is_none() && nip05.is_none() {
+         println!("No profile options specified. Available options:");
+         println!("  -n, --name <NAME>                 Set profile name");
+         println!("  -d, --display-name <DISPLAY_NAME> Set profile display name");
+         println!("  -p, --picture <PICTURE>           Set profile picture URL");
+         println!("  -a, --about <ABOUT>               Set profile about/description");
+         println!("  -w, --website <WEBSITE>           Set profile website URL");
+         println!("  -i, --nip05 <NIP05>               Set profile NIP-05 identifier");
+         println!("  -r, --relay <RELAY>               Specify relay URL to publish to");
+         println!("\nExample: gcli profile set --name \"Alice\" --about \"Nostr user\"");
+         return Ok(());
+    }
+
+    // Get keypair from vault using configured address
+    let keypair = fetch_or_get_keypair(&data, data.cfg.address.clone()).await?;
+    
+    // Get Nostr pubkey in hex format (used for protocol)
+    let pubkey = get_nostr_pubkey(&keypair)?;
+    
+    // Get Nostr pubkey in bech32 format (for display)
+    let npub = get_nostr_npub(&keypair)?;
+    
+    // Get Nostr private key in bech32 format (for display)
+    let nsec = get_nostr_nsec(&keypair)?;
+    
+    // Create profile data
+    let mut profile = NostrProfile::default();
+    
+    if let Some(name) = name {
+        profile.name = Some(name);
+    }
+    if let Some(display_name) = display_name {
+        profile.display_name = Some(display_name);
+    }
+    if let Some(picture) = picture {
+        profile.picture = Some(picture);
+    }
+    if let Some(about) = about {
+        profile.about = Some(about);
+    }
+    if let Some(website) = website {
+        profile.website = Some(website);
+    }
+    if let Some(nip05) = nip05 {
+        profile.nip05 = Some(nip05);
+    }
+    
+    // Serialize profile to JSON
+    let profile_json = serde_json::to_string(&profile)
+        .map_err(|e| anyhow!("Failed to serialize profile: {}", e))?;
+    
+    // Create and sign Nostr event
+    let mut event = NostrEvent::new(pubkey.clone(), 0, profile_json);
+    
+    // Make sure tags is initialized as an empty array, not null
+    event.tags = Vec::new();
+    
+    log::debug!("Created event with pubkey: {}", event.pubkey);
+    log::debug!("Event content: {}", event.content);
+    
+    // Calculate ID and sign
+    event.calculate_id()?;
+    log::debug!("Calculated event ID: {}", event.id);
+    
+    event.sign(&keypair)?;
+    log::debug!("Signed event with signature: {}", event.sig);
+    
+    // Log the complete event for debugging
+    log::debug!("Event to publish: {}", serde_json::to_string_pretty(&event).unwrap());
+    
+    // Verify the event signature
+    match verify_nostr_event(&event) {
+        Ok(true) => log::debug!("Event signature verified successfully"),
+        Ok(false) => {
+            log::error!("Event signature verification failed - relay will likely reject this event");
+            return Err(anyhow!("Event signature verification failed - cannot proceed").into());
+        },
+        Err(e) => log::warn!("Error verifying event signature: {}", e),
+    }
+    
+    // Use default relay if none provided
+    let relay = relay_url.unwrap_or_else(|| {
+        data.cfg
+            .relay
+            .clone()
+            .unwrap_or_else(|| "wss://relay.copylaradio.com".to_string())
+    });
+    
+    // Ensure the relay URL starts with ws:// or wss://
+    let relay = if !relay.starts_with("ws://") && !relay.starts_with("wss://") {
+        format!("wss://{}", relay)
+    } else {
+        relay
+    };
+    
+    log::debug!("Using relay URL: {}", relay);
+    
+    // Parse the URL
+    let url = Url::parse(&relay).map_err(|e| anyhow!("Invalid relay URL: {}", e))?;
+    
+    // Connect to the WebSocket
+    println!("Connecting to relay {}...", relay);
+    let (mut ws_stream, _) = connect_async(url).await
+        .map_err(|e| anyhow!("Failed to connect to relay: {}", e))?;
+    
+    println!("Connected to relay, publishing profile...");
+    
+    // Create event message - CORRECTED FORMAT
+    // According to NIP-01, EVENT should be an array: ["EVENT", <event_object>]
+    let event_msg = json!([
+        "EVENT",
+        event
+    ]);
+    
+    let event_msg_str = event_msg.to_string();
+    log::debug!("Sending message to relay: {}", event_msg_str);
+    
+    // Send the event
+    ws_stream.send(Message::Text(event_msg_str)).await
+        .map_err(|e| anyhow!("Failed to send event: {}", e))?;
+    
+    // Wait for OK message with timeout
+    let mut success = false;
+    let mut response_message = String::new();
+    
+    // Set a timeout for receiving messages
+    let timeout = tokio::time::Duration::from_secs(10); // Increased timeout
+    let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(100));
+    
+    log::debug!("Waiting for relay response with timeout of {} seconds", timeout.as_secs());
+    
+    let start_time = tokio::time::Instant::now();
+    
+    while start_time.elapsed() < timeout {
+        interval.tick().await;
+        
+        // Check for messages with a timeout
+        match tokio::time::timeout(
+            tokio::time::Duration::from_millis(100),
+            ws_stream.next()
+        ).await {
+            Ok(Some(Ok(msg))) => {
+                if let Message::Text(text) = msg {
+                    log::debug!("Received message from relay: {}", text);
+                    response_message = text.clone();
+                    
+                    // Parse the message
+                    if let Ok(json) = serde_json::from_str::<Value>(&text) {
+                        log::debug!("Parsed JSON response: {}", json);
+                        
+                        // Check if it's an OK message
+                        if let Some(msg_type) = json.get(0).and_then(|v| v.as_str()) {
+                            log::debug!("Message type: {}", msg_type);
+                            
+                            if msg_type == "OK" && json.get(1).is_some() && json.get(2).is_some() {
+                                let success_status = json[2].as_bool().unwrap_or(false);
+                                log::debug!("OK status: {}", success_status);
+                                
+                                if success_status {
+                                    success = true;
+                                    log::debug!("Event accepted by relay");
+                                    break;
+                                } else {
+                                    // Error message
+                                    if let Some(error_msg) = json.get(3).and_then(|v| v.as_str()) {
+                                        log::error!("Relay rejected event: {}", error_msg);
+                                        return Err(anyhow!("Relay rejected event: {}", error_msg).into());
+                                    } else {
+                                        log::error!("Relay rejected event without specific error message");
+                                    }
+                                    break;
+                                }
+                            } else if msg_type == "NOTICE" && json.get(1).and_then(|v| v.as_str()).is_some() {
+                                log::debug!("Received NOTICE: {}", json[1].as_str().unwrap());
+                            }
+                        }
+                    } else {
+                        log::warn!("Failed to parse relay response as JSON: {}", text);
+                    }
+                } else {
+                    log::debug!("Received non-text message from relay");
+                }
+            },
+            Ok(Some(Err(e))) => {
+                log::error!("WebSocket error: {}", e);
+                break;
+            },
+            Ok(None) => {
+                log::debug!("Connection closed by relay");
+                break;
+            },
+            Err(_) => {
+                // Timeout, continue
+                continue;
+            }
+        }
+    }
+    
+    if start_time.elapsed() >= timeout {
+        log::warn!("Timeout waiting for relay response");
+    }
+    
+    // Close the WebSocket connection
+    log::debug!("Closing WebSocket connection");
+    ws_stream.close(None).await.ok();
+    
+    match data.args.output_format {
+        OutputFormat::Human => {
+            println!("Profile data published to npub: {}", npub);
+            
+            if let Some(name) = &profile.name {
+                println!("Name: {}", name);
+            }
+            if let Some(display_name) = &profile.display_name {
+                println!("Display Name: {}", display_name);
+            }
+            if let Some(picture) = &profile.picture {
+                println!("Picture: {}", picture);
+            }
+            if let Some(about) = &profile.about {
+                println!("About: {}", about);
+            }
+            if let Some(website) = &profile.website {
+                println!("Website: {}", website);
+            }
+            if let Some(nip05) = &profile.nip05 {
+                println!("NIP-05: {}", nip05);
+            }
+            
+            println!("\nPublished to relay: {}", relay);
+            if success {
+                println!("Status: Success");
+            } else {
+                println!("Status: No confirmation received from relay");
+                println!("Last response: {}", response_message);
+            }
+        }
+        OutputFormat::Json => {
+            println!("{}", serde_json::to_string_pretty(&json!({
+                "pubkey_hex": pubkey,
+                "pubkey_bech32": npub,
+                "seckey_bech32": nsec,
+                "profile": profile,
+                "event": event,
+                "relay": relay,
+                "success": success,
+                "response": response_message
+            })).unwrap());
+        }
+    }
+    
+    Ok(())
+} 
\ No newline at end of file
diff --git a/src/commands/vault.rs b/src/commands/vault.rs
index 07b40fc..dac5146 100644
--- a/src/commands/vault.rs
+++ b/src/commands/vault.rs
@@ -206,7 +206,7 @@ pub fn decrypt(input: &[u8], passphrase: String) -> Result<Vec<u8>, age::Decrypt
 pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
 	let db = data.connect_db();
 
-	// match subcommand
+	#[allow(deprecated)]
 	match command {
 		Subcommand::List(choice) => match choice {
 			ListChoice::All { show_g1v1, show_type } => {
diff --git a/src/conf.rs b/src/conf.rs
index 509e0c5..8809c39 100644
--- a/src/conf.rs
+++ b/src/conf.rs
@@ -15,6 +15,8 @@ pub struct Config {
 	/// user address
 	/// to perform actions, user must provide secret
 	pub address: Option<AccountId>,
+	/// nostr relay endpoint
+	pub relay: Option<String>,
 }
 
 impl std::default::Default for Config {
@@ -23,6 +25,7 @@ impl std::default::Default for Config {
 			duniter_endpoint: String::from(data::LOCAL_DUNITER_ENDPOINT),
 			indexer_endpoint: String::from(data::LOCAL_INDEXER_ENDPOINT),
 			address: None,
+			relay: Some(String::from("wss://relay.copylaradio.com")),
 		}
 	}
 }
@@ -34,10 +37,16 @@ impl std::fmt::Display for Config {
 		} else {
 			"(no address)".to_string()
 		};
+		let relay = if let Some(relay) = &self.relay {
+			relay.clone()
+		} else {
+			"(no relay)".to_string()
+		};
 		writeln!(f, "Äžcli config")?;
 		writeln!(f, "duniter endpoint {}", self.duniter_endpoint)?;
 		writeln!(f, "indexer endpoint {}", self.indexer_endpoint)?;
-		write!(f, "address {address}")
+		writeln!(f, "address {address}")?;
+		write!(f, "nostr relay {relay}")
 	}
 }
 
diff --git a/src/main.rs b/src/main.rs
index 116552d..3f8b9fc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -149,6 +149,9 @@ pub enum Subcommand {
 	/// Key management (import, generate, list...)
 	#[clap(subcommand)]
 	Vault(commands::vault::Subcommand),
+	/// Nostr profile management (get, set...)
+	#[clap(subcommand)]
+	Profile(commands::profile::Subcommand),
 	/// Cesium
 	#[clap(subcommand, hide = true)]
 	Cesium(commands::cesium::Subcommand),
@@ -190,6 +193,7 @@ async fn main() -> Result<(), GcliError> {
 		Subcommand::Indexer(subcommand) => indexer::handle_command(data, subcommand).await,
 		Subcommand::Config(subcommand) => conf::handle_command(data, subcommand).await,
 		Subcommand::Vault(subcommand) => commands::vault::handle_command(data, subcommand).await,
+		Subcommand::Profile(subcommand) => commands::profile::handle_command(data, subcommand).await,
 		Subcommand::Cesium(subcommand) => commands::cesium::handle_command(data, subcommand).await,
 		Subcommand::Publish => commands::publish::handle_command().await,
 	};
-- 
GitLab