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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion crates/cast/src/cmd/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ pub enum WalletSubcommands {
wallet: WalletOpts,
},

/// Derive accounts from a mnemonic
#[command(visible_alias = "d")]
Derive {
/// The accounts will be derived from the specified mnemonic phrase.
#[arg(value_name = "MNEMONIC")]
mnemonic: String,

/// Number of accounts to display.
#[arg(long, short, default_value = "1")]
accounts: Option<u8>,

/// Insecure mode: display private keys in the terminal.
#[arg(long, default_value = "false")]
insecure: bool,
},

/// Sign a message or typed data.
#[command(visible_alias = "s")]
Sign {
Expand Down Expand Up @@ -228,7 +244,7 @@ pub enum WalletSubcommands {
/// Derives private key from mnemonic
#[command(name = "private-key", visible_alias = "pk", aliases = &["derive-private-key", "--derive-private-key"])]
PrivateKey {
/// If provided, the private key will be derived from the specified menomonic phrase.
/// If provided, the private key will be derived from the specified mnemonic phrase.
#[arg(value_name = "MNEMONIC")]
mnemonic_override: Option<String>,

Expand Down Expand Up @@ -491,6 +507,54 @@ impl WalletSubcommands {
let addr = wallet.address();
sh_println!("{}", addr.to_checksum(None))?;
}
Self::Derive { mnemonic, accounts, insecure } => {
let format_json = shell::is_json();
let mut accounts_json = json!([]);
for i in 0..accounts.unwrap_or(1) {
let wallet = WalletOpts {
raw: RawWalletOpts {
mnemonic: Some(mnemonic.clone()),
mnemonic_index: i as u32,
..Default::default()
},
..Default::default()
}
.signer()
.await?;

match wallet {
WalletSigner::Local(local_wallet) => {
let address = local_wallet.address().to_checksum(None);
let private_key = hex::encode(local_wallet.credential().to_bytes());
if format_json {
if insecure {
accounts_json.as_array_mut().unwrap().push(json!({
"address": format!("{}", address),
"private_key": format!("0x{}", private_key),
}));
} else {
accounts_json.as_array_mut().unwrap().push(json!({
"address": format!("{}", address)
}));
}
} else {
sh_println!("- Account {i}:")?;
if insecure {
sh_println!("Address: {}", address)?;
sh_println!("Private key: 0x{}\n", private_key)?;
} else {
sh_println!("Address: {}\n", address)?;
}
}
}
_ => eyre::bail!("Only local wallets are supported by this command"),
}
}

if format_json {
sh_println!("{}", serde_json::to_string_pretty(&accounts_json)?)?;
}
}
Self::PublicKey { wallet, private_key_override } => {
let wallet = private_key_override
.map(|pk| WalletOpts {
Expand Down
113 changes: 113 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,119 @@ casttest!(wallet_mnemonic_from_entropy_json_verbose, |_prj, cmd| {
"#]]);
});

// tests that `cast wallet derive` outputs the addresses of the accounts derived from the mnemonic
casttest!(wallet_derive_mnemonic, |_prj, cmd| {
cmd.args([
"wallet",
"derive",
"--accounts",
"3",
"test test test test test test test test test test test junk",
])
.assert_success()
.stdout_eq(str![[r#"
- Account 0:
[ADDRESS]

- Account 1:
[ADDRESS]

- Account 2:
[ADDRESS]


"#]]);
});

// tests that `cast wallet derive` with insecure flag outputs the addresses and private keys of the
// accounts derived from the mnemonic
casttest!(wallet_derive_mnemonic_insecure, |_prj, cmd| {
cmd.args([
"wallet",
"derive",
"--accounts",
"3",
"--insecure",
"test test test test test test test test test test test junk",
])
.assert_success()
.stdout_eq(str![[r#"
- Account 0:
[ADDRESS]
[PRIVATE_KEY]

- Account 1:
[ADDRESS]
[PRIVATE_KEY]

- Account 2:
[ADDRESS]
[PRIVATE_KEY]


"#]]);
});

// tests that `cast wallet derive` with json flag outputs the addresses of the accounts derived from
// the mnemonic in JSON format
casttest!(wallet_derive_mnemonic_json, |_prj, cmd| {
cmd.args([
"wallet",
"derive",
"--accounts",
"3",
"--json",
"test test test test test test test test test test test junk",
])
.assert_success()
.stdout_eq(str![[r#"
[
{
"address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
},
{
"address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
},
{
"address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"
}
]

"#]]);
});

// tests that `cast wallet derive` with insecure and json flag outputs the addresses and private
// keys of the accounts derived from the mnemonic in JSON format
casttest!(wallet_derive_mnemonic_insecure_json, |_prj, cmd| {
cmd.args([
"wallet",
"derive",
"--accounts",
"3",
"--insecure",
"--json",
"test test test test test test test test test test test junk",
])
.assert_success()
.stdout_eq(str![[r#"
[
{
"address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
},
{
"address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
},
{
"address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
"private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
}
]

"#]]);
});

// tests that `cast wallet private-key` with arguments outputs the private key
casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| {
cmd.args([
Expand Down
Loading