diff --git a/Cargo.lock b/Cargo.lock index 27b062cdf4d7ac..2b623ce68b7213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3744,6 +3744,7 @@ dependencies = [ "solana-faucet 0.24.0", "solana-logger 0.24.0", "solana-net-utils 0.24.0", + "solana-remote-wallet 0.24.0", "solana-runtime 0.24.0", "solana-sdk 0.24.0", "solana-stake-program 0.24.0", diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index 93bcb77dbcf732..34cf248fe2b9a8 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -32,8 +32,8 @@ pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant { #[derive(Debug, PartialEq)] pub enum Source { - File, Generated, + Path, SeedPhrase, } @@ -131,7 +131,12 @@ pub fn keypair_input( keypair_from_seed_phrase(keypair_name, skip_validation, true) .map(|keypair| KeypairWithSource::new(keypair, Source::SeedPhrase)) } else if let Some(keypair_file) = matches.value_of(keypair_match_name) { - read_keypair_file(keypair_file).map(|keypair| KeypairWithSource::new(keypair, Source::File)) + if keypair_file.starts_with("usb://") { + Ok(KeypairWithSource::new(Keypair::new(), Source::Path)) + } else { + read_keypair_file(keypair_file) + .map(|keypair| KeypairWithSource::new(keypair, Source::Path)) + } } else { Ok(KeypairWithSource::new(Keypair::new(), Source::Generated)) } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ff309925223504..0f11192c0e752e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,6 +34,7 @@ solana-config-program = { path = "../programs/config", version = "0.24.0" } solana-faucet = { path = "../faucet", version = "0.24.0" } solana-logger = { path = "../logger", version = "0.24.0" } solana-net-utils = { path = "../net-utils", version = "0.24.0" } +solana-remote-wallet = { path = "../remote-wallet", version = "0.24.0" } solana-runtime = { path = "../runtime", version = "0.24.0" } solana-sdk = { path = "../sdk", version = "0.24.0" } solana-stake-program = { path = "../programs/stake", version = "0.24.0" } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e5f3e1df52459c..9f2eb8fb61250b 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -20,6 +20,10 @@ use solana_client::{client_error::ClientError, rpc_client::RpcClient}; use solana_faucet::faucet::request_airdrop_transaction; #[cfg(test)] use solana_faucet::faucet_mock::request_airdrop_transaction; +use solana_remote_wallet::{ + ledger::get_ledger_from_info, + remote_wallet::{DerivationPath, RemoteWallet, RemoteWalletInfo}, +}; use solana_sdk::{ bpf_loader, clock::{Epoch, Slot}, @@ -416,6 +420,7 @@ pub struct CliConfig { pub json_rpc_url: String, pub keypair: Keypair, pub keypair_path: Option, + pub derivation_path: Option, pub rpc_client: Option, pub verbose: bool, } @@ -430,6 +435,22 @@ impl CliConfig { pub fn default_json_rpc_url() -> String { "http://127.0.0.1:8899".to_string() } + + pub(crate) fn pubkey(&self) -> Result> { + if let Some(path) = &self.keypair_path { + if path.starts_with("usb://") { + let (remote_wallet_info, mut derivation_path) = + RemoteWalletInfo::parse_path(path.to_string())?; + if let Some(derivation) = &self.derivation_path { + let derivation = derivation.clone(); + derivation_path = derivation; + } + let ledger = get_ledger_from_info(remote_wallet_info)?; + return Ok(ledger.get_pubkey(derivation_path)?); + } + } + Ok(self.keypair.pubkey()) + } } impl Default for CliConfig { @@ -442,6 +463,7 @@ impl Default for CliConfig { json_rpc_url: Self::default_json_rpc_url(), keypair: Keypair::new(), keypair_path: Some(Self::default_keypair_path()), + derivation_path: None, rpc_client: None, verbose: false, } @@ -821,7 +843,7 @@ fn process_create_address_with_seed( seed: &str, program_id: &Pubkey, ) -> ProcessResult { - let config_pubkey = config.keypair.pubkey(); + let config_pubkey = config.pubkey()?; let from_pubkey = from_pubkey.unwrap_or(&config_pubkey); let address = create_address_with_seed(from_pubkey, seed, program_id)?; Ok(address.to_string()) @@ -834,12 +856,13 @@ fn process_airdrop( lamports: u64, use_lamports_unit: bool, ) -> ProcessResult { + let pubkey = config.pubkey()?; println!( "Requesting airdrop of {} from {}", build_balance_message(lamports, use_lamports_unit, true), faucet_addr ); - let previous_balance = match rpc_client.retry_get_balance(&config.keypair.pubkey(), 5)? { + let previous_balance = match rpc_client.retry_get_balance(&pubkey, 5)? { Some(lamports) => lamports, None => { return Err(CliError::RpcRequestError( @@ -849,10 +872,10 @@ fn process_airdrop( } }; - request_and_confirm_airdrop(&rpc_client, faucet_addr, &config.keypair.pubkey(), lamports)?; + request_and_confirm_airdrop(&rpc_client, faucet_addr, &pubkey, lamports)?; let current_balance = rpc_client - .retry_get_balance(&config.keypair.pubkey(), 5)? + .retry_get_balance(&pubkey, 5)? .unwrap_or(previous_balance); Ok(build_balance_message( @@ -868,7 +891,7 @@ fn process_balance( pubkey: &Option, use_lamports_unit: bool, ) -> ProcessResult { - let pubkey = pubkey.unwrap_or(config.keypair.pubkey()); + let pubkey = pubkey.unwrap_or(config.pubkey()?); let balance = rpc_client.retry_get_balance(&pubkey, 5)?; match balance { Some(lamports) => Ok(build_balance_message(lamports, use_lamports_unit, true)), @@ -1251,7 +1274,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { match &config.command { // Cluster Query Commands // Get address of this client - CliCommand::Address => Ok(format!("{}", config.keypair.pubkey())), + CliCommand::Address => Ok(format!("{}", config.pubkey()?)), // Return software version of solana-cli and cluster entrypoint node CliCommand::Catchup { node_pubkey } => process_catchup(&rpc_client, node_pubkey), diff --git a/cli/src/main.rs b/cli/src/main.rs index ce9fdb29d2c5d6..8f00b1d279ece3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,7 +2,8 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches use console::style; use solana_clap_utils::{ - input_validators::is_url, + input_parsers::derivation_of, + input_validators::{is_derivation, is_url}, keypair::{ self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG, SKIP_SEED_PHRASE_VALIDATION_ARG, @@ -102,7 +103,7 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result ( + keypair::Source::Path => ( keypair, Some(matches.value_of("keypair").unwrap().to_string()), ), @@ -122,12 +123,16 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result) -> Result Result<(), Box> { .value_name("PATH") .global(true) .takes_value(true) - .help("/path/to/id.json"), + .help("/path/to/id.json or usb://remote/wallet/path"), + ) + .arg( + Arg::with_name("derivation_path") + .long("derivation-path") + .value_name("ACCOUNT or ACCOUNT/CHANGE") + .takes_value(true) + .validator(is_derivation) + .help("Derivation path to use: m/44'/501'/ACCOUNT'/CHANGE'; default key is device base pubkey: m/44'/501'/0'") ) .arg( Arg::with_name("verbose")