Remove interactive behavior from wallet
This commit is contained in:
committed by
Greg Fitzgerald
parent
f3df5df52c
commit
9a4733bde7
@ -13,6 +13,8 @@ use solana::mint::Mint;
|
|||||||
use solana::signature::Signature;
|
use solana::signature::Signature;
|
||||||
use solana::thin_client::ThinClient;
|
use solana::thin_client::ThinClient;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
@ -21,6 +23,58 @@ use std::process::exit;
|
|||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
enum WalletCommand {
|
||||||
|
Balance,
|
||||||
|
AirDrop,
|
||||||
|
Pay,
|
||||||
|
Confirm,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum WalletError {
|
||||||
|
CommandNotRecognized(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for WalletError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for WalletError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&error::Error> {
|
||||||
|
// Generic error, underlying cause isn't tracked.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WalletConfig {
|
||||||
|
leader: ReplicatedData,
|
||||||
|
id: Mint,
|
||||||
|
client_addr: SocketAddr,
|
||||||
|
drone_addr: SocketAddr,
|
||||||
|
command: WalletCommand,
|
||||||
|
sig: Option<Signature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WalletConfig {
|
||||||
|
fn default() -> WalletConfig {
|
||||||
|
let default_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000);
|
||||||
|
WalletConfig {
|
||||||
|
leader: ReplicatedData::new_leader(&default_addr.clone()),
|
||||||
|
id: Mint::new(0),
|
||||||
|
client_addr: default_addr.clone(),
|
||||||
|
drone_addr: default_addr.clone(),
|
||||||
|
command: WalletCommand::Balance,
|
||||||
|
sig: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn print_usage(program: &str, opts: Options) {
|
fn print_usage(program: &str, opts: Options) {
|
||||||
let mut brief = format!("Usage: {} [options]\n\n", program);
|
let mut brief = format!("Usage: {} [options]\n\n", program);
|
||||||
brief += " solana-wallet allows you to perform basic actions, including\n";
|
brief += " solana-wallet allows you to perform basic actions, including\n";
|
||||||
@ -30,16 +84,24 @@ fn print_usage(program: &str, opts: Options) {
|
|||||||
print!("{}", opts.usage(&brief));
|
print!("{}", opts.usage(&brief));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn parse_command(input: &str) -> Result<WalletCommand, WalletError> {
|
||||||
env_logger::init();
|
match input {
|
||||||
|
"balance" => Ok(WalletCommand::Balance),
|
||||||
|
"airdrop" => Ok(WalletCommand::AirDrop),
|
||||||
|
"pay" => Ok(WalletCommand::Pay),
|
||||||
|
"confirm" => Ok(WalletCommand::Confirm),
|
||||||
|
_ => Err(WalletError::CommandNotRecognized(input.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args(args: Vec<String>) -> Result<WalletConfig, Box<error::Error>> {
|
||||||
let mut opts = Options::new();
|
let mut opts = Options::new();
|
||||||
opts.optopt("l", "", "leader", "leader.json");
|
opts.optopt("l", "", "leader", "leader.json");
|
||||||
opts.optopt("m", "", "mint", "mint.json");
|
opts.optopt("m", "", "mint", "mint.json");
|
||||||
opts.optopt("c", "", "client port", "port");
|
opts.optopt("c", "", "client port", "port");
|
||||||
opts.optflag("d", "dyn", "detect network address dynamically");
|
opts.optflag("d", "dyn", "detect network address dynamically");
|
||||||
opts.optflag("h", "help", "print help");
|
opts.optflag("h", "help", "print help");
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -47,126 +109,129 @@ fn main() -> io::Result<()> {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches.opt_present("h") {
|
if matches.opt_present("h") {
|
||||||
let program = args[0].clone();
|
let program = args[0].clone();
|
||||||
print_usage(&program, opts);
|
print_usage(&program, opts);
|
||||||
return Ok(());
|
display_actions();
|
||||||
|
return Ok(WalletConfig::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut client_addr: SocketAddr = "0.0.0.0:8100".parse().unwrap();
|
let mut client_addr: SocketAddr = "0.0.0.0:8100".parse().unwrap();
|
||||||
if matches.opt_present("c") {
|
if matches.opt_present("c") {
|
||||||
let port = matches.opt_str("c").unwrap().parse().unwrap();
|
let port = matches.opt_str("c").unwrap().parse().unwrap();
|
||||||
client_addr.set_port(port);
|
client_addr.set_port(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.opt_present("d") {
|
if matches.opt_present("d") {
|
||||||
client_addr.set_ip(get_ip_addr().unwrap());
|
client_addr.set_ip(get_ip_addr().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let leader = if matches.opt_present("l") {
|
let leader = if matches.opt_present("l") {
|
||||||
read_leader(matches.opt_str("l").unwrap())
|
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)
|
ReplicatedData::new_leader(&server_addr)
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = if matches.opt_present("m") {
|
let id = if matches.opt_present("m") {
|
||||||
read_mint(matches.opt_str("m").unwrap())
|
read_mint(matches.opt_str("m").unwrap())?
|
||||||
} else {
|
} else {
|
||||||
eprintln!("No mint found!");
|
eprintln!("No mint found!");
|
||||||
exit(1);
|
exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut client = mk_client(&client_addr, &leader)?;
|
|
||||||
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 mut last_transaction_sig: Option<Signature> = None;
|
let command = parse_command(&matches.free[0])?;
|
||||||
|
|
||||||
// Start the a, generate a random client keypair, and show user possible commands
|
Ok(WalletConfig {
|
||||||
display_actions();
|
leader,
|
||||||
|
id,
|
||||||
|
client_addr,
|
||||||
|
drone_addr, // TODO: Add an option for this.
|
||||||
|
command,
|
||||||
|
sig: None, // TODO: Add an option for this.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
fn process_command(
|
||||||
let mut input = String::new();
|
config: &WalletConfig,
|
||||||
match std::io::stdin().read_line(&mut input) {
|
client: &mut ThinClient,
|
||||||
Ok(_) => {
|
) -> Result<(), Box<error::Error>> {
|
||||||
match input.trim() {
|
match config.command {
|
||||||
// Check client balance
|
// Check client balance
|
||||||
"balance" => {
|
WalletCommand::Balance => {
|
||||||
println!("Balance requested...");
|
println!("Balance requested...");
|
||||||
let balance = client.poll_get_balance(&id.pubkey());
|
let balance = client.poll_get_balance(&config.id.pubkey());
|
||||||
match balance {
|
match balance {
|
||||||
Ok(balance) => {
|
Ok(balance) => {
|
||||||
println!("Your balance is: {:?}", balance);
|
println!("Your balance is: {:?}", balance);
|
||||||
}
|
}
|
||||||
Err(ref e) if e.kind() == std::io::ErrorKind::Other => {
|
Err(ref e) if e.kind() == std::io::ErrorKind::Other => {
|
||||||
println!("No account found! Request an airdrop to get started.");
|
println!("No account found! Request an airdrop to get started.");
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("An error occurred: {:?}", error);
|
println!("An error occurred: {:?}", error);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Request an airdrop from Solana Drone;
|
|
||||||
// Request amount is set in request_airdrop function
|
|
||||||
"airdrop" => {
|
|
||||||
println!("Airdrop requested...");
|
|
||||||
let _airdrop = request_airdrop(&drone_addr, &id);
|
|
||||||
// TODO: return airdrop Result from Drone
|
|
||||||
sleep(Duration::from_millis(100));
|
|
||||||
println!(
|
|
||||||
"Your balance is: {:?}",
|
|
||||||
client.poll_get_balance(&id.pubkey()).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// If client has positive balance, spend tokens in {balance} number of transactions
|
|
||||||
"pay" => {
|
|
||||||
let last_id = client.get_last_id();
|
|
||||||
let balance = client.poll_get_balance(&id.pubkey());
|
|
||||||
match balance {
|
|
||||||
Ok(0) => {
|
|
||||||
println!("You don't have any tokens!");
|
|
||||||
}
|
|
||||||
Ok(balance) => {
|
|
||||||
println!("Sending {:?} tokens to self...", balance);
|
|
||||||
let sig = client
|
|
||||||
.transfer(balance, &id.keypair(), id.pubkey(), &last_id)
|
|
||||||
.expect("transfer return signature");
|
|
||||||
last_transaction_sig = Some(sig);
|
|
||||||
println!("Transaction sent!");
|
|
||||||
println!("Signature: {:?}", sig);
|
|
||||||
}
|
|
||||||
Err(ref e) if e.kind() == std::io::ErrorKind::Other => {
|
|
||||||
println!("No account found! Request an airdrop to get started.");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
println!("An error occurred: {:?}", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Confirm the last client transaction by signature
|
|
||||||
"confirm" => match last_transaction_sig {
|
|
||||||
Some(sig) => {
|
|
||||||
if client.check_signature(&sig) {
|
|
||||||
println!("Signature found at bank id {:?}", id);
|
|
||||||
} else {
|
|
||||||
println!("Uh oh... Signature not found!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("No recent signature. Make a payment to get started.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
println!("Command {:?} not recognized", input.trim());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
display_actions();
|
|
||||||
}
|
}
|
||||||
Err(error) => println!("error: {}", error),
|
|
||||||
}
|
}
|
||||||
|
// Request an airdrop from Solana Drone;
|
||||||
|
// Request amount is set in request_airdrop function
|
||||||
|
WalletCommand::AirDrop => {
|
||||||
|
println!("Airdrop requested...");
|
||||||
|
let _airdrop = request_airdrop(&config.drone_addr, &config.id);
|
||||||
|
// TODO: return airdrop Result from Drone
|
||||||
|
sleep(Duration::from_millis(100));
|
||||||
|
println!(
|
||||||
|
"Your balance is: {:?}",
|
||||||
|
client.poll_get_balance(&config.id.pubkey()).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If client has positive balance, spend tokens in {balance} number of transactions
|
||||||
|
WalletCommand::Pay => {
|
||||||
|
let last_id = client.get_last_id();
|
||||||
|
let balance = client.poll_get_balance(&config.id.pubkey());
|
||||||
|
match balance {
|
||||||
|
Ok(0) => {
|
||||||
|
println!("You don't have any tokens!");
|
||||||
|
}
|
||||||
|
Ok(balance) => {
|
||||||
|
println!("Sending {:?} tokens to self...", balance);
|
||||||
|
let sig = client
|
||||||
|
.transfer(balance, &config.id.keypair(), config.id.pubkey(), &last_id)
|
||||||
|
.expect("transfer return signature");
|
||||||
|
println!("Transaction sent!");
|
||||||
|
println!("Signature: {:?}", sig);
|
||||||
|
}
|
||||||
|
Err(ref e) if e.kind() == std::io::ErrorKind::Other => {
|
||||||
|
println!("No account found! Request an airdrop to get started.");
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
println!("An error occurred: {:?}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Confirm the last client transaction by signature
|
||||||
|
WalletCommand::Confirm => match config.sig {
|
||||||
|
Some(sig) => {
|
||||||
|
if client.check_signature(&sig) {
|
||||||
|
println!("Signature found at bank id {:?}", config.id);
|
||||||
|
} else {
|
||||||
|
println!("Uh oh... Signature not found!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("No recent signature. Make a payment to get started.");
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_actions() {
|
fn display_actions() {
|
||||||
println!("");
|
println!("");
|
||||||
println!("What would you like to do? Type a command:");
|
|
||||||
println!(" `balance` - Get your account balance");
|
println!(" `balance` - Get your account balance");
|
||||||
println!(" `airdrop` - Request a batch of tokens");
|
println!(" `airdrop` - Request a batch of tokens");
|
||||||
println!(" `pay` - Spend your tokens as fast as possible");
|
println!(" `pay` - Spend your tokens as fast as possible");
|
||||||
@ -179,9 +244,10 @@ fn read_leader(path: String) -> ReplicatedData {
|
|||||||
serde_json::from_reader(file).expect(&format!("failed to parse {}", path))
|
serde_json::from_reader(file).expect(&format!("failed to parse {}", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_mint(path: String) -> Mint {
|
fn read_mint(path: String) -> Result<Mint, Box<error::Error>> {
|
||||||
let file = File::open(path.clone()).expect(&format!("file not found: {}", path));
|
let file = File::open(path.clone())?;
|
||||||
serde_json::from_reader(file).expect(&format!("failed to parse {}", path))
|
let mint = serde_json::from_reader(file)?;
|
||||||
|
Ok(mint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result<ThinClient> {
|
fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result<ThinClient> {
|
||||||
@ -211,3 +277,10 @@ fn request_airdrop(drone_addr: &SocketAddr, id: &Mint) {
|
|||||||
stream.write_all(&tx).unwrap();
|
stream.write_all(&tx).unwrap();
|
||||||
// TODO: add timeout to this function, in case of unresponsive drone
|
// TODO: add timeout to this function, in case of unresponsive drone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<error::Error>> {
|
||||||
|
env_logger::init();
|
||||||
|
let config = parse_args(env::args().collect())?;
|
||||||
|
let mut client = mk_client(&config.client_addr, &config.leader)?;
|
||||||
|
process_command(&config, &mut client)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user