2018-06-27 14:41:49 -07:00
|
|
|
extern crate atty;
|
2018-06-26 22:52:34 -07:00
|
|
|
extern crate bincode;
|
|
|
|
extern crate env_logger;
|
|
|
|
extern crate getopts;
|
|
|
|
extern crate serde_json;
|
|
|
|
extern crate solana;
|
|
|
|
|
|
|
|
use bincode::serialize;
|
|
|
|
use getopts::Options;
|
|
|
|
use solana::crdt::{get_ip_addr, ReplicatedData};
|
|
|
|
use solana::drone::DroneRequest;
|
2018-06-27 14:41:49 -07:00
|
|
|
use solana::mint::Mint;
|
2018-06-28 11:58:33 -07:00
|
|
|
use solana::signature::Signature;
|
2018-06-26 22:52:34 -07:00
|
|
|
use solana::thin_client::ThinClient;
|
|
|
|
use std::env;
|
|
|
|
use std::fs::File;
|
2018-06-27 14:41:49 -07:00
|
|
|
use std::io;
|
2018-06-26 22:52:34 -07:00
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream, UdpSocket};
|
|
|
|
use std::process::exit;
|
|
|
|
use std::thread::sleep;
|
2018-06-27 14:21:30 -07:00
|
|
|
use std::time::Duration;
|
2018-06-26 22:52:34 -07:00
|
|
|
|
|
|
|
fn print_usage(program: &str, opts: Options) {
|
|
|
|
let mut brief = format!("Usage: {} [options]\n\n", program);
|
2018-06-27 14:21:30 -07:00
|
|
|
brief += " solana-wallet allows you to perform basic actions, including\n";
|
2018-06-26 22:52:34 -07:00
|
|
|
brief += " requesting an airdrop, checking your balance, and spending tokens.";
|
|
|
|
brief += " Takes json formatted mint file to stdin.";
|
|
|
|
|
|
|
|
print!("{}", opts.usage(&brief));
|
|
|
|
}
|
|
|
|
|
2018-06-27 14:41:49 -07:00
|
|
|
fn main() -> io::Result<()> {
|
2018-06-26 22:52:34 -07:00
|
|
|
env_logger::init();
|
2018-06-27 14:41:49 -07:00
|
|
|
|
2018-06-26 22:52:34 -07:00
|
|
|
let mut opts = Options::new();
|
|
|
|
opts.optopt("l", "", "leader", "leader.json");
|
2018-06-28 11:58:33 -07:00
|
|
|
opts.optopt("m", "", "mint", "mint.json");
|
2018-06-26 22:52:34 -07:00
|
|
|
opts.optopt("c", "", "client port", "port");
|
|
|
|
opts.optflag("d", "dyn", "detect network address dynamically");
|
|
|
|
opts.optflag("h", "help", "print help");
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
let matches = match opts.parse(&args[1..]) {
|
|
|
|
Ok(m) => m,
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{}", e);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if matches.opt_present("h") {
|
|
|
|
let program = args[0].clone();
|
|
|
|
print_usage(&program, opts);
|
2018-06-27 14:41:49 -07:00
|
|
|
return Ok(());
|
2018-06-26 22:52:34 -07:00
|
|
|
}
|
|
|
|
let mut client_addr: SocketAddr = "0.0.0.0:8100".parse().unwrap();
|
|
|
|
if matches.opt_present("c") {
|
|
|
|
let port = matches.opt_str("c").unwrap().parse().unwrap();
|
|
|
|
client_addr.set_port(port);
|
|
|
|
}
|
|
|
|
if matches.opt_present("d") {
|
|
|
|
client_addr.set_ip(get_ip_addr().unwrap());
|
|
|
|
}
|
2018-06-28 12:09:10 -07:00
|
|
|
let leader = if matches.opt_present("l") {
|
2018-06-26 22:52:34 -07:00
|
|
|
read_leader(matches.opt_str("l").unwrap())
|
|
|
|
} else {
|
|
|
|
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8000);
|
|
|
|
ReplicatedData::new_leader(&server_addr)
|
|
|
|
};
|
2018-06-28 12:09:10 -07:00
|
|
|
let id = if matches.opt_present("m") {
|
2018-06-28 11:58:33 -07:00
|
|
|
read_mint(matches.opt_str("m").unwrap())
|
|
|
|
} else {
|
2018-06-28 12:09:10 -07:00
|
|
|
println!("No mint found!");
|
|
|
|
exit(1);
|
2018-06-28 11:58:33 -07:00
|
|
|
};
|
2018-06-26 22:52:34 -07:00
|
|
|
|
2018-06-27 14:41:49 -07:00
|
|
|
let mut client = mk_client(&client_addr, &leader)?;
|
2018-06-26 22:52:34 -07:00
|
|
|
let mut drone_addr = leader.transactions_addr.clone();
|
|
|
|
drone_addr.set_port(9900);
|
|
|
|
|
2018-06-28 11:58:33 -07:00
|
|
|
let mut last_transaction_sig: Option<Signature> = None;
|
|
|
|
|
2018-06-27 14:41:49 -07:00
|
|
|
// Start the a, generate a random client keypair, and show user possible commands
|
2018-06-26 22:52:34 -07:00
|
|
|
display_actions();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let mut input = String::new();
|
|
|
|
match std::io::stdin().read_line(&mut input) {
|
2018-06-26 23:15:43 -07:00
|
|
|
Ok(_) => {
|
2018-06-26 22:52:34 -07:00
|
|
|
match input.trim() {
|
2018-06-26 23:06:10 -07:00
|
|
|
// Check client balance
|
2018-06-26 22:52:34 -07:00
|
|
|
"balance" => {
|
|
|
|
println!("Balance requested...");
|
2018-06-27 14:41:49 -07:00
|
|
|
let balance = client.poll_get_balance(&id.pubkey());
|
2018-06-26 22:52:34 -07:00
|
|
|
match balance {
|
|
|
|
Ok(balance) => {
|
|
|
|
println!("Your balance is: {:?}", balance);
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-26 23:06:10 -07:00
|
|
|
// Request an airdrop from Solana Drone;
|
|
|
|
// Request amount is set in request_airdrop function
|
2018-06-26 22:52:34 -07:00
|
|
|
"airdrop" => {
|
|
|
|
println!("Airdrop requested...");
|
2018-06-27 14:41:49 -07:00
|
|
|
let _airdrop = request_airdrop(&drone_addr, &id);
|
2018-06-26 22:52:34 -07:00
|
|
|
// TODO: return airdrop Result from Drone
|
|
|
|
sleep(Duration::from_millis(100));
|
|
|
|
println!(
|
|
|
|
"Your balance is: {:?}",
|
2018-06-27 14:41:49 -07:00
|
|
|
client.poll_get_balance(&id.pubkey()).unwrap()
|
2018-06-26 22:52:34 -07:00
|
|
|
);
|
|
|
|
}
|
2018-06-26 23:06:10 -07:00
|
|
|
// If client has positive balance, spend tokens in {balance} number of transactions
|
2018-06-26 22:52:34 -07:00
|
|
|
"pay" => {
|
|
|
|
let last_id = client.get_last_id();
|
2018-06-27 14:41:49 -07:00
|
|
|
let balance = client.poll_get_balance(&id.pubkey());
|
2018-06-26 22:52:34 -07:00
|
|
|
match balance {
|
|
|
|
Ok(0) => {
|
|
|
|
println!("You don't have any tokens!");
|
|
|
|
}
|
|
|
|
Ok(balance) => {
|
2018-06-27 14:21:30 -07:00
|
|
|
println!("Sending {:?} tokens to self...", balance);
|
2018-06-28 11:58:33 -07:00
|
|
|
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);
|
2018-06-26 22:52:34 -07:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-28 11:58:33 -07:00
|
|
|
// Confirm the last client transaction by signature
|
|
|
|
"confirm" => match last_transaction_sig {
|
|
|
|
Some(sig) => {
|
2018-06-28 17:59:02 -07:00
|
|
|
if client.check_signature(&sig) {
|
|
|
|
println!("Signature found at bank id {:?}", id);
|
|
|
|
} else {
|
2018-06-29 09:21:53 -07:00
|
|
|
println!("Uh oh... Signature not found!");
|
2018-06-28 11:58:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
println!("No recent signature. Make a payment to get started.");
|
|
|
|
}
|
|
|
|
},
|
2018-06-26 22:52:34 -07:00
|
|
|
_ => {
|
|
|
|
println!("Command {:?} not recognized", input.trim());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
display_actions();
|
|
|
|
}
|
|
|
|
Err(error) => println!("error: {}", error),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn display_actions() {
|
|
|
|
println!("");
|
|
|
|
println!("What would you like to do? Type a command:");
|
|
|
|
println!(" `balance` - Get your account balance");
|
2018-06-28 11:58:33 -07:00
|
|
|
println!(" `airdrop` - Request a batch of tokens");
|
2018-06-26 22:52:34 -07:00
|
|
|
println!(" `pay` - Spend your tokens as fast as possible");
|
2018-06-28 11:58:33 -07:00
|
|
|
println!(" `confirm` - Confirm your last payment by signature");
|
2018-06-26 22:52:34 -07:00
|
|
|
println!("");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read_leader(path: String) -> ReplicatedData {
|
|
|
|
let file = File::open(path.clone()).expect(&format!("file not found: {}", path));
|
|
|
|
serde_json::from_reader(file).expect(&format!("failed to parse {}", path))
|
2018-06-28 11:58:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn read_mint(path: String) -> Mint {
|
|
|
|
let file = File::open(path.clone()).expect(&format!("file not found: {}", path));
|
|
|
|
serde_json::from_reader(file).expect(&format!("failed to parse {}", path))
|
2018-06-26 22:52:34 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 14:41:49 -07:00
|
|
|
fn mk_client(client_addr: &SocketAddr, r: &ReplicatedData) -> io::Result<ThinClient> {
|
2018-06-26 22:52:34 -07:00
|
|
|
let mut addr = client_addr.clone();
|
|
|
|
let port = addr.port();
|
2018-06-27 14:41:49 -07:00
|
|
|
let transactions_socket = UdpSocket::bind(addr.clone())?;
|
2018-06-26 22:52:34 -07:00
|
|
|
addr.set_port(port + 1);
|
2018-06-27 14:41:49 -07:00
|
|
|
let requests_socket = UdpSocket::bind(addr.clone())?;
|
|
|
|
requests_socket.set_read_timeout(Some(Duration::new(1, 0)))?;
|
2018-06-26 22:52:34 -07:00
|
|
|
|
|
|
|
addr.set_port(port + 2);
|
2018-06-27 14:41:49 -07:00
|
|
|
Ok(ThinClient::new(
|
2018-06-26 22:52:34 -07:00
|
|
|
r.requests_addr,
|
|
|
|
requests_socket,
|
|
|
|
r.transactions_addr,
|
|
|
|
transactions_socket,
|
2018-06-27 14:41:49 -07:00
|
|
|
))
|
2018-06-26 22:52:34 -07:00
|
|
|
}
|
|
|
|
|
2018-06-27 14:41:49 -07:00
|
|
|
fn request_airdrop(drone_addr: &SocketAddr, id: &Mint) {
|
2018-06-26 22:52:34 -07:00
|
|
|
let mut stream = TcpStream::connect(drone_addr).unwrap();
|
|
|
|
let req = DroneRequest::GetAirdrop {
|
2018-06-27 14:41:49 -07:00
|
|
|
airdrop_request_amount: id.tokens as u64,
|
|
|
|
client_public_key: id.pubkey(),
|
2018-06-26 22:52:34 -07:00
|
|
|
};
|
|
|
|
let tx = serialize(&req).expect("serialize drone request");
|
|
|
|
stream.write_all(&tx).unwrap();
|
|
|
|
// TODO: add timeout to this function, in case of unresponsive drone
|
|
|
|
}
|