use blockchain::env; use blockchain::from_str; use blockchain::read_to_string; use blockchain::tilde; use blockchain::wallets::structures::Wallet; use blockchain::Value; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; use rustyline::{history::DefaultHistory, CompletionType, Config, Editor}; use rustyline_derive::Completer; use rustyline_derive::Helper as RustyHelper; use rustyline_derive::Highlighter as RustyHighlighter; use rustyline_derive::Hinter as RustyHinter; use rustyline_derive::Validator as RustyValidator; async fn read_address_file(path: &str) -> Result { // Allow ~ in paths and then extract either a raw address or an address from wallet JSON. let expanded_path = tilde(path).to_string(); read_to_string(&expanded_path) .await .map_err(|e| format!("Failed to read {expanded_path}: {e}")) .and_then(|contents| extract_address(&contents)) } fn extract_address(contents: &str) -> Result { // Address files may be a plain address string or a saved wallet JSON object. let trimmed = contents.trim(); if trimmed.is_empty() { return Err("Address file is empty".to_string()); } if trimmed.starts_with('{') { // Prefer the short address when present, but accept long_address for wallet validation too. let value: Value = from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?; let address = value .get("short_address") .and_then(|v| v.as_str()) .or_else(|| value.get("long_address").and_then(|v| v.as_str())) .ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?; return Ok(address.trim().to_string()); } Ok(trimmed.to_string()) } #[derive(RustyHelper, Completer, RustyHinter, RustyHighlighter, RustyValidator)] struct PathHelper { #[rustyline(Completer)] completer: FilenameCompleter, } fn prompt_for_path(prompt: &str) -> Result { // Rustyline gives the interactive prompt filesystem completion. let config = Config::builder() .completion_type(CompletionType::List) .build(); let mut editor = Editor::::with_config(config).map_err(|e| e.to_string())?; editor.set_helper(Some(PathHelper { completer: FilenameCompleter::new(), })); match editor.readline(prompt) { Ok(line) => Ok(line.trim().to_string()), Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { Err("Input cancelled".to_string()) } Err(err) => Err(format!("Failed to read address file path: {err}")), } } #[tokio::main] async fn main() { // Accept the address file path as an arg or prompt interactively. let args: Vec = env::args().collect(); if args.len() > 1 && args.len() != 2 { println!("Usage: ./verify_address "); return; } let address_path = if args.len() == 2 { args[1].clone() } else { match prompt_for_path( "Please enter the path to the file containing the wallet address: ", ) { Ok(path) => path, Err(err) => { eprintln!("{err}"); return; } } }; let address = match read_address_file(&address_path).await { Ok(address) => address, Err(err) => { println!("{err}"); return; } }; // Verify either a long wallet address or a current-network short address. let message_hash = Wallet::wallet_validation(address.trim()).await || Wallet::short_address_validation(address.trim()); println!("{message_hash}"); }