use crate::cli::{Cli, SetupArgs};
use crate::config::{load_persisted_config, save_persisted_config};
use crate::error::AppError;
use crate::output::CommandOutput;
use crate::utils::{parse_network, parse_payment_address_type, parse_scheme};
use crate::wallet_service::{
default_bitcoin_cli, default_bitcoin_cli_args, default_esplora_url, default_ord_url,
validate_mnemonic_internal, Profile,
};
use crate::wizard::{resolve_setup_values, run_tui_setup_wizard, should_run_setup_wizard};
use crate::{now_unix, profile_path, wallet_password, write_profile};
use serde_json::json;
use std::io::IsTerminal;
use zeroize::Zeroizing;
use zinc_core::{encrypt_wallet_internal, generate_wallet_internal};
pub async fn run(cli: &Cli, args: &SetupArgs) -> Result<CommandOutput, AppError> {
let seed = resolve_setup_values(cli, args)?;
let is_tui = should_run_setup_wizard(
args,
cli,
std::io::stdin().is_terminal(),
std::io::stdout().is_terminal(),
);
let values = if is_tui {
run_tui_setup_wizard(seed).await?
} else {
seed
};
let mut config = load_persisted_config()?;
config.profile = Some(values.profile.clone());
config.data_dir.clone_from(&values.data_dir);
config.password_env = Some(values.password_env.clone());
config.network.clone_from(&values.default_network);
config.scheme.clone_from(&values.default_scheme);
config
.payment_address_type
.clone_from(&values.default_payment_address_type);
config.esplora_url.clone_from(&values.default_esplora_url);
config.ord_url.clone_from(&values.default_ord_url);
save_persisted_config(&config)?;
let wallet_result = if values.initialize_wallet {
let network_str = values
.default_network
.as_deref()
.or(cli.network.as_deref())
.unwrap_or("regtest");
let network = parse_network(network_str)?;
let scheme_str = values
.default_scheme
.as_deref()
.or(cli.scheme.as_deref())
.unwrap_or("dual");
let scheme = parse_scheme(scheme_str)?;
let payment_address_type_str = values
.default_payment_address_type
.as_deref()
.or(cli.payment_address_type.as_deref())
.unwrap_or("native");
let payment_address_type = parse_payment_address_type(payment_address_type_str)?;
let wallet_cli = Cli {
profile: Some(values.profile.clone()),
data_dir: values.data_dir.as_ref().map(std::path::PathBuf::from),
password_env: Some(values.password_env.clone()),
password_stdin: cli.password_stdin,
reveal: cli.reveal,
yes: cli.yes,
agent: cli.agent,
no_images: cli.no_images,
ascii: false,
thumb: cli.thumb_enabled(),
no_thumb: cli.no_thumb,
correlation_id: cli.correlation_id.clone(),
log_json: cli.log_json,
idempotency_key: cli.idempotency_key.clone(),
network_timeout_secs: cli.network_timeout_secs,
network_retries: cli.network_retries,
policy_mode: cli.policy_mode,
explicit_network: false,
started_at_unix_ms: cli.started_at_unix_ms,
network: values.default_network.clone(),
scheme: values.default_scheme.clone(),
payment_address_type: values.default_payment_address_type.clone(),
esplora_url: values.default_esplora_url.clone(),
ord_url: values.default_ord_url.clone(),
pulse_url: values.default_pulse_url.clone(),
pulse_api_token: cli.pulse_api_token.clone(),
command: crate::cli::Command::Doctor, password: None,
};
let password: Zeroizing<String> = if let Some(ref p) = values.password {
p.clone()
} else {
wallet_password(&wallet_cli)?
};
let words = values.words.unwrap_or(12);
if words != 12 && words != 24 {
return Err(AppError::Invalid(
"setup --words must be 12 or 24".to_string(),
));
}
let (phrase, word_count) = if let Some(ref mnemonic) = values.restore_mnemonic {
let p: String = mnemonic.trim().to_string();
if !validate_mnemonic_internal(&p) {
return Err(AppError::Invalid(
"setup restore mnemonic is invalid".to_string(),
));
}
let count = p.split_whitespace().count();
(p, count)
} else {
let result = generate_wallet_internal(words)
.map_err(|e| AppError::Internal(format!("failed to generate wallet: {e}")))?;
(result.phrase, result.words.len())
};
let encrypted = encrypt_wallet_internal(&phrase, &password)
.map_err(|e| AppError::Internal(format!("failed to encrypt mnemonic: {e}")))?;
let esplora_url = values
.default_esplora_url
.clone()
.unwrap_or_else(|| default_esplora_url(network).to_string());
let ord_url = values
.default_ord_url
.clone()
.unwrap_or_else(|| default_ord_url(network).to_string());
let pulse_url = values
.default_pulse_url
.clone()
.unwrap_or_else(|| crate::config::default_pulse_url(network).to_string());
let profile = Profile {
version: 1,
scan_policy_version: crate::config::SCAN_POLICY_VERSION_MAIN_ONLY,
network,
scheme,
payment_address_type,
account_index: 0,
esplora_url,
ord_url,
pulse_url,
bitcoin_cli: default_bitcoin_cli(),
bitcoin_cli_args: default_bitcoin_cli_args(),
encrypted_mnemonic: Some(encrypted),
mode: crate::config::ProfileModeArg::Seed,
taproot_xpub: None,
payment_xpub: None,
watch_address: None,
account_gap_limit: crate::config::default_gap_limit(),
address_scan_depth: crate::config::default_scan_depth(),
accounts: std::collections::BTreeMap::new(),
updated_at_unix: now_unix(),
pulse_session: None,
};
let profile_path = profile_path(&wallet_cli)?;
write_profile(&profile_path, &profile)?;
let display_phrase = if cli.reveal {
phrase.clone()
} else {
"<hidden; use --reveal to show>".to_string()
};
Some(json!({
"wallet_initialized": true,
"mode": if values.restore_mnemonic.is_some() { "restore" } else { "generate" },
"phrase": display_phrase,
"word_count": word_count,
}))
} else {
None
};
#[cfg(feature = "ui")]
if is_tui {
return crate::dashboard::run(cli).await.map(CommandOutput::Generic);
}
let mut wallet_initialized = false;
let mut wallet_mode = None;
let mut wallet_phrase = None;
let mut wallet_word_count = None;
if let Some(wallet_info) = wallet_result {
wallet_initialized = wallet_info["wallet_initialized"].as_bool().unwrap_or(false);
wallet_mode = wallet_info["mode"].as_str().map(|s: &str| s.to_string());
wallet_phrase = wallet_info["phrase"].as_str().map(|s: &str| s.to_string());
wallet_word_count = wallet_info["word_count"].as_u64().map(|v| v as usize);
}
Ok(CommandOutput::Setup {
config_saved: true,
wizard_used: is_tui,
profile: Some(values.profile),
data_dir: crate::wallet_service::data_dir(&crate::service_config(cli))
.display()
.to_string(),
default_network: values
.default_network
.as_deref()
.or(cli.network.as_deref())
.unwrap_or("regtest")
.to_string(),
default_scheme: values
.default_scheme
.as_deref()
.or(cli.scheme.as_deref())
.unwrap_or("dual")
.to_string(),
default_esplora_url: values
.default_esplora_url
.as_deref()
.or(cli.esplora_url.as_deref())
.unwrap_or_else(|| default_esplora_url(parse_network("regtest").unwrap()))
.to_string(),
default_ord_url: values
.default_ord_url
.as_deref()
.or(cli.ord_url.as_deref())
.unwrap_or_else(|| default_ord_url(parse_network("regtest").unwrap()))
.to_string(),
default_pulse_url: values
.default_pulse_url
.as_deref()
.or(cli.pulse_url.as_deref())
.unwrap_or_else(|| crate::config::default_pulse_url(parse_network("regtest").unwrap()))
.to_string(),
password_env: values.password_env.clone(),
wallet_requested: values.initialize_wallet,
wallet_initialized,
wallet_mode,
wallet_phrase,
wallet_word_count,
})
}