Use clap crate for wallet CLI subcommands and arguments

This commit is contained in:
Tyera Eulberg 2018-06-29 19:33:20 -06:00 committed by Greg Fitzgerald
parent 9a38d61048
commit ba6a6f5227
2 changed files with 125 additions and 76 deletions

View File

@ -78,3 +78,4 @@ itertools = "0.7.8"
bs58 = "0.2.0" bs58 = "0.2.0"
p2p = "0.5.2" p2p = "0.5.2"
futures = "0.1.21" futures = "0.1.21"
clap = "2.31"

View File

@ -1,20 +1,20 @@
extern crate atty; extern crate atty;
extern crate bincode; extern crate bincode;
extern crate bs58; extern crate bs58;
extern crate clap;
extern crate env_logger; extern crate env_logger;
extern crate getopts; extern crate getopts;
extern crate serde_json; extern crate serde_json;
extern crate solana; extern crate solana;
use bincode::serialize; use bincode::serialize;
use getopts::{Matches, Options}; use clap::{App, Arg, SubCommand};
use solana::crdt::ReplicatedData; use solana::crdt::ReplicatedData;
use solana::drone::DroneRequest; use solana::drone::DroneRequest;
use solana::mint::Mint; use solana::mint::Mint;
use solana::nat::udp_public_bind; use solana::nat::udp_public_bind;
use solana::signature::{PublicKey, Signature}; use solana::signature::{PublicKey, Signature};
use solana::thin_client::ThinClient; use solana::thin_client::ThinClient;
use std::env;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
@ -28,7 +28,7 @@ use std::time::Duration;
enum WalletCommand { enum WalletCommand {
Address, Address,
Balance, Balance,
AirDrop, AirDrop(i64),
Pay(i64, PublicKey), Pay(i64, PublicKey),
Confirm(Signature), Confirm(Signature),
} }
@ -74,77 +74,84 @@ impl Default for WalletConfig {
} }
} }
fn print_usage(program: &str, opts: Options) { fn parse_args() -> Result<WalletConfig, Box<error::Error>> {
let mut brief = format!("Usage: {} [options]\n\n", program); let matches = App::new("solana-wallet")
brief += " solana-wallet allows you to perform basic actions, including"; .arg(
brief += " requesting an airdrop, checking your balance, and spending tokens."; Arg::with_name("leader")
brief += " Takes json formatted mint file to stdin."; .short("l")
.long("leader")
.value_name("PATH")
.takes_value(true)
.help("/path/to/leader.json"),
)
.arg(
Arg::with_name("mint")
.short("m")
.long("mint")
.value_name("PATH")
.takes_value(true)
.help("/path/to/mint.json"),
)
.subcommand(
SubCommand::with_name("airdrop")
.about("Request a batch of tokens")
.arg(
Arg::with_name("tokens")
// .index(1)
.long("tokens")
.value_name("NUMBER")
.takes_value(true)
.help("The number of tokens to request"),
),
)
.subcommand(
SubCommand::with_name("pay")
.about("Send a payment")
.arg(
Arg::with_name("tokens")
// .index(2)
.long("tokens")
.value_name("NUMBER")
.takes_value(true)
.required(true)
.help("the number of tokens to send"),
)
.arg(
Arg::with_name("to")
// .index(1)
.long("to")
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("The pubkey of recipient"),
),
)
.subcommand(
SubCommand::with_name("confirm")
.about("Confirm your payment by signature")
.arg(
Arg::with_name("signature")
.index(1)
.value_name("SIGNATURE")
.required(true)
.help("The transaction signature to confirm"),
),
)
.subcommand(SubCommand::with_name("balance").about("Get your balance"))
.subcommand(SubCommand::with_name("address").about("Get your public key"))
.get_matches();
print!("{}", opts.usage(&brief)); let leader: ReplicatedData;
display_actions(); if let Some(l) = matches.value_of("leader") {
} leader = read_leader(l.to_string());
fn parse_command(matches: &Matches) -> Result<WalletCommand, WalletError> {
let input = &matches.free[0];
match input.as_ref() {
"address" => Ok(WalletCommand::Address),
"balance" => Ok(WalletCommand::Balance),
"airdrop" => Ok(WalletCommand::AirDrop),
"pay" => {
if matches.free.len() < 3 {
eprintln!("No tokens and public key provided");
exit(1);
}
let tokens = matches.free[1].parse().expect("parse integer");
let pubkey_vec = bs58::decode(&matches.free[2])
.into_vec()
.expect("base58-encoded public key");
let to = PublicKey::clone_from_slice(&pubkey_vec);
Ok(WalletCommand::Pay(tokens, to))
}
"confirm" => {
if matches.free.len() < 2 {
eprintln!("No signature provided");
exit(1);
}
let sig_vec = bs58::decode(&matches.free[1])
.into_vec()
.expect("base58-encoded signature");
let sig = Signature::clone_from_slice(&sig_vec);
Ok(WalletCommand::Confirm(sig))
}
_ => Err(WalletError::CommandNotRecognized(input.to_string())),
}
}
fn parse_args(args: Vec<String>) -> Result<WalletConfig, Box<error::Error>> {
let mut opts = Options::new();
opts.optopt("l", "", "leader", "leader.json");
opts.optopt("m", "", "mint", "mint.json");
opts.optflag("h", "help", "print help");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(e) => {
eprintln!("{}", e);
exit(1);
}
};
if matches.opt_present("h") || matches.free.len() < 1 {
print_usage(&args[0], opts);
return Ok(WalletConfig::default());
}
let leader = if matches.opt_present("l") {
read_leader(matches.opt_str("l").unwrap())
} else { } else {
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000); let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000);
ReplicatedData::new_leader(&server_addr) leader = ReplicatedData::new_leader(&server_addr);
}; };
let id = if matches.opt_present("m") { let id: Mint;
read_mint(matches.opt_str("m").unwrap())? if let Some(m) = matches.value_of("mint") {
id = read_mint(m.to_string())?;
} else { } else {
eprintln!("No mint found!"); eprintln!("No mint found!");
exit(1); exit(1);
@ -153,7 +160,48 @@ fn parse_args(args: Vec<String>) -> Result<WalletConfig, Box<error::Error>> {
let mut drone_addr = leader.transactions_addr.clone(); let mut drone_addr = leader.transactions_addr.clone();
drone_addr.set_port(9900); drone_addr.set_port(9900);
let command = parse_command(&matches)?; let command = match matches.subcommand() {
("airdrop", Some(airdrop_matches)) => {
let mut tokens: i64 = id.tokens;
if airdrop_matches.is_present("tokens") {
tokens = airdrop_matches.value_of("tokens").unwrap().parse()?;
}
Ok(WalletCommand::AirDrop(tokens))
}
("pay", Some(pay_matches)) => {
let to: PublicKey;
if pay_matches.is_present("to") {
let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap())
.into_vec()
.expect("base58-encoded public key");
to = PublicKey::clone_from_slice(&pubkey_vec);
} else {
to = id.pubkey();
}
let mut tokens: i64 = id.tokens;
if pay_matches.is_present("tokens") {
tokens = pay_matches.value_of("tokens").unwrap().parse()?;
}
Ok(WalletCommand::Pay(tokens, to))
}
("confirm", Some(confirm_matches)) => {
let sig_vec = bs58::decode(confirm_matches.value_of("sig").unwrap())
.into_vec()
.expect("base58-encoded signature");
let sig = Signature::clone_from_slice(&sig_vec);
Ok(WalletCommand::Confirm(sig))
}
("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance),
("address", Some(_address_matches)) => Ok(WalletCommand::Address),
("", None) => {
display_actions();
Err(WalletError::CommandNotRecognized(
"no subcommand given".to_string(),
))
}
_ => unreachable!(),
}?;
Ok(WalletConfig { Ok(WalletConfig {
leader, leader,
id, id,
@ -188,9 +236,10 @@ fn process_command(
} }
// Request an airdrop from Solana Drone; // Request an airdrop from Solana Drone;
// Request amount is set in request_airdrop function // Request amount is set in request_airdrop function
WalletCommand::AirDrop => { WalletCommand::AirDrop(tokens) => {
println!("Airdrop requested..."); println!("Airdrop requested...");
let _airdrop = request_airdrop(&config.drone_addr, &config.id); println!("Airdropping {:?} tokens", tokens);
let _airdrop = request_airdrop(&config.drone_addr, &config.id, tokens as u64);
// TODO: return airdrop Result from Drone // TODO: return airdrop Result from Drone
sleep(Duration::from_millis(100)); sleep(Duration::from_millis(100));
println!( println!(
@ -255,10 +304,10 @@ fn mk_client(r: &ReplicatedData) -> io::Result<ThinClient> {
)) ))
} }
fn request_airdrop(drone_addr: &SocketAddr, id: &Mint) { fn request_airdrop(drone_addr: &SocketAddr, id: &Mint, tokens: u64) {
let mut stream = TcpStream::connect(drone_addr).unwrap(); let mut stream = TcpStream::connect(drone_addr).unwrap();
let req = DroneRequest::GetAirdrop { let req = DroneRequest::GetAirdrop {
airdrop_request_amount: id.tokens as u64, airdrop_request_amount: tokens,
client_public_key: id.pubkey(), client_public_key: id.pubkey(),
}; };
let tx = serialize(&req).expect("serialize drone request"); let tx = serialize(&req).expect("serialize drone request");
@ -267,8 +316,7 @@ fn request_airdrop(drone_addr: &SocketAddr, id: &Mint) {
} }
fn main() -> Result<(), Box<error::Error>> { fn main() -> Result<(), Box<error::Error>> {
env_logger::init(); let config = parse_args()?;
let config = parse_args(env::args().collect())?;
let mut client = mk_client(&config.leader)?; let mut client = mk_client(&config.leader)?;
process_command(&config, &mut client) process_command(&config, &mut client)
} }