Contractless/src/bin/verify_address.rs

111 lines
3.7 KiB
Rust

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<String, String> {
// 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<String, String> {
// 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<String, String> {
// Rustyline gives the interactive prompt filesystem completion.
let config = Config::builder()
.completion_type(CompletionType::List)
.build();
let mut editor =
Editor::<PathHelper, DefaultHistory>::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<String> = env::args().collect();
if args.len() > 1 && args.len() != 2 {
println!("Usage: ./verify_address <address_file>");
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}");
}