use anyhow::{Context, Result};
use bsv_sdk::primitives::PrivateKey;
use bsv_sdk::wallet::{ListOutputsArgs, WalletInterface};
use bsv_wallet_toolbox::{
services::providers::ArcConfig, Chain, Monitor, Services, ServicesOptions, StorageSqlx, Wallet,
WalletStorageWriter,
};
const TAAL_ARC_URL: &str = "https://arc.taal.com";
use std::sync::Arc;
use crate::cli::Cli;
use crate::server::{self, ServerConfig, TlsConfig};
pub async fn run(cli: &Cli) -> Result<()> {
let root_key_hex =
std::env::var("ROOT_KEY").context("ROOT_KEY not set. Run `bsv-wallet init` first.")?;
let root_key = PrivateKey::from_hex(&root_key_hex)?;
let chain = if cli.testnet {
Chain::Test
} else {
Chain::Main
};
let storage = StorageSqlx::open(&cli.db).await?;
storage.make_available().await?;
sqlx::query("PRAGMA busy_timeout = 500")
.execute(storage.pool())
.await
.ok();
let make_services = |chain: Chain| -> anyhow::Result<Services> {
let mut opts = match chain {
Chain::Main => ServicesOptions::mainnet(),
Chain::Test => ServicesOptions::testnet(),
};
if let Ok(url) = std::env::var("CHAINTRACKS_URL") {
opts = opts.with_chaintracks_url(url);
}
if let Ok(api_key) = std::env::var("MAIN_TAAL_API_KEY") {
if !api_key.is_empty() {
let mut headers = std::collections::HashMap::new();
headers.insert("Authorization".to_string(), api_key);
opts = opts.with_arc(
TAAL_ARC_URL,
Some(ArcConfig {
headers: Some(headers),
..Default::default()
}),
);
}
}
Ok(Services::with_options(chain, opts)?)
};
let services = make_services(chain)?;
if let Some(ref ct) = services.chaintracks {
storage.set_chain_tracker(ct.clone()).await;
}
let storage_arc = Arc::new(storage);
let services_arc = Arc::new(services);
let monitor = Monitor::new(storage_arc.clone(), services_arc.clone());
monitor
.start()
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;
eprintln!("Monitor started");
let storage2 = StorageSqlx::open(&cli.db).await?;
storage2.make_available().await?;
sqlx::query("PRAGMA busy_timeout = 500")
.execute(storage2.pool())
.await
.ok();
let services2 = make_services(chain)?;
if let Some(ref ct) = services2.chaintracks {
storage2.set_chain_tracker(ct.clone()).await;
}
let wallet = Wallet::new(Some(root_key), storage2, services2)
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;
let wallet_state = server::make_wallet_state(wallet);
let tls = match (
std::env::var("TLS_CERT_PATH").ok(),
std::env::var("TLS_KEY_PATH").ok(),
) {
(Some(cert_path), Some(key_path)) => Some(TlsConfig {
cert_path,
key_path,
}),
_ => None,
};
let config = ServerConfig {
auth_token: std::env::var("AUTH_TOKEN").ok(),
tls,
};
let min_utxos: usize = std::env::var("MIN_UTXOS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(3);
let check_wallet = wallet_state.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));
loop {
interval.tick().await;
match check_wallet
.list_outputs(
ListOutputsArgs {
basket: "default".to_string(),
tags: None,
tag_query_mode: None,
include: None,
include_custom_instructions: None,
include_tags: None,
include_labels: None,
limit: None,
offset: None,
seek_permission: None,
},
"bsv-wallet-daemon",
)
.await
{
Ok(result) => {
let count = result.outputs.len();
if count < min_utxos {
tracing::warn!(
utxo_count = count,
min_utxos = min_utxos,
"Low UTXO count! Run `bsv-wallet split --count {}` to create more.",
min_utxos
);
}
}
Err(e) => {
tracing::debug!("UTXO check failed: {}", e);
}
}
}
});
let server_handle = tokio::spawn(server::run(wallet_state, cli.port, config));
tokio::signal::ctrl_c().await?;
eprintln!("\nStopping monitor...");
monitor.stop().await.map_err(|e| anyhow::anyhow!("{}", e))?;
eprintln!("Monitor stopped");
let _ = server_handle.await;
Ok(())
}