diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 60f533a3c8..bbf888660f 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1002,6 +1002,7 @@ Add a new identity (keypair, ledger, OS specific secure store) - `--global` — ⚠️ Deprecated: global config is always on - `--config-dir ` — Location of config directory. By default, it uses `$XDG_CONFIG_HOME/stellar` if set, falling back to `~/.config/stellar` otherwise. Contains configuration files, aliases, and other persistent settings - `--public-key ` — Add a public key, ed25519, or muxed account, e.g. G1.., M2.. +- `--overwrite` — Overwrite existing identity if it already exists ## `stellar keys public-key` diff --git a/cmd/crates/soroban-test/tests/it/integration/keys.rs b/cmd/crates/soroban-test/tests/it/integration/keys.rs index b89953f2cd..e9a9742b8a 100644 --- a/cmd/crates/soroban-test/tests/it/integration/keys.rs +++ b/cmd/crates/soroban-test/tests/it/integration/keys.rs @@ -90,3 +90,56 @@ async fn overwrite_identity() { assert_ne!(initial_pubkey, pubkey_for_identity(sandbox, "test2")); } + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn overwrite_identity_with_add() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test3") + .assert() + .success(); + + let initial_pubkey = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("test3") + .assert() + .stdout_as_str(); + + // Try to add a key with the same name, should fail + sandbox + .new_assert_cmd("keys") + .arg("add") + .arg("test3") + .arg("--public-key") + .arg("GAKSH6AD2IPJQELTHIOWDAPYX74YELUOWJLI2L4RIPIPZH6YQIFNUSDC") + .assert() + .stderr(predicate::str::contains( + "error: An identity with the name 'test3' already exists", + )); + + // Verify the key wasn't changed + assert_eq!(initial_pubkey, pubkey_for_identity(sandbox, "test3")); + + // Try again with --overwrite flag, should succeed + sandbox + .new_assert_cmd("keys") + .arg("add") + .arg("test3") + .arg("--public-key") + .arg("GAKSH6AD2IPJQELTHIOWDAPYX74YELUOWJLI2L4RIPIPZH6YQIFNUSDC") + .arg("--overwrite") + .assert() + .stderr(predicate::str::contains("Overwriting identity 'test3'")) + .success(); + + // Verify the key was changed + assert_ne!(initial_pubkey, pubkey_for_identity(sandbox, "test3")); + assert_eq!( + "GAKSH6AD2IPJQELTHIOWDAPYX74YELUOWJLI2L4RIPIPZH6YQIFNUSDC", + pubkey_for_identity(sandbox, "test3").trim() + ); +} diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index 59e2983134..d8ca60f795 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -31,6 +31,9 @@ pub enum Error { #[error("secret input error")] PasswordRead, + + #[error("An identity with the name '{0}' already exists")] + IdentityAlreadyExists(String), } #[derive(Debug, clap::Parser, Clone)] @@ -48,11 +51,24 @@ pub struct Cmd { /// Add a public key, ed25519, or muxed account, e.g. G1.., M2.. #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")] pub public_key: Option, + + /// Overwrite existing identity if it already exists. + #[arg(long)] + pub overwrite: bool, } impl Cmd { pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { let print = Print::new(global_args.quiet); + + if self.config_locator.read_identity(&self.name).is_ok() { + if !self.overwrite { + return Err(Error::IdentityAlreadyExists(self.name.to_string())); + } + + print.exclaimln(format!("Overwriting identity '{}'", &self.name.to_string())); + } + let key = if let Some(key) = self.public_key.as_ref() { key.parse()? } else {