feat(tollgate): periodically sweep TollGate's router wallet into the local wallet
tollgate-wrt keeps its own separate Cashu wallet on the router (/etc/tollgate/wallet.db) — customer payments land there, never in this node's own wallet. Its Lightning auto-payout is configured independently in /etc/tollgate/identities.json, which is easy to leave misconfigured or pointed at a placeholder address (found live: both "owner" and "developer" identities on this deployment pointed at the same unconfigured tollgate@minibits.cash default). Rather than depend on getting that Lightning payout config right, tollgate_sweep::sweep_once() periodically (every 5 min, via SSH) checks the router's `tollgate wallet balance`, and if nonzero, runs `tollgate wallet drain cashu` and receives the resulting token(s) straight into the local wallet via the same path the "Receive ecash" UI uses (wallet::ecash::receive_token) — bypassing Lightning payout entirely. A no-op (Ok(0)) if no router is configured or it doesn't have TollGate installed.
This commit is contained in:
parent
9902ffd31d
commit
8f47d6608a
@ -71,6 +71,7 @@ mod state;
|
||||
mod storage_crypto;
|
||||
mod streaming;
|
||||
mod swarm;
|
||||
mod tollgate_sweep;
|
||||
mod totp;
|
||||
mod transport;
|
||||
mod trust;
|
||||
|
||||
@ -534,6 +534,28 @@ impl Server {
|
||||
});
|
||||
}
|
||||
|
||||
// Periodic TollGate ecash sweep. tollgate-wrt keeps its own separate
|
||||
// Cashu wallet on the router — customer payments never land in this
|
||||
// node's wallet on their own, and its Lightning auto-payout config is
|
||||
// independent and easy to leave misconfigured. This drains whatever
|
||||
// TollGate has collected straight into the local wallet on a timer,
|
||||
// sidestepping Lightning payout configuration entirely.
|
||||
{
|
||||
let data_dir = config.data_dir.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(300));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
match crate::tollgate_sweep::sweep_once(&data_dir).await {
|
||||
Ok(0) => {}
|
||||
Ok(swept) => info!(sats = swept, "tollgate wallet sweep complete"),
|
||||
Err(e) => debug!(error = %e, "tollgate wallet sweep (non-fatal)"),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize container scanner — discovers installed apps from Podman/Docker
|
||||
{
|
||||
let scanner = create_docker_scanner(&config).await?;
|
||||
|
||||
81
core/archipelago/src/tollgate_sweep.rs
Normal file
81
core/archipelago/src/tollgate_sweep.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use archipelago_openwrt::router::Router;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::network::router as net_router;
|
||||
use crate::wallet::ecash;
|
||||
|
||||
/// Pull whatever TollGate has collected in its on-router Cashu wallet into
|
||||
/// this node's own wallet.
|
||||
///
|
||||
/// `tollgate-wrt` keeps a completely separate Cashu wallet on the router
|
||||
/// (`/etc/tollgate/wallet.db`) — customer payments land there, never in this
|
||||
/// node's wallet directly. Its own Lightning auto-payout is configured
|
||||
/// independently in `/etc/tollgate/identities.json`, which is easy to leave
|
||||
/// pointed at the wrong (or a placeholder) address and easy to lose track of.
|
||||
/// This sweep sidesteps that entirely: periodically drain the router's
|
||||
/// TollGate wallet to Cashu tokens (`tollgate wallet drain cashu`, over SSH)
|
||||
/// and receive them straight into the local wallet via the same path the
|
||||
/// "Receive ecash" UI uses.
|
||||
///
|
||||
/// Returns the total sats swept in (0 if there was nothing to do, including
|
||||
/// when no router is configured or it doesn't have TollGate installed).
|
||||
pub async fn sweep_once(data_dir: &Path) -> Result<u64> {
|
||||
let cfg = net_router::load_router_config(data_dir).await?;
|
||||
if !cfg.configured {
|
||||
return Ok(0);
|
||||
}
|
||||
let ssh_user = cfg.username.clone().unwrap_or_else(|| "root".to_string());
|
||||
let ssh_password = cfg.password.clone().unwrap_or_default();
|
||||
|
||||
let router = Router::connect_password(&cfg.address, 22, &ssh_user, &ssh_password)
|
||||
.context("connect to router for tollgate wallet sweep")?;
|
||||
|
||||
// `tollgate` CLI missing (no TollGate installed here) is a normal,
|
||||
// expected case, not an error — just nothing to sweep.
|
||||
let (balance_out, code) = router.run("tollgate wallet balance 2>/dev/null")?;
|
||||
if code != 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
let balance_sats = balance_out
|
||||
.lines()
|
||||
.find_map(|l| l.trim().strip_prefix("balance_sats:"))
|
||||
.and_then(|v| v.trim().parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
if balance_sats == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let (drain_out, drain_code) = router.run("tollgate wallet drain cashu 2>&1")?;
|
||||
if drain_code != 0 {
|
||||
anyhow::bail!("tollgate wallet drain cashu failed: {}", drain_out.trim());
|
||||
}
|
||||
|
||||
// The CLI has no machine-readable output mode; pull tokens out of its
|
||||
// " Token: cashuB..." lines.
|
||||
let mut received_total = 0u64;
|
||||
for line in drain_out.lines() {
|
||||
let Some(token) = line.trim().strip_prefix("Token:") else {
|
||||
continue;
|
||||
};
|
||||
let token = token.trim();
|
||||
if token.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match ecash::receive_token(data_dir, token).await {
|
||||
Ok(amount) => {
|
||||
received_total += amount;
|
||||
info!(amount_sats = amount, "swept TollGate ecash into local wallet");
|
||||
}
|
||||
Err(e) => {
|
||||
// The token is still in this log line if this happens — not
|
||||
// silently lost, just needs manual `wallet.ecash-receive`.
|
||||
warn!(error = %e, token, "failed to receive swept TollGate token");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(received_total)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user