uhash-cli 0.5.1

UniversalHash proof-of-work miner for Bostrom blockchain
Documentation
use std::path::PathBuf;

use anyhow::Context;
use clap::Subcommand;
use serde::Serialize;

use crate::wallet::{
    default_wallet_path, ensure_wallet_dir, get_new_password, get_password, Wallet,
};

#[derive(Subcommand)]
pub(crate) enum WalletCommand {
    /// Create a new encrypted wallet
    New,
    /// Show the wallet address
    Address,
    /// Import a mnemonic phrase into an encrypted wallet
    Import {
        #[arg(long)]
        phrase: String,
    },
    /// Export the mnemonic phrase (requires password)
    Export,
}

#[derive(Serialize)]
struct JsonWallet {
    address: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    mnemonic: Option<String>,
}

pub(crate) fn cmd_wallet(
    command: WalletCommand,
    wallet_path: Option<PathBuf>,
    json: bool,
) -> anyhow::Result<()> {
    let path = wallet_path.unwrap_or_else(default_wallet_path);
    match command {
        WalletCommand::New => {
            if path.exists() {
                anyhow::bail!("wallet already exists at {}", path.display());
            }
            let wallet = Wallet::new()?;
            let password = get_new_password()?;
            let save_path = ensure_parent_dir(path)?;
            wallet.save_to_file(&save_path, &password)?;
            print_wallet(&wallet, None, json)
        }
        WalletCommand::Address => {
            let password = get_password("Enter password: ")?;
            let wallet = Wallet::load_from_file(&path, &password)
                .with_context(|| format!("failed to load wallet at {}", path.display()))?;
            print_wallet(&wallet, None, json)
        }
        WalletCommand::Import { phrase } => {
            let wallet = Wallet::from_phrase(&phrase)?;
            let password = get_new_password()?;
            let save_path = ensure_parent_dir(path)?;
            wallet.save_to_file(&save_path, &password)?;
            print_wallet(&wallet, None, json)
        }
        WalletCommand::Export => {
            let password = get_password("Enter password: ")?;
            let wallet = Wallet::load_from_file(&path, &password)
                .with_context(|| format!("failed to load wallet at {}", path.display()))?;
            print_wallet(&wallet, Some(wallet.mnemonic()), json)
        }
    }
}

fn ensure_parent_dir(path: PathBuf) -> anyhow::Result<PathBuf> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    } else {
        let _ = ensure_wallet_dir()?;
    }
    Ok(path)
}

fn print_wallet(wallet: &Wallet, mnemonic: Option<String>, json: bool) -> anyhow::Result<()> {
    if json {
        let out = JsonWallet {
            address: wallet.address_str(),
            mnemonic,
        };
        println!("{}", serde_json::to_string(&out)?);
    } else {
        println!("Address: {}", wallet.address_str());
        if let Some(phrase) = mnemonic {
            println!("Mnemonic: {}", phrase);
        }
    }
    Ok(())
}