pea-wallet 0.4.0

エンドウ
Documentation
use crate::Wallet;
use colored::*;
use crossterm::{event, terminal};
use inquire::{Confirm, CustomType, Select};
use pea_address as address;
use pea_api::{get, post};
use pea_core::constants::COIN;
use pea_stake::Stake;
use pea_transaction::Transaction;
use std::{ops::Range, process, time::Duration};
pub struct Command {
    api: String,
    wallet: Option<Wallet>,
}
impl Command {
    pub fn new(api: String) -> Command {
        Command { api, wallet: None }
    }
    pub async fn select(&mut self) -> bool {
        let mut vec = vec!["Wallet", "Search", "Height", "API", "Exit"];
        if self.wallet.is_some() {
            let mut v = vec!["Address", "Balance", "Send", "Stake", "Secret", "Encrypted", "Subkeys"];
            v.append(&mut vec);
            vec = v;
        };
        match Select::new(">>", vec).prompt().unwrap_or_else(|err| {
            println!("{}", err.to_string().red());
            process::exit(0)
        }) {
            "Wallet" => {
                self.decrypt();
                false
            }
            "Search" => {
                Self::search(&self.api).await;
                true
            }
            "Height" => {
                Self::height(&self.api).await;
                true
            }
            "API" => {
                Self::info(&self.api).await;
                true
            }
            "Address" => {
                Self::address(self.wallet.as_ref().unwrap());
                true
            }
            "Balance" => {
                Self::balance(&self.api, &self.wallet.as_ref().unwrap().key.public()).await;
                true
            }
            "Send" => {
                Self::transaction(self.wallet.as_ref().unwrap(), &self.api).await;
                true
            }
            "Stake" => {
                Self::stake(self.wallet.as_ref().unwrap(), &self.api).await;
                true
            }
            "Secret" => {
                Self::key(self.wallet.as_ref().unwrap());
                true
            }
            "Encrypted" => {
                Self::data(self.wallet.as_ref().unwrap());
                true
            }
            "Subkeys" => self.subkeys().await,
            _ => {
                process::exit(0);
            }
        }
    }
    pub fn press_any_key_to_continue() {
        println!("{}", "Press any key to continue...".magenta().italic());
        terminal::enable_raw_mode().unwrap();
        event::read().unwrap();
        terminal::disable_raw_mode().unwrap();
    }
    pub fn clear() {
        print!("\x1B[2J\x1B[1;1H");
    }
    fn decrypt(&mut self) {
        self.wallet = Some(Wallet::import("", "").unwrap());
    }
    async fn subkeys(&mut self) -> bool {
        match Select::new(">>", vec!["Balance", "Withdraw", "Back"]).prompt().unwrap_or_else(|err| {
            println!("{}", err.to_string().red());
            process::exit(0)
        }) {
            "Balance" => {
                self.subkeys_balance().await;
                true
            }
            "Withdraw" => {
                self.subkeys_withdraw().await;
                true
            }
            "Back" => false,
            _ => process::exit(0),
        }
    }
    fn inquire_subkeys_range() -> Range<usize> {
        let start = CustomType::<usize>::new("Range Start:")
            .with_error_message("Please type a valid number")
            .with_help_message("The start of the range of subkeys to check")
            .prompt()
            .unwrap_or_else(|err| {
                println!("{}", err.to_string().red());
                process::exit(0)
            });
        let end = CustomType::<usize>::new("Range End:")
            .with_error_message("Please type a valid number")
            .with_help_message("The end of the range of subkeys to check")
            .prompt()
            .unwrap_or_else(|err| {
                println!("{}", err.to_string().red());
                process::exit(0)
            });
        start..end
    }
    async fn subkeys_balance(&self) {
        let key = &self.wallet.as_ref().unwrap().key;
        for n in Self::inquire_subkeys_range() {
            let subkey = key.subkey(n);
            let address = subkey.public();
            println!("{} {}", n.to_string().red(), address.green());
            Self::balance(&self.api, &address).await;
        }
    }
    async fn subkeys_withdraw(&self) {
        let fee = Self::inquire_fee();
        let key = &self.wallet.as_ref().unwrap().key;
        for n in Self::inquire_subkeys_range() {
            let subkey = key.subkey(n);
            let address = subkey.public();
            println!("{} {}", n.to_string().red(), address.green());
            match get::balance(&self.api, &address).await {
                Ok(balance) => {
                    if balance == 0 {
                        continue;
                    }
                    println!("Account balance: {}", pea_int::to_string(balance).yellow());
                    if balance <= fee {
                        println!("{}", "Insufficient balance".red());
                        continue;
                    }
                    let amount = balance - fee;
                    println!("Withdrawing: {} = {} - {}", amount.to_string().yellow(), balance, fee);
                    let mut transaction = Transaction::new(key.public_key_bytes(), amount, fee);
                    transaction.sign(&subkey);
                    println!("{:?}", transaction);
                    match post::transaction(&self.api, &transaction).await {
                        Ok(res) => println!("{}", if res == "success" { res.green() } else { res.red() }),
                        Err(err) => println!("{}", err.to_string().red()),
                    };
                }
                Err(err) => println!("{}", err.to_string().red()),
            };
        }
    }
    async fn info(api: &str) {
        match get::index(api).await {
            Ok(info) => println!("{}", info.green()),
            Err(err) => println!("{}", err.to_string().red()),
        };
        match get::info(api).await {
            Ok(info) => {
                if info.syncing {
                    println!("{}", "Downloading blockchain!".yellow());
                } else {
                    println!("Blockchain synchronized.");
                }
                println!("Latest block height is {}", info.height.to_string().yellow());
                println!("Tree size {}", info.tree_size.to_string().yellow());
                println!("Gossipsub peers {}", info.gossipsub_peers.to_string().yellow());
                println!("Heartbeats {}", info.heartbeats.to_string().yellow());
                println!("Lag {}", format!("{:?}", Duration::from_micros((info.lag * 1000_f64) as u64)).yellow());
            }
            Err(err) => println!("{}", err.to_string().red()),
        };
    }
    async fn balance(api: &str, address: &str) {
        match get::balance(api, address).await {
            Ok(balance) => match get::balance_staked(api, address).await {
                Ok(balance_staked) => println!(
                    "Account balance: {}, locked: {}",
                    pea_int::to_string(balance).yellow(),
                    pea_int::to_string(balance_staked).yellow()
                ),
                Err(err) => println!("{}", err.to_string().red()),
            },
            Err(err) => println!("{}", err.to_string().red()),
        };
    }
    async fn height(api: &str) {
        match get::height(api).await {
            Ok(height) => println!("Latest block height is {}.", height.to_string().yellow()),
            Err(err) => println!("{}", err.to_string().red()),
        };
    }
    fn inquire_address() -> String {
        CustomType::<String>::new("Address:")
            .with_error_message("Please enter a valid address")
            .with_help_message("Type the hex encoded address with 0x as prefix")
            .with_parser(&|x| match address::public::decode(x) {
                Ok(y) => Ok(address::public::encode(&y)),
                Err(_) => Err(()),
            })
            .prompt()
            .unwrap_or_else(|err| {
                println!("{}", err.to_string().red());
                process::exit(0)
            })
    }
    fn inquire_amount() -> u128 {
        (CustomType::<f64>::new("Amount:")
            .with_formatter(&|i| format!("{:.18} pea", i))
            .with_error_message("Please type a valid number")
            .with_help_message("Type the amount to send using a decimal point as a separator")
            .with_parser(&|x| match x.parse::<f64>() {
                Ok(f) => Ok(pea_int::floor((f * COIN as f64) as u128) as f64 / COIN as f64),
                Err(_) => Err(()),
            })
            .prompt()
            .unwrap_or_else(|err| {
                println!("{}", err.to_string().red());
                process::exit(0)
            })
            * COIN as f64) as u128
    }
    fn inquire_fee() -> u128 {
        CustomType::<u128>::new("Fee:")
            .with_formatter(&|i| format!("{} {}", i, if i == 1 { "satoshi" } else { "satoshis" }))
            .with_error_message("Please type a valid number")
            .with_help_message("Type the fee to use in satoshis")
            .with_parser(&|x| match x.parse::<u128>() {
                Ok(u) => Ok(pea_int::floor(u)),
                Err(_) => Err(()),
            })
            .prompt()
            .unwrap_or_else(|err| {
                println!("{}", err.to_string().red());
                process::exit(0)
            })
    }
    fn inquire_deposit() -> bool {
        match Select::new(">>", vec!["deposit", "withdraw"]).prompt().unwrap_or_else(|err| {
            println!("{}", err.to_string().red());
            process::exit(0)
        }) {
            "deposit" => true,
            "withdraw" => false,
            _ => false,
        }
    }
    async fn transaction(wallet: &Wallet, api: &str) {
        let address = Self::inquire_address();
        let amount = Self::inquire_amount();
        let fee = Self::inquire_fee();
        if !match Confirm::new("Send?").prompt() {
            Ok(b) => b,
            Err(err) => {
                println!("{}", err.to_string().red());
                process::exit(0)
            }
        } {
            return;
        }
        let mut transaction = Transaction::new(address::public::decode(&address).unwrap(), amount, fee);
        transaction.sign(&wallet.key);
        println!("Hash: {}", hex::encode(transaction.hash()).cyan());
        match post::transaction(api, &transaction).await {
            Ok(res) => println!("{}", if res == "success" { res.green() } else { res.red() }),
            Err(err) => println!("{}", err.to_string().red()),
        };
    }
    fn inquire_send() -> bool {
        match Confirm::new("Send?").prompt() {
            Ok(b) => b,
            Err(err) => {
                println!("{}", err.to_string().red());
                process::exit(0)
            }
        }
    }
    async fn stake(wallet: &Wallet, api: &str) {
        let deposit = Self::inquire_deposit();
        let amount = Self::inquire_amount();
        let fee = Self::inquire_fee();
        let send = Self::inquire_send();
        if !send {
            return;
        }
        let mut stake = Stake::new(deposit, amount, fee);
        stake.sign(&wallet.key);
        println!("Hash: {}", hex::encode(stake.hash()).cyan());
        match post::stake(api, &stake).await {
            Ok(res) => println!("{}", if res == "success" { res.green() } else { res.red() }),
            Err(err) => println!("{}", err.to_string().red()),
        };
    }
    fn address(wallet: &Wallet) {
        println!("{}", wallet.key.public().green());
    }
    fn inquire_search() -> String {
        CustomType::<String>::new("Search:")
            .with_error_message("Please enter a valid Address, Hash or Number.")
            .with_help_message("Search Blockchain, Transactions, Addresses, Blocks and Stakes")
            .with_parser(&|x| {
                if address::public::decode(x).is_ok() || x.len() == 64 || x.parse::<usize>().is_ok() {
                    return Ok(x.to_string());
                }
                Err(())
            })
            .prompt()
            .unwrap_or_else(|err| {
                println!("{}", err.to_string().red());
                process::exit(0)
            })
    }
    async fn search(api: &str) {
        let search = Self::inquire_search();
        if address::public::decode(&search).is_ok() {
            Self::balance(api, &search).await;
            return;
        } else if search.len() == 64 {
            if let Ok(block) = get::block(api, &search).await {
                println!("{:?}", block);
                return;
            };
            if let Ok(transaction) = get::transaction(api, &search).await {
                println!("{:?}", transaction);
                return;
            };
            if let Ok(stake) = get::stake(api, &search).await {
                println!("{:?}", stake);
                return;
            };
        } else if search.parse::<usize>().is_ok() {
            if let Ok(hash) = get::hash(api, &search.parse::<usize>().unwrap()).await {
                if let Ok(block) = get::block(api, &hash).await {
                    println!("{:?}", block);
                    return;
                };
                return;
            };
        }
        println!("{}", "Nothing found".red());
    }
    fn key(wallet: &Wallet) {
        println!("{}", "Are you being watched?".yellow());
        println!("{}", "Never share your secret key!".yellow());
        println!("{}", "Anyone who has it can access your funds from anywhere.".italic());
        println!("{}", "View in private with no cameras around.".italic());
        if match Confirm::new("View secret key?").prompt() {
            Ok(b) => b,
            Err(err) => {
                println!("{}", err.to_string().red());
                process::exit(0)
            }
        } {
            println!("{}", wallet.key.secret().red());
        }
    }
    fn data(wallet: &Wallet) {
        println!(
            "{}{}{}",
            hex::encode(&wallet.salt).red(),
            hex::encode(&wallet.nonce).red(),
            hex::encode(&wallet.ciphertext).red()
        );
    }
}