Add balance monitor, low-balance admin banner, dry-run auto-topup (sweep) framework
This commit is contained in:
4
app.py
4
app.py
@@ -487,8 +487,10 @@ def admin():
|
|||||||
ipr_balance = iproyal.get_balance()
|
ipr_balance = iproyal.get_balance()
|
||||||
except Exception:
|
except Exception:
|
||||||
ipr_balance = "unavailable"
|
ipr_balance = "unavailable"
|
||||||
|
low_balance = isinstance(ipr_balance, (int, float)) and \
|
||||||
|
ipr_balance < CFG.get("alerts", {}).get("low_balance_threshold", 10.0)
|
||||||
return render_template("admin.html", stats=stats, users=users, orders=orders,
|
return render_template("admin.html", stats=stats, users=users, orders=orders,
|
||||||
dedicated=deds, ipr_balance=ipr_balance)
|
dedicated=deds, ipr_balance=ipr_balance, low_balance=low_balance)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin/credit", methods=["POST"])
|
@app.route("/admin/credit", methods=["POST"])
|
||||||
|
|||||||
21
monitor.py
Normal file
21
monitor.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Monitor IPRoyal reseller balance. Prints an ALERT line (for cron -> Telegram)
|
||||||
|
when below threshold. Exit 0 = OK, 1 = low/failed."""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import iproyal
|
||||||
|
|
||||||
|
threshold = iproyal.CFG.get("alerts", {}).get("low_balance_threshold", 10.0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
bal = iproyal.get_balance()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ALERT: IPRoyal balance check FAILED: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if isinstance(bal, (int, float)) and bal < threshold:
|
||||||
|
print(f"ALERT: IPRoyal reseller balance ${bal:.2f} is below ${threshold:.2f}. Top up now.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"OK: IPRoyal balance ${bal}")
|
||||||
|
sys.exit(0)
|
||||||
52
sweep.py
Normal file
52
sweep.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Auto-top-up (sweep): send BTC to IPRoyal when reseller balance is low.
|
||||||
|
|
||||||
|
SAFETY: DRY-RUN by default — prints what it *would* do, never moves money.
|
||||||
|
To go live you must:
|
||||||
|
1. set sweep.iproyal_btc_address in config.json (IPRoyal's BTC deposit address),
|
||||||
|
2. wire the send() function to your source wallet (BTCPay hot wallet / Alby),
|
||||||
|
3. pass --live, with a hard cap via --cap-sats.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import iproyal
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--live", action="store_true", help="actually send (default: dry-run)")
|
||||||
|
ap.add_argument("--cap-sats", type=int, default=0, help="hard cap on sats to send")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
cfg = iproyal.CFG.get("sweep", {})
|
||||||
|
threshold = iproyal.CFG.get("alerts", {}).get("low_balance_threshold", 10.0)
|
||||||
|
max_sats = args.cap_sats or int(cfg.get("max_sats", 100000))
|
||||||
|
dest = cfg.get("iproyal_btc_address", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
bal = iproyal.get_balance()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"balance check failed: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if bal >= threshold:
|
||||||
|
print(f"balance ${bal} >= threshold ${threshold} — no top-up needed")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not dest:
|
||||||
|
print("NO destination set (sweep.iproyal_btc_address). "
|
||||||
|
"Set it in config.json before going live.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.live:
|
||||||
|
print(f"LIVE: balance ${bal} < ${threshold} — would send {max_sats} sats to {dest}")
|
||||||
|
# TODO: wire send() to source wallet (BTCPay hot wallet / Alby on-chain).
|
||||||
|
# Hard cap enforced here: never exceed max_sats.
|
||||||
|
print("WARNING: send() not yet wired — this is a no-op placeholder.")
|
||||||
|
else:
|
||||||
|
print(f"DRY-RUN: balance ${bal} < ${threshold} — would top up {max_sats} sats to {dest}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h2>Admin</h2>
|
<h2>Admin</h2>
|
||||||
|
{% if low_balance %}
|
||||||
|
<div class="flash warn" style="font-size:15px;border-color:var(--danger);color:var(--danger);background:rgba(255,77,109,.08)">
|
||||||
|
⚠️ IPRoyal balance LOW (${{ ipr_balance }}) — top up now or dedicated orders will fail.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<div class="card stat"><span class="lbl">Revenue (paid)</span><span class="val">{{ stats.revenue_sats }} sats</span></div>
|
<div class="card stat"><span class="lbl">Revenue (paid)</span><span class="val">{{ stats.revenue_sats }} sats</span></div>
|
||||||
<div class="card stat"><span class="lbl">IPRoyal balance</span><span class="val">${{ ipr_balance }}</span></div>
|
<div class="card stat"><span class="lbl">IPRoyal balance</span><span class="val">${{ ipr_balance }}</span></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user