Skip to content

Commit

Permalink
feat(anvil/cast): mnemonic generation (#6066)
Browse files Browse the repository at this point in the history
* feat(anvil): add random mnemonic generation and generation from seed to Anvil

* Cast: added a new 'wallet' option to generate a mnemonic and associated accounts

* fix clippy

* fix fmt

* update anvil option to 'unsafe'

* chore: remove unwraps

---------

Co-authored-by: Enrique Ortiz <hi@enriqueortiz.dev>
  • Loading branch information
teddav and Evalir authored Nov 3, 2023
1 parent b85ff16 commit 691e1bd
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/anvil/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ thiserror = "1"
yansi = "0.5"
tempfile = "3"
itertools.workspace = true
rand = "0.8"

# cli
clap = { version = "4", features = ["derive", "env", "wrap_help"], optional = true }
Expand Down
35 changes: 33 additions & 2 deletions crates/anvil/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ use crate::{
use anvil_server::ServerConfig;
use clap::Parser;
use core::fmt;
use ethers::utils::WEI_IN_ETHER;
use ethers::{
signers::coins_bip39::{English, Mnemonic},
utils::WEI_IN_ETHER,
};
use foundry_config::{Chain, Config};
use futures::FutureExt;
use rand::{rngs::StdRng, SeedableRng};
use std::{
future::Future,
net::IpAddr,
Expand Down Expand Up @@ -45,9 +49,25 @@ pub struct NodeArgs {
pub timestamp: Option<u64>,

/// BIP39 mnemonic phrase used for generating accounts.
#[clap(long, short)]
/// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used
#[clap(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])]
pub mnemonic: Option<String>,

/// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it.
/// Cannot be used with other `mnemonic` options
/// You can specify the number of words you want in the mnemonic.
/// [default: 12]
#[clap(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))]
pub mnemonic_random: Option<usize>,

/// Generates a BIP39 mnemonic phrase from a given seed
/// Cannot be used with other `mnemonic` options
///
/// CAREFUL: this is NOT SAFE and should only be used for testing.
/// Never use the private keys generated in production.
#[clap(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])]
pub mnemonic_seed: Option<u64>,

/// Sets the derivation path of the child key to be derived.
///
/// [default: m/44'/60'/0'/0/]
Expand Down Expand Up @@ -219,6 +239,17 @@ impl NodeArgs {
.chain_id(self.evm_opts.chain_id.unwrap_or_else(|| CHAIN_ID.into()));
if let Some(ref mnemonic) = self.mnemonic {
gen = gen.phrase(mnemonic);
} else if let Some(count) = self.mnemonic_random {
let mut rng = rand::thread_rng();
let mnemonic = match Mnemonic::<English>::new_with_count(&mut rng, count) {
Ok(mnemonic) => mnemonic.to_phrase(),
Err(_) => DEFAULT_MNEMONIC.to_string(),
};
gen = gen.phrase(mnemonic);
} else if let Some(seed) = self.mnemonic_seed {
let mut seed = StdRng::seed_from_u64(seed);
let mnemonic = Mnemonic::<English>::new(&mut seed).to_phrase();
gen = gen.phrase(mnemonic);
}
if let Some(ref derivation) = self.derivation_path {
gen = gen.derivation_path(derivation);
Expand Down
39 changes: 38 additions & 1 deletion crates/cast/bin/cmd/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ use alloy_primitives::Address;
use clap::Parser;
use ethers::{
core::rand::thread_rng,
signers::{LocalWallet, Signer},
signers::{
coins_bip39::{English, Mnemonic},
LocalWallet, MnemonicBuilder, Signer,
},
types::{transaction::eip712::TypedData, Signature},
};
use ethers_core::utils::to_checksum;
use eyre::{Context, Result};
use foundry_cli::opts::{RawWallet, Wallet};
use foundry_common::fs;
Expand Down Expand Up @@ -47,6 +51,18 @@ pub enum WalletSubcommands {
json: bool,
},

/// Generates a random BIP39 mnemonic phrase
#[clap(visible_alias = "nm")]
NewMnemonic {
/// Number of words for the mnemonic
#[clap(long, short, default_value = "12")]
words: usize,

/// Number of accounts to display
#[clap(long, short, default_value = "1")]
accounts: u8,
},

/// Generate a vanity address.
#[clap(visible_alias = "va")]
Vanity(VanityArgs),
Expand Down Expand Up @@ -193,6 +209,27 @@ impl WalletSubcommands {
}
}
}
WalletSubcommands::NewMnemonic { words, accounts } => {
let mut rng = thread_rng();
let phrase = Mnemonic::<English>::new_with_count(&mut rng, words)?.to_phrase();

let builder = MnemonicBuilder::<English>::default().phrase(phrase.as_str());
let derivation_path = "m/44'/60'/0'/0/";
let wallets = (0..accounts)
.map(|i| builder.clone().derivation_path(&format!("{derivation_path}{i}")))
.collect::<Result<Vec<_>, _>>()?;
let wallets =
wallets.into_iter().map(|b| b.build()).collect::<Result<Vec<_>, _>>()?;

println!("{}", Paint::green("Successfully generated a new mnemonic."));
println!("Phrase:\n{phrase}");
println!("\nAccounts:");
for (i, wallet) in wallets.iter().enumerate() {
println!("- Account {i}:");
println!("Address: {}", to_checksum(&wallet.address(), None));
println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes()));
}
}
WalletSubcommands::Vanity(cmd) => {
cmd.run()?;
}
Expand Down

0 comments on commit 691e1bd

Please sign in to comment.