364 lines
14 KiB
Rust
364 lines
14 KiB
Rust
use clap::{
|
|
crate_description, crate_name, value_t_or_exit, AppSettings, Arg, ArgGroup, ArgMatches,
|
|
SubCommand,
|
|
};
|
|
use console::style;
|
|
|
|
use solana_clap_utils::{
|
|
commitment::COMMITMENT_ARG,
|
|
input_parsers::commitment_of,
|
|
input_validators::is_url,
|
|
keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
|
|
DisplayError,
|
|
};
|
|
use solana_cli::cli::{
|
|
app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType,
|
|
DEFAULT_RPC_TIMEOUT_SECONDS,
|
|
};
|
|
use solana_cli_config::{Config, CONFIG_FILE};
|
|
use solana_cli_output::{display::println_name_value, OutputFormat};
|
|
use solana_client::rpc_config::RpcSendTransactionConfig;
|
|
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
|
use std::{collections::HashMap, error, path::PathBuf, sync::Arc, time::Duration};
|
|
|
|
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
|
let description = match setting_type {
|
|
SettingType::Explicit => "",
|
|
SettingType::Computed => "(computed)",
|
|
SettingType::SystemDefault => "(default)",
|
|
};
|
|
|
|
println!(
|
|
"{} {} {}",
|
|
style(name).bold(),
|
|
style(value),
|
|
style(description).italic(),
|
|
);
|
|
}
|
|
|
|
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
|
|
let parse_args = match matches.subcommand() {
|
|
("config", Some(matches)) => {
|
|
let config_file = match matches.value_of("config_file") {
|
|
None => {
|
|
println!(
|
|
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
|
|
style("No config file found.").bold()
|
|
);
|
|
return Ok(false);
|
|
}
|
|
Some(config_file) => config_file,
|
|
};
|
|
let mut config = Config::load(config_file).unwrap_or_default();
|
|
|
|
match matches.subcommand() {
|
|
("get", Some(subcommand_matches)) => {
|
|
let (url_setting_type, json_rpc_url) =
|
|
CliConfig::compute_json_rpc_url_setting("", &config.json_rpc_url);
|
|
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
|
|
"",
|
|
&config.websocket_url,
|
|
"",
|
|
&config.json_rpc_url,
|
|
);
|
|
let (keypair_setting_type, keypair_path) =
|
|
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
|
|
|
|
if let Some(field) = subcommand_matches.value_of("specific_setting") {
|
|
let (field_name, value, setting_type) = match field {
|
|
"json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type),
|
|
"websocket_url" => ("WebSocket URL", websocket_url, ws_setting_type),
|
|
"keypair" => ("Key Path", keypair_path, keypair_setting_type),
|
|
_ => unreachable!(),
|
|
};
|
|
println_name_value_or(&format!("{}:", field_name), &value, setting_type);
|
|
} else {
|
|
println_name_value("Config File:", config_file);
|
|
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
|
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
|
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
|
}
|
|
}
|
|
("set", Some(subcommand_matches)) => {
|
|
if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
|
|
config.json_rpc_url = url.to_string();
|
|
// Revert to a computed `websocket_url` value when `json_rpc_url` is
|
|
// changed
|
|
config.websocket_url = "".to_string();
|
|
}
|
|
if let Some(url) = subcommand_matches.value_of("websocket_url") {
|
|
config.websocket_url = url.to_string();
|
|
}
|
|
if let Some(keypair) = subcommand_matches.value_of("keypair") {
|
|
config.keypair_path = keypair.to_string();
|
|
}
|
|
|
|
config.save(config_file)?;
|
|
|
|
let (url_setting_type, json_rpc_url) =
|
|
CliConfig::compute_json_rpc_url_setting("", &config.json_rpc_url);
|
|
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
|
|
"",
|
|
&config.websocket_url,
|
|
"",
|
|
&config.json_rpc_url,
|
|
);
|
|
let (keypair_setting_type, keypair_path) =
|
|
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
|
|
|
|
println_name_value("Config File:", config_file);
|
|
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
|
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
|
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
|
}
|
|
("import-address-labels", Some(subcommand_matches)) => {
|
|
let filename = value_t_or_exit!(subcommand_matches, "filename", PathBuf);
|
|
config.import_address_labels(&filename)?;
|
|
config.save(config_file)?;
|
|
println!("Address labels imported from {:?}", filename);
|
|
}
|
|
("export-address-labels", Some(subcommand_matches)) => {
|
|
let filename = value_t_or_exit!(subcommand_matches, "filename", PathBuf);
|
|
config.export_address_labels(&filename)?;
|
|
println!("Address labels exported to {:?}", filename);
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
false
|
|
}
|
|
_ => true,
|
|
};
|
|
Ok(parse_args)
|
|
}
|
|
|
|
pub fn parse_args<'a>(
|
|
matches: &ArgMatches<'_>,
|
|
mut wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
|
) -> Result<(CliConfig<'a>, CliSigners), Box<dyn error::Error>> {
|
|
let config = if let Some(config_file) = matches.value_of("config_file") {
|
|
Config::load(config_file).unwrap_or_default()
|
|
} else {
|
|
Config::default()
|
|
};
|
|
let (_, json_rpc_url) = CliConfig::compute_json_rpc_url_setting(
|
|
matches.value_of("json_rpc_url").unwrap_or(""),
|
|
&config.json_rpc_url,
|
|
);
|
|
|
|
let rpc_timeout = value_t_or_exit!(matches, "rpc_timeout", u64);
|
|
let rpc_timeout = Duration::from_secs(rpc_timeout);
|
|
|
|
let (_, websocket_url) = CliConfig::compute_websocket_url_setting(
|
|
matches.value_of("websocket_url").unwrap_or(""),
|
|
&config.websocket_url,
|
|
matches.value_of("json_rpc_url").unwrap_or(""),
|
|
&config.json_rpc_url,
|
|
);
|
|
let default_signer_arg_name = "keypair".to_string();
|
|
let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
|
|
matches.value_of(&default_signer_arg_name).unwrap_or(""),
|
|
&config.keypair_path,
|
|
);
|
|
|
|
let default_signer = DefaultSigner {
|
|
arg_name: default_signer_arg_name,
|
|
path: default_signer_path.clone(),
|
|
};
|
|
|
|
let CliCommandInfo { command, signers } =
|
|
parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
|
|
|
let output_format = matches
|
|
.value_of("output_format")
|
|
.map(|value| match value {
|
|
"json" => OutputFormat::Json,
|
|
"json-compact" => OutputFormat::JsonCompact,
|
|
_ => unreachable!(),
|
|
})
|
|
.unwrap_or(OutputFormat::Display);
|
|
|
|
let commitment = matches
|
|
.subcommand_name()
|
|
.and_then(|name| matches.subcommand_matches(name))
|
|
.and_then(|sub_matches| commitment_of(sub_matches, COMMITMENT_ARG.long))
|
|
.unwrap_or_default();
|
|
|
|
let address_labels = if matches.is_present("no_address_labels") {
|
|
HashMap::new()
|
|
} else {
|
|
config.address_labels
|
|
};
|
|
|
|
Ok((
|
|
CliConfig {
|
|
command,
|
|
json_rpc_url,
|
|
websocket_url,
|
|
signers: vec![],
|
|
keypair_path: default_signer_path,
|
|
rpc_client: None,
|
|
rpc_timeout,
|
|
verbose: matches.is_present("verbose"),
|
|
output_format,
|
|
commitment,
|
|
send_transaction_config: RpcSendTransactionConfig::default(),
|
|
address_labels,
|
|
},
|
|
signers,
|
|
))
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn error::Error>> {
|
|
solana_logger::setup();
|
|
let matches = app(
|
|
crate_name!(),
|
|
crate_description!(),
|
|
solana_version::version!(),
|
|
)
|
|
.arg({
|
|
let arg = Arg::with_name("config_file")
|
|
.short("C")
|
|
.long("config")
|
|
.value_name("FILEPATH")
|
|
.takes_value(true)
|
|
.global(true)
|
|
.help("Configuration file to use");
|
|
if let Some(ref config_file) = *CONFIG_FILE {
|
|
arg.default_value(&config_file)
|
|
} else {
|
|
arg
|
|
}
|
|
})
|
|
.arg(
|
|
Arg::with_name("json_rpc_url")
|
|
.short("u")
|
|
.long("url")
|
|
.value_name("URL")
|
|
.takes_value(true)
|
|
.global(true)
|
|
.validator(is_url)
|
|
.help("JSON RPC URL for the solana cluster"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("websocket_url")
|
|
.long("ws")
|
|
.value_name("URL")
|
|
.takes_value(true)
|
|
.global(true)
|
|
.validator(is_url)
|
|
.help("WebSocket URL for the solana cluster"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("keypair")
|
|
.short("k")
|
|
.long("keypair")
|
|
.value_name("KEYPAIR")
|
|
.global(true)
|
|
.takes_value(true)
|
|
.help("Filepath or URL to a keypair"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("verbose")
|
|
.long("verbose")
|
|
.short("v")
|
|
.global(true)
|
|
.help("Show additional information"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("no_address_labels")
|
|
.long("no-address-labels")
|
|
.global(true)
|
|
.help("Do not use address labels in the output"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("output_format")
|
|
.long("output")
|
|
.value_name("FORMAT")
|
|
.global(true)
|
|
.takes_value(true)
|
|
.possible_values(&["json", "json-compact"])
|
|
.help("Return information in specified output format"),
|
|
)
|
|
.arg(
|
|
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
|
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
|
.global(true)
|
|
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
|
|
)
|
|
.arg(
|
|
Arg::with_name("rpc_timeout")
|
|
.long("rpc-timeout")
|
|
.value_name("SECONDS")
|
|
.takes_value(true)
|
|
.default_value(DEFAULT_RPC_TIMEOUT_SECONDS)
|
|
.global(true)
|
|
.hidden(true)
|
|
.help("Timeout value for RPC requests"),
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("config")
|
|
.about("Solana command-line tool configuration settings")
|
|
.aliases(&["get", "set"])
|
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
|
.subcommand(
|
|
SubCommand::with_name("get")
|
|
.about("Get current config settings")
|
|
.arg(
|
|
Arg::with_name("specific_setting")
|
|
.index(1)
|
|
.value_name("CONFIG_FIELD")
|
|
.takes_value(true)
|
|
.possible_values(&["json_rpc_url", "websocket_url", "keypair"])
|
|
.help("Return a specific config setting"),
|
|
),
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("set")
|
|
.about("Set a config setting")
|
|
.group(
|
|
ArgGroup::with_name("config_settings")
|
|
.args(&["json_rpc_url", "websocket_url", "keypair"])
|
|
.multiple(true)
|
|
.required(true),
|
|
),
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("import-address-labels")
|
|
.about("Import a list of address labels")
|
|
.arg(
|
|
Arg::with_name("filename")
|
|
.index(1)
|
|
.value_name("FILENAME")
|
|
.takes_value(true)
|
|
.help("YAML file of address labels"),
|
|
),
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("export-address-labels")
|
|
.about("Export the current address labels")
|
|
.arg(
|
|
Arg::with_name("filename")
|
|
.index(1)
|
|
.value_name("FILENAME")
|
|
.takes_value(true)
|
|
.help("YAML file to receive the current address labels"),
|
|
),
|
|
),
|
|
)
|
|
.get_matches();
|
|
|
|
do_main(&matches).map_err(|err| DisplayError::new_as_boxed(err).into())
|
|
}
|
|
|
|
fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
|
|
if parse_settings(&matches)? {
|
|
let mut wallet_manager = None;
|
|
|
|
let (mut config, signers) = parse_args(&matches, &mut wallet_manager)?;
|
|
config.signers = signers.iter().map(|s| s.as_ref()).collect();
|
|
let result = process_command(&config)?;
|
|
println!("{}", result);
|
|
};
|
|
Ok(())
|
|
}
|