use colored::Colorize;
use kobe::{DerivedAccount, Wallet};
use serde::Serialize;
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct HdWalletOutput {
pub chain: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address_type: Option<&'static str>,
pub mnemonic: String,
pub passphrase_protected: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub derivation_style: Option<&'static str>,
pub accounts: Vec<AccountOutput>,
}
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct AccountOutput {
pub index: u32,
pub derivation_path: String,
pub address: String,
pub private_key: String,
}
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct CamouflageOutput {
pub mode: &'static str,
pub words: usize,
pub input: String,
pub output: String,
}
impl HdWalletOutput {
#[must_use]
pub fn simple(chain: &'static str, wallet: &Wallet, accounts: &[DerivedAccount]) -> Self {
Self {
chain,
network: None,
address_type: None,
mnemonic: wallet.mnemonic().to_owned(),
passphrase_protected: wallet.has_passphrase(),
derivation_style: None,
accounts: accounts
.iter()
.enumerate()
.map(|(i, a)| AccountOutput::from_derived(i, a))
.collect(),
}
}
}
impl AccountOutput {
#[must_use]
pub fn from_derived(index: usize, account: &DerivedAccount) -> Self {
Self {
index: u32::try_from(index).unwrap_or(u32::MAX),
derivation_path: account.path.clone(),
address: account.address.clone(),
private_key: account.private_key.to_string(),
}
}
}
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct ErrorOutput {
pub error: String,
}
#[rustfmt::skip]
pub fn render_hd_wallet(
out: &HdWalletOutput,
json: bool,
show_qr: bool,
) -> Result<(), Box<dyn std::error::Error>> {
if json {
return Ok(print_json(out)?);
}
println!();
if let Some(network) = out.network {
println!(" {} {}", "Network".cyan().bold(), network);
}
if let Some(addr_type) = out.address_type {
println!(" {} {}", "Address Type".cyan().bold(), addr_type);
}
println!(" {} {}", "Mnemonic".cyan().bold(), out.mnemonic);
if out.passphrase_protected {
println!(" {} {}", "Passphrase".cyan().bold(), "(set)".dimmed());
}
if let Some(style) = out.derivation_style {
println!(" {} {}", "Style".cyan().bold(), style.dimmed());
}
println!();
let multi = out.accounts.len() > 1;
for (i, acct) in out.accounts.iter().enumerate() {
if multi {
println!(" {} {}", "Index".cyan().bold(), format!("[{}]", acct.index).dimmed());
}
println!(" {} {}", "Path".cyan().bold(), acct.derivation_path);
println!(" {} {}", "Address".cyan().bold(), acct.address.green());
println!(" {} {}", "Private Key".cyan().bold(), acct.private_key);
if show_qr {
crate::qr::render_to_terminal(&acct.address);
}
if i < out.accounts.len() - 1 {
println!();
}
}
println!();
Ok(())
}
#[rustfmt::skip]
pub fn render_camouflage(
out: &CamouflageOutput,
json: bool,
) -> Result<(), Box<dyn std::error::Error>> {
if json {
return Ok(print_json(out)?);
}
let mode_label = if out.mode == "encrypt" { "Encrypt" } else { "Decrypt" };
let (in_label, out_label) = if out.mode == "encrypt" {
("Original", "Camouflaged")
} else {
("Camouflaged", "Recovered")
};
println!();
println!(" {} {}", "Mode".cyan().bold(), mode_label);
println!(" {} {} words", "Words".cyan().bold(), out.words);
if out.mode == "encrypt" {
println!(" {} {}", in_label.cyan().bold(), out.input);
println!(" {} {}", out_label.cyan().bold(), out.output.green());
} else {
println!(" {} {}", in_label.cyan().bold(), out.input);
println!(" {} {}", out_label.cyan().bold(), out.output.green());
}
println!();
Ok(())
}
pub fn print_json<T: Serialize>(value: &T) -> Result<(), serde_json::Error> {
let json = serde_json::to_string_pretty(value)?;
println!("{json}");
Ok(())
}