1use alloy_chains::Chain;
2use alloy_dyn_abi::TypedData;
3use alloy_primitives::{Address, B256, Signature, U256, hex};
4use alloy_provider::Provider;
5use alloy_rpc_types::Authorization;
6use alloy_signer::Signer;
7use alloy_signer_local::{
8 MnemonicBuilder, PrivateKeySigner,
9 coins_bip39::{English, Entropy, Mnemonic},
10};
11use clap::Parser;
12use eyre::{Context, Result};
13use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig};
14use foundry_common::{fs, sh_println, shell};
15use foundry_config::Config;
16use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner};
17use rand_08::thread_rng;
18use serde_json::json;
19use std::path::Path;
20use yansi::Paint;
21
22pub mod vanity;
23use vanity::VanityArgs;
24
25pub mod list;
26use list::ListArgs;
27
28#[derive(Debug, Parser)]
30pub enum WalletSubcommands {
31 #[command(visible_alias = "n")]
33 New {
34 path: Option<String>,
36
37 #[arg(value_name = "ACCOUNT_NAME")]
40 account_name: Option<String>,
41
42 #[arg(long, short, requires = "path", conflicts_with = "unsafe_password")]
46 password: bool,
47
48 #[arg(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")]
52 unsafe_password: Option<String>,
53
54 #[arg(long, short, default_value = "1")]
56 number: u32,
57 },
58
59 #[command(visible_alias = "nm")]
61 NewMnemonic {
62 #[arg(long, short, default_value = "12")]
64 words: usize,
65
66 #[arg(long, short, default_value = "1")]
68 accounts: u8,
69
70 #[arg(long, short, conflicts_with = "words")]
72 entropy: Option<String>,
73 },
74
75 #[command(visible_alias = "va")]
77 Vanity(VanityArgs),
78
79 #[command(visible_aliases = &["a", "addr"])]
81 Address {
82 #[arg(value_name = "PRIVATE_KEY")]
84 private_key_override: Option<String>,
85
86 #[command(flatten)]
87 wallet: WalletOpts,
88 },
89
90 #[command(visible_alias = "s")]
92 Sign {
93 message: String,
107
108 #[arg(long)]
110 data: bool,
111
112 #[arg(long, requires = "data")]
114 from_file: bool,
115
116 #[arg(long, conflicts_with = "data")]
118 no_hash: bool,
119
120 #[command(flatten)]
121 wallet: WalletOpts,
122 },
123
124 #[command(visible_alias = "sa")]
126 SignAuth {
127 address: Address,
129
130 #[command(flatten)]
131 rpc: RpcOpts,
132
133 #[arg(long)]
134 nonce: Option<u64>,
135
136 #[arg(long)]
137 chain: Option<Chain>,
138
139 #[command(flatten)]
140 wallet: WalletOpts,
141 },
142
143 #[command(visible_alias = "v")]
145 Verify {
146 message: String,
151
152 signature: Signature,
154
155 #[arg(long, short)]
157 address: Address,
158 },
159
160 #[command(visible_alias = "i")]
162 Import {
163 #[arg(value_name = "ACCOUNT_NAME")]
165 account_name: String,
166 #[arg(long, short)]
169 keystore_dir: Option<String>,
170 #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")]
173 unsafe_password: Option<String>,
174 #[command(flatten)]
175 raw_wallet_options: RawWalletOpts,
176 },
177
178 #[command(visible_alias = "ls")]
180 List(ListArgs),
181
182 #[command(visible_aliases = &["rm"], override_usage = "cast wallet remove --name <NAME>")]
187 Remove {
188 #[arg(long, required = true)]
190 name: String,
191 #[arg(long)]
194 dir: Option<String>,
195 #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")]
198 unsafe_password: Option<String>,
199 },
200
201 #[command(name = "private-key", visible_alias = "pk", aliases = &["derive-private-key", "--derive-private-key"])]
203 PrivateKey {
204 #[arg(value_name = "MNEMONIC")]
206 mnemonic_override: Option<String>,
207
208 #[arg(value_name = "MNEMONIC_INDEX_OR_DERIVATION_PATH")]
211 mnemonic_index_or_derivation_path_override: Option<String>,
212
213 #[command(flatten)]
214 wallet: WalletOpts,
215 },
216 #[command(visible_aliases = &["pubkey"])]
218 PublicKey {
219 #[arg(long = "raw-private-key", value_name = "PRIVATE_KEY")]
221 private_key_override: Option<String>,
222
223 #[command(flatten)]
224 wallet: WalletOpts,
225 },
226 #[command(name = "decrypt-keystore", visible_alias = "dk")]
228 DecryptKeystore {
229 #[arg(value_name = "ACCOUNT_NAME")]
231 account_name: String,
232 #[arg(long, short)]
235 keystore_dir: Option<String>,
236 #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")]
239 unsafe_password: Option<String>,
240 },
241
242 #[command(name = "change-password", visible_alias = "cp")]
244 ChangePassword {
245 #[arg(value_name = "ACCOUNT_NAME")]
247 account_name: String,
248 #[arg(long, short)]
251 keystore_dir: Option<String>,
252 #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")]
255 unsafe_password: Option<String>,
256 #[arg(long, env = "CAST_UNSAFE_NEW_PASSWORD", value_name = "NEW_PASSWORD")]
259 unsafe_new_password: Option<String>,
260 },
261}
262
263impl WalletSubcommands {
264 pub async fn run(self) -> Result<()> {
265 match self {
266 Self::New { path, account_name, unsafe_password, number, .. } => {
267 let mut rng = thread_rng();
268
269 let mut json_values = if shell::is_json() { Some(vec![]) } else { None };
270 if let Some(path) = path {
271 let path = match dunce::canonicalize(path.clone()) {
272 Ok(path) => path,
273 Err(e) => {
276 eyre::bail!(
277 "If you specified a directory, please make sure it exists, or create it before running `cast wallet new <DIR>`.\n{path} is not a directory.\nError: {}",
278 e
279 );
280 }
281 };
282 if !path.is_dir() {
283 eyre::bail!("`{}` is not a directory", path.display());
285 }
286
287 let password = if let Some(password) = unsafe_password {
288 password
289 } else {
290 rpassword::prompt_password("Enter secret: ")?
292 };
293
294 for i in 0..number {
295 let account_name_ref = account_name.as_deref().map(|name| match number {
296 1 => name.to_string(),
297 _ => format!("{}_{}", name, i + 1),
298 });
299
300 let (wallet, uuid) = PrivateKeySigner::new_keystore(
301 &path,
302 &mut rng,
303 password.clone(),
304 account_name_ref.as_deref(),
305 )?;
306 let identifier = account_name_ref.as_deref().unwrap_or(&uuid);
307
308 if let Some(json) = json_values.as_mut() {
309 json.push(if shell::verbosity() > 0 {
310 json!({
311 "address": wallet.address().to_checksum(None),
312 "public_key": format!("0x{}", hex::encode(wallet.public_key())),
313 "path": format!("{}", path.join(identifier).display()),
314 })
315 } else {
316 json!({
317 "address": wallet.address().to_checksum(None),
318 "path": format!("{}", path.join(identifier).display()),
319 })
320 });
321 } else {
322 sh_println!(
323 "Created new encrypted keystore file: {}",
324 path.join(identifier).display()
325 )?;
326 sh_println!("Address: {}", wallet.address().to_checksum(None))?;
327 if shell::verbosity() > 0 {
328 sh_println!("Public key: 0x{}", hex::encode(wallet.public_key()))?;
329 }
330 }
331 }
332 } else {
333 for _ in 0..number {
334 let wallet = PrivateKeySigner::random_with(&mut rng);
335
336 if let Some(json) = json_values.as_mut() {
337 json.push(if shell::verbosity() > 0 {
338 json!({
339 "address": wallet.address().to_checksum(None),
340 "public_key": format!("0x{}", hex::encode(wallet.public_key())),
341 "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())),
342 })
343 } else {
344 json!({
345 "address": wallet.address().to_checksum(None),
346 "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())),
347 })
348 });
349 } else {
350 sh_println!("Successfully created new keypair.")?;
351 sh_println!("Address: {}", wallet.address().to_checksum(None))?;
352 if shell::verbosity() > 0 {
353 sh_println!("Public key: 0x{}", hex::encode(wallet.public_key()))?;
354 }
355 sh_println!(
356 "Private key: 0x{}",
357 hex::encode(wallet.credential().to_bytes())
358 )?;
359 }
360 }
361 }
362 if let Some(json) = json_values.as_ref() {
363 sh_println!("{}", serde_json::to_string_pretty(json)?)?;
364 }
365 }
366 Self::NewMnemonic { words, accounts, entropy } => {
367 let phrase = if let Some(entropy) = entropy {
368 let entropy = Entropy::from_slice(hex::decode(entropy)?)?;
369 Mnemonic::<English>::new_from_entropy(entropy).to_phrase()
370 } else {
371 let mut rng = thread_rng();
372 Mnemonic::<English>::new_with_count(&mut rng, words)?.to_phrase()
373 };
374
375 let format_json = shell::is_json();
376
377 if !format_json {
378 sh_println!("{}", "Generating mnemonic from provided entropy...".yellow())?;
379 }
380
381 let builder = MnemonicBuilder::<English>::default().phrase(phrase.as_str());
382 let derivation_path = "m/44'/60'/0'/0/";
383 let wallets = (0..accounts)
384 .map(|i| builder.clone().derivation_path(format!("{derivation_path}{i}")))
385 .collect::<Result<Vec<_>, _>>()?;
386 let wallets =
387 wallets.into_iter().map(|b| b.build()).collect::<Result<Vec<_>, _>>()?;
388
389 if !format_json {
390 sh_println!("{}", "Successfully generated a new mnemonic.".green())?;
391 sh_println!("Phrase:\n{phrase}")?;
392 sh_println!("\nAccounts:")?;
393 }
394
395 let mut accounts = json!([]);
396 for (i, wallet) in wallets.iter().enumerate() {
397 let public_key = hex::encode(wallet.public_key());
398 let private_key = hex::encode(wallet.credential().to_bytes());
399 if format_json {
400 accounts.as_array_mut().unwrap().push(if shell::verbosity() > 0 {
401 json!({
402 "address": format!("{}", wallet.address()),
403 "public_key": format!("0x{}", public_key),
404 "private_key": format!("0x{}", private_key),
405 })
406 } else {
407 json!({
408 "address": format!("{}", wallet.address()),
409 "private_key": format!("0x{}", private_key),
410 })
411 });
412 } else {
413 sh_println!("- Account {i}:")?;
414 sh_println!("Address: {}", wallet.address())?;
415 if shell::verbosity() > 0 {
416 sh_println!("Public key: 0x{}", public_key)?;
417 }
418 sh_println!("Private key: 0x{}\n", private_key)?;
419 }
420 }
421
422 if format_json {
423 let obj = json!({
424 "mnemonic": phrase,
425 "accounts": accounts,
426 });
427 sh_println!("{}", serde_json::to_string_pretty(&obj)?)?;
428 }
429 }
430 Self::Vanity(cmd) => {
431 cmd.run()?;
432 }
433 Self::Address { wallet, private_key_override } => {
434 let wallet = private_key_override
435 .map(|pk| WalletOpts {
436 raw: RawWalletOpts { private_key: Some(pk), ..Default::default() },
437 ..Default::default()
438 })
439 .unwrap_or(wallet)
440 .signer()
441 .await?;
442 let addr = wallet.address();
443 sh_println!("{}", addr.to_checksum(None))?;
444 }
445 Self::PublicKey { wallet, private_key_override } => {
446 let wallet = private_key_override
447 .map(|pk| WalletOpts {
448 raw: RawWalletOpts { private_key: Some(pk), ..Default::default() },
449 ..Default::default()
450 })
451 .unwrap_or(wallet)
452 .signer()
453 .await?;
454
455 let public_key = match wallet {
456 WalletSigner::Local(wallet) => wallet.public_key(),
457 _ => eyre::bail!("Only local wallets are supported by this command"),
458 };
459
460 sh_println!("0x{}", hex::encode(public_key))?;
461 }
462 Self::Sign { message, data, from_file, no_hash, wallet } => {
463 let wallet = wallet.signer().await?;
464 let sig = if data {
465 let typed_data: TypedData = if from_file {
466 foundry_common::fs::read_json_file(message.as_ref())?
468 } else {
469 serde_json::from_str(&message)?
471 };
472 wallet.sign_dynamic_typed_data(&typed_data).await?
473 } else if no_hash {
474 wallet.sign_hash(&hex::decode(&message)?[..].try_into()?).await?
475 } else {
476 wallet.sign_message(&Self::hex_str_to_bytes(&message)?).await?
477 };
478
479 if shell::verbosity() > 0 {
480 if shell::is_json() {
481 sh_println!(
482 "{}",
483 serde_json::to_string_pretty(&json!({
484 "message": message,
485 "address": wallet.address(),
486 "signature": hex::encode(sig.as_bytes()),
487 }))?
488 )?;
489 } else {
490 sh_println!(
491 "Successfully signed!\n Message: {}\n Address: {}\n Signature: 0x{}",
492 message,
493 wallet.address(),
494 hex::encode(sig.as_bytes()),
495 )?;
496 }
497 } else {
498 sh_println!("0x{}", hex::encode(sig.as_bytes()))?;
500 }
501 }
502 Self::SignAuth { rpc, nonce, chain, wallet, address } => {
503 let wallet = wallet.signer().await?;
504 let provider = utils::get_provider(&rpc.load_config()?)?;
505 let nonce = if let Some(nonce) = nonce {
506 nonce
507 } else {
508 provider.get_transaction_count(wallet.address()).await?
509 };
510 let chain_id = if let Some(chain) = chain {
511 chain.id()
512 } else {
513 provider.get_chain_id().await?
514 };
515 let auth = Authorization { chain_id: U256::from(chain_id), address, nonce };
516 let signature = wallet.sign_hash(&auth.signature_hash()).await?;
517 let auth = auth.into_signed(signature);
518
519 if shell::verbosity() > 0 {
520 if shell::is_json() {
521 sh_println!(
522 "{}",
523 serde_json::to_string_pretty(&json!({
524 "nonce": nonce,
525 "chain_id": chain_id,
526 "address": wallet.address(),
527 "signature": hex::encode_prefixed(alloy_rlp::encode(&auth)),
528 }))?
529 )?;
530 } else {
531 sh_println!(
532 "Successfully signed!\n Nonce: {}\n Chain ID: {}\n Address: {}\n Signature: 0x{}",
533 nonce,
534 chain_id,
535 wallet.address(),
536 hex::encode_prefixed(alloy_rlp::encode(&auth)),
537 )?;
538 }
539 } else {
540 sh_println!("{}", hex::encode_prefixed(alloy_rlp::encode(&auth)))?;
542 }
543 }
544 Self::Verify { message, signature, address } => {
545 let recovered_address = Self::recover_address_from_message(&message, &signature)?;
546 if address == recovered_address {
547 sh_println!("Validation succeeded. Address {address} signed this message.")?;
548 } else {
549 eyre::bail!("Validation failed. Address {address} did not sign this message.");
550 }
551 }
552 Self::Import { account_name, keystore_dir, unsafe_password, raw_wallet_options } => {
553 let dir = if let Some(path) = keystore_dir {
555 Path::new(&path).to_path_buf()
556 } else {
557 Config::foundry_keystores_dir().ok_or_else(|| {
558 eyre::eyre!("Could not find the default keystore directory.")
559 })?
560 };
561
562 fs::create_dir_all(&dir)?;
563
564 let keystore_path = Path::new(&dir).join(&account_name);
566 if keystore_path.exists() {
567 eyre::bail!("Keystore file already exists at {}", keystore_path.display());
568 }
569
570 let wallet = raw_wallet_options
572 .signer()?
573 .and_then(|s| match s {
574 WalletSigner::Local(s) => Some(s),
575 _ => None,
576 })
577 .ok_or_else(|| {
578 eyre::eyre!(
579 "\
580Did you set a private key or mnemonic?
581Run `cast wallet import --help` and use the corresponding CLI
582flag to set your key via:
583--private-key, --mnemonic-path or --interactive."
584 )
585 })?;
586
587 let private_key = wallet.credential().to_bytes();
588 let password = if let Some(password) = unsafe_password {
589 password
590 } else {
591 rpassword::prompt_password("Enter password: ")?
593 };
594
595 let mut rng = thread_rng();
596 let (wallet, _) = PrivateKeySigner::encrypt_keystore(
597 dir,
598 &mut rng,
599 private_key,
600 password,
601 Some(&account_name),
602 )?;
603 let address = wallet.address();
604 let success_message = format!(
605 "`{}` keystore was saved successfully. Address: {:?}",
606 &account_name, address,
607 );
608 sh_println!("{}", success_message.green())?;
609 }
610 Self::List(cmd) => {
611 cmd.run().await?;
612 }
613 Self::Remove { name, dir, unsafe_password } => {
614 let dir = if let Some(path) = dir {
615 Path::new(&path).to_path_buf()
616 } else {
617 Config::foundry_keystores_dir().ok_or_else(|| {
618 eyre::eyre!("Could not find the default keystore directory.")
619 })?
620 };
621
622 let keystore_path = Path::new(&dir).join(&name);
623 if !keystore_path.exists() {
624 eyre::bail!("Keystore file does not exist at {}", keystore_path.display());
625 }
626
627 let password = if let Some(pwd) = unsafe_password {
628 pwd
629 } else {
630 rpassword::prompt_password("Enter password: ")?
631 };
632
633 if PrivateKeySigner::decrypt_keystore(&keystore_path, password).is_err() {
634 eyre::bail!("Invalid password - wallet removal cancelled");
635 }
636
637 std::fs::remove_file(&keystore_path).wrap_err_with(|| {
638 format!("Failed to remove keystore file at {}", keystore_path.display())
639 })?;
640
641 let success_message = format!("`{}` keystore was removed successfully.", &name);
642 sh_println!("{}", success_message.green())?;
643 }
644 Self::PrivateKey {
645 wallet,
646 mnemonic_override,
647 mnemonic_index_or_derivation_path_override,
648 } => {
649 let (index_override, derivation_path_override) =
650 match mnemonic_index_or_derivation_path_override {
651 Some(value) => match value.parse::<u32>() {
652 Ok(index) => (Some(index), None),
653 Err(_) => (None, Some(value)),
654 },
655 None => (None, None),
656 };
657 let wallet = WalletOpts {
658 raw: RawWalletOpts {
659 mnemonic: mnemonic_override.or(wallet.raw.mnemonic),
660 mnemonic_index: index_override.unwrap_or(wallet.raw.mnemonic_index),
661 hd_path: derivation_path_override.or(wallet.raw.hd_path),
662 ..wallet.raw
663 },
664 ..wallet
665 }
666 .signer()
667 .await?;
668 match wallet {
669 WalletSigner::Local(wallet) => {
670 if shell::verbosity() > 0 {
671 sh_println!("Address: {}", wallet.address())?;
672 sh_println!(
673 "Private key: 0x{}",
674 hex::encode(wallet.credential().to_bytes())
675 )?;
676 } else {
677 sh_println!("0x{}", hex::encode(wallet.credential().to_bytes()))?;
678 }
679 }
680 _ => {
681 eyre::bail!("Only local wallets are supported by this command.");
682 }
683 }
684 }
685 Self::DecryptKeystore { account_name, keystore_dir, unsafe_password } => {
686 let dir = if let Some(path) = keystore_dir {
688 Path::new(&path).to_path_buf()
689 } else {
690 Config::foundry_keystores_dir().ok_or_else(|| {
691 eyre::eyre!("Could not find the default keystore directory.")
692 })?
693 };
694
695 let keypath = dir.join(&account_name);
696
697 if !keypath.exists() {
698 eyre::bail!("Keystore file does not exist at {}", keypath.display());
699 }
700
701 let password = if let Some(password) = unsafe_password {
702 password
703 } else {
704 rpassword::prompt_password("Enter password: ")?
706 };
707
708 let wallet = PrivateKeySigner::decrypt_keystore(keypath, password)?;
709
710 let private_key = B256::from_slice(&wallet.credential().to_bytes());
711
712 let success_message =
713 format!("{}'s private key is: {}", &account_name, private_key);
714
715 sh_println!("{}", success_message.green())?;
716 }
717 Self::ChangePassword {
718 account_name,
719 keystore_dir,
720 unsafe_password,
721 unsafe_new_password,
722 } => {
723 let dir = if let Some(path) = keystore_dir {
725 Path::new(&path).to_path_buf()
726 } else {
727 Config::foundry_keystores_dir().ok_or_else(|| {
728 eyre::eyre!("Could not find the default keystore directory.")
729 })?
730 };
731
732 let keypath = dir.join(&account_name);
733
734 if !keypath.exists() {
735 eyre::bail!("Keystore file does not exist at {}", keypath.display());
736 }
737
738 let current_password = if let Some(password) = unsafe_password {
739 password
740 } else {
741 rpassword::prompt_password("Enter current password: ")?
743 };
744
745 let wallet = PrivateKeySigner::decrypt_keystore(&keypath, current_password.clone())
747 .map_err(|_| eyre::eyre!("Invalid password - password change cancelled"))?;
748
749 let new_password = if let Some(password) = unsafe_new_password {
750 password
751 } else {
752 rpassword::prompt_password("Enter new password: ")?
754 };
755
756 if current_password == new_password {
757 eyre::bail!("New password cannot be the same as the current password");
758 }
759
760 let private_key = wallet.credential().to_bytes();
762 let mut rng = thread_rng();
763 let (wallet, _) = PrivateKeySigner::encrypt_keystore(
764 dir,
765 &mut rng,
766 private_key,
767 new_password,
768 Some(&account_name),
769 )?;
770
771 let success_message = format!(
772 "Password for keystore `{}` was changed successfully. Address: {:?}",
773 &account_name,
774 wallet.address(),
775 );
776 sh_println!("{}", success_message.green())?;
777 }
778 };
779
780 Ok(())
781 }
782
783 fn recover_address_from_message(message: &str, signature: &Signature) -> Result<Address> {
787 let message = Self::hex_str_to_bytes(message)?;
788 Ok(signature.recover_address_from_msg(message)?)
789 }
790
791 fn hex_str_to_bytes(s: &str) -> Result<Vec<u8>> {
795 Ok(match s.strip_prefix("0x") {
796 Some(data) => hex::decode(data).wrap_err("Could not decode 0x-prefixed string.")?,
797 None => s.as_bytes().to_vec(),
798 })
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805 use alloy_primitives::address;
806 use std::str::FromStr;
807
808 #[test]
809 fn can_parse_wallet_sign_message() {
810 let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "deadbeef"]);
811 match args {
812 WalletSubcommands::Sign { message, data, from_file, .. } => {
813 assert_eq!(message, "deadbeef".to_string());
814 assert!(!data);
815 assert!(!from_file);
816 }
817 _ => panic!("expected WalletSubcommands::Sign"),
818 }
819 }
820
821 #[test]
822 fn can_parse_wallet_sign_hex_message() {
823 let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "0xdeadbeef"]);
824 match args {
825 WalletSubcommands::Sign { message, data, from_file, .. } => {
826 assert_eq!(message, "0xdeadbeef".to_string());
827 assert!(!data);
828 assert!(!from_file);
829 }
830 _ => panic!("expected WalletSubcommands::Sign"),
831 }
832 }
833
834 #[test]
835 fn can_verify_signed_hex_message() {
836 let message = "hello";
837 let signature = Signature::from_str("f2dd00eac33840c04b6fc8a5ec8c4a47eff63575c2bc7312ecb269383de0c668045309c423484c8d097df306e690c653f8e1ec92f7f6f45d1f517027771c3e801c").unwrap();
838 let address = address!("0x28A4F420a619974a2393365BCe5a7b560078Cc13");
839 let recovered_address =
840 WalletSubcommands::recover_address_from_message(message, &signature);
841 assert!(recovered_address.is_ok());
842 assert_eq!(address, recovered_address.unwrap());
843 }
844
845 #[test]
846 fn can_parse_wallet_sign_data() {
847 let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "--data", "{ ... }"]);
848 match args {
849 WalletSubcommands::Sign { message, data, from_file, .. } => {
850 assert_eq!(message, "{ ... }".to_string());
851 assert!(data);
852 assert!(!from_file);
853 }
854 _ => panic!("expected WalletSubcommands::Sign"),
855 }
856 }
857
858 #[test]
859 fn can_parse_wallet_sign_data_file() {
860 let args = WalletSubcommands::parse_from([
861 "foundry-cli",
862 "sign",
863 "--data",
864 "--from-file",
865 "tests/data/typed_data.json",
866 ]);
867 match args {
868 WalletSubcommands::Sign { message, data, from_file, .. } => {
869 assert_eq!(message, "tests/data/typed_data.json".to_string());
870 assert!(data);
871 assert!(from_file);
872 }
873 _ => panic!("expected WalletSubcommands::Sign"),
874 }
875 }
876
877 #[test]
878 fn can_parse_wallet_change_password() {
879 let args = WalletSubcommands::parse_from([
880 "foundry-cli",
881 "change-password",
882 "my_account",
883 "--unsafe-password",
884 "old_password",
885 "--unsafe-new-password",
886 "new_password",
887 ]);
888 match args {
889 WalletSubcommands::ChangePassword {
890 account_name,
891 keystore_dir,
892 unsafe_password,
893 unsafe_new_password,
894 } => {
895 assert_eq!(account_name, "my_account".to_string());
896 assert_eq!(unsafe_password, Some("old_password".to_string()));
897 assert_eq!(unsafe_new_password, Some("new_password".to_string()));
898 assert!(keystore_dir.is_none());
899 }
900 _ => panic!("expected WalletSubcommands::ChangePassword"),
901 }
902 }
903}