Wallet system (sats ledger + deposit + spend), HydraProxy fulfillment queue, paid IP-quality scorer (85/100 verified)

This commit is contained in:
drjones
2026-09-20 18:51:47 -07:00
parent 16dde93ce2
commit a7d6b98183
8 changed files with 481 additions and 12 deletions

173
app.py
View File

@@ -14,12 +14,14 @@ from flask import (Flask, abort, flash, jsonify, redirect, render_template,
import db
import iproyal
import hydraproxy
import quality
CFG = db.CFG
app = Flask(__name__)
app.secret_key = CFG["secret_key"]
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # mitigate CSRF on modern browsers
app.config["SESSION_COOKIE_HTTPONLY"] = True # block JS session theft (XSS)
db.init_db() # ensure schema + migrations on gunicorn start
# simple SQLite-backed rate limiter (shared across gunicorn workers)
def _client_ip():
@@ -196,20 +198,24 @@ def plans():
@app.route("/buy/<int:plan_id>", methods=["POST"])
@login_required
def buy(plan_id):
u = current_user()
d = db.get_db()
plan = d.execute("SELECT * FROM plans WHERE id=?", (plan_id,)).fetchone()
d.close()
if not plan:
abort(404)
order_id = db.random_token(12)
price = plan["price_sats"]
if db.get_balance_sats(u["id"]) < price:
flash("Insufficient wallet balance. Deposit first.", "warn")
return redirect(url_for("wallet"))
db.add_transaction(u["id"], -price, "gb", f"plan_{plan_id}")
d = db.get_db()
d.execute("INSERT INTO orders (user_id,plan_id,invoice_id,amount_sats,status,created_at) "
"VALUES (?,?,?,?,'pending',?)",
(current_user()["id"], plan_id, order_id, plan["price_sats"], int(time.time())))
d.execute("UPDATE users SET balance_gb = balance_gb + ?, active=1 WHERE id=?",
(plan["gb"], u["id"]))
d.commit()
d.close()
inv = create_invoice(plan["price_sats"], order_id)
return redirect(inv["checkoutLink"])
flash(f"Added {plan['gb']} GB. Happy scraping.", "ok")
return redirect(url_for("dashboard"))
@app.route("/order/<order_id>")
@@ -235,6 +241,20 @@ def webhook_btcpay():
kind = (payload.get("metadata") or {}).get("kind")
if not order_id:
return "ok"
if kind == "deposit":
uid = (payload.get("metadata") or {}).get("user_id")
amt = (payload.get("metadata") or {}).get("amount_sats")
d = db.get_db()
already = d.execute("SELECT 1 FROM transactions WHERE ref=? AND type='deposit'",
(order_id,)).fetchone()
if not already and uid and amt:
d.execute("UPDATE users SET balance_sats = balance_sats + ? WHERE id=?",
(int(amt), uid))
d.execute("INSERT INTO transactions (user_id, amount_sats, type, ref, ts) "
"VALUES (?,?,?,?,?)", (uid, int(amt), "deposit", order_id, int(time.time())))
d.commit()
d.close()
return "ok"
d = db.get_db()
if kind == "dedicated":
row = d.execute("SELECT * FROM dedicated_ips WHERE invoice_id=? AND status='pending_payment'",
@@ -265,6 +285,31 @@ def webhook_btcpay():
return "ok"
@app.route("/wallet")
@login_required
def wallet():
u = current_user()
bal = db.get_balance_sats(u["id"])
txs = db.get_transactions(u["id"])
return render_template("wallet.html", user=u, balance=bal, txs=txs)
@app.route("/wallet/deposit", methods=["POST"])
@login_required
def wallet_deposit():
u = current_user()
try:
sats = int(request.form.get("sats", 0))
except ValueError:
sats = 0
if sats < 500:
flash("Minimum deposit is 500 sats.", "warn")
return redirect(url_for("wallet"))
ref = db.random_token(12)
inv = create_invoice(sats, ref, meta={"kind": "deposit", "user_id": u["id"], "amount_sats": sats})
return redirect(inv["checkoutLink"])
@app.route("/dashboard")
@login_required
def dashboard():
@@ -277,11 +322,15 @@ def dashboard():
"WHERE o.user_id=? ORDER BY o.id DESC LIMIT 20", (u["id"],)).fetchall()]
sessions = [dict(r) for r in d.execute(
"SELECT * FROM usage_log WHERE user_id=? ORDER BY id DESC LIMIT 10", (u["id"],)).fetchall()]
hydra = [dict(r) for r in d.execute(
"SELECT * FROM hydraproxy_orders WHERE user_id=? ORDER BY id DESC", (u["id"],)).fetchall()]
d.close()
bal = db.get_balance_sats(u["id"])
socks = f"socks5://{u['username']}:{u['proxy_password_plain']}@{PROXY['host']}:{PROXY['socks_port']}"
http = f"http://{u['username']}:{u['proxy_password_plain']}@{PROXY['host']}:{PROXY['http_port']}"
return render_template("dashboard.html", user=u, used_gb=used / 1e9,
socks=socks, http=http, dedicated=deds, orders=orders, sessions=sessions)
socks=socks, http=http, dedicated=deds, orders=orders, sessions=sessions,
hydra=hydra, balance=bal)
@app.route("/dashboard/test")
@@ -410,16 +459,39 @@ def dedicated_buy():
flash("Pick a location.", "warn")
return redirect(url_for("dedicated"))
_, price = DEDICATED_PRODUCTS[pid]["plans"][days]
u = current_user()
if db.get_balance_sats(u["id"]) < price:
flash("Insufficient wallet balance. Deposit first.", "warn")
return redirect(url_for("wallet"))
oid = db.random_token(12)
d = db.get_db()
d.execute("INSERT INTO dedicated_ips (user_id,product_id,product_name,plan_days,location_id,"
"location_name,invoice_id,price_sats,status,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)",
(current_user()["id"], pid, DEDICATED_PRODUCTS[pid]["name"], days, loc_id, loc_name,
(u["id"], pid, DEDICATED_PRODUCTS[pid]["name"], days, loc_id, loc_name,
oid, price, "pending_payment", int(time.time())))
d.commit()
d.close()
inv = create_invoice(price, oid, meta={"kind": "dedicated"})
return redirect(inv["checkoutLink"])
db.add_transaction(u["id"], -price, "dedicated", oid)
# place the IPRoyal order directly (no webhook needed in wallet model)
try:
plan_id = DEDICATED_PRODUCTS[pid]["plans"][days][0]
o = iproyal.create_order(pid, plan_id, loc_id, 1)
oid2 = o.get("id") or o.get("order_id") or (o.get("data") or {}).get("id")
d = db.get_db()
d.execute("UPDATE dedicated_ips SET status='provisioning', iproyal_order_id=? WHERE invoice_id=?",
(oid2, oid))
d.commit()
d.close()
flash("Dedicated IP ordered — provisioning now.", "ok")
except Exception as e:
app.logger.error("dedicated order failed: %s", e)
d = db.get_db()
d.execute("UPDATE dedicated_ips SET status='failed' WHERE invoice_id=?", (oid,))
d.commit()
d.close()
db.add_transaction(u["id"], price, "dedicated_refund", oid)
flash("Dedicated order failed — wallet refunded.", "warn")
return redirect(url_for("dashboard"))
@app.route("/dedicated/poll", methods=["POST"])
@@ -504,6 +576,53 @@ def api_hydraproxy_info():
return jsonify({"error": str(e)}), 502
@app.route("/hydraproxy/order", methods=["POST"])
@login_required
def hydraproxy_order():
u = current_user()
product = request.form.get("product", "").strip()
location = request.form.get("location", "").strip()
if product not in HYDRA_PRODUCTS:
flash("Bad product.", "warn")
return redirect(url_for("hydraproxy_page"))
price = HYDRA_PRODUCTS[product]["price_sats"]
if db.get_balance_sats(u["id"]) < price:
flash("Insufficient wallet balance. Deposit first.", "warn")
return redirect(url_for("wallet"))
d = db.get_db()
d.execute("INSERT INTO hydraproxy_orders (user_id, product, location, price_sats, status, created_at) "
"VALUES (?,?,?,?,'pending_fulfillment',?)",
(u["id"], product, location, price, int(time.time())))
d.commit()
d.close()
db.add_transaction(u["id"], -price, "hydraproxy", product)
flash("Order placed — our broker fulfills it shortly and delivers your proxy to the dashboard.", "ok")
return redirect(url_for("dashboard"))
@app.route("/quality")
@login_required
def quality_page():
return render_template("quality.html", user=current_user(),
price=CFG.get("quality_price_sats", 220))
@app.route("/quality/run", methods=["POST"])
@login_required
def quality_run():
u = current_user()
price = CFG.get("quality_price_sats", 220)
if db.get_balance_sats(u["id"]) < price:
return jsonify({"error": "Insufficient balance. Deposit first."}), 402
proxy = request.form.get("proxy", "").strip()
if not proxy:
proxy = f"socks5h://{u['username']}:{u['proxy_password_plain']}@127.0.0.1:{PROXY['socks_port']}"
db.add_transaction(u["id"], -price, "quality", "ip_check")
result = quality.score_proxy(proxy)
result["charged_sats"] = price
return jsonify(result)
# ---------- admin ----------
ADMIN_PASSWORD = CFG.get("admin_password", "")
@@ -530,6 +649,7 @@ def admin():
"FROM users ORDER BY id DESC LIMIT 50")]
orders = [dict(r) for r in d.execute("SELECT * FROM orders ORDER BY id DESC LIMIT 20")]
deds = [dict(r) for r in d.execute("SELECT * FROM dedicated_ips ORDER BY id DESC LIMIT 30")]
hydra_orders = [dict(r) for r in d.execute("SELECT * FROM hydraproxy_orders ORDER BY id DESC LIMIT 30")]
d.close()
try:
ipr_balance = iproyal.get_balance()
@@ -544,7 +664,7 @@ def admin():
ipr_balance < CFG.get("alerts", {}).get("low_balance_threshold", 10.0)
return render_template("admin.html", stats=stats, users=users, orders=orders,
dedicated=deds, ipr_balance=ipr_balance, low_balance=low_balance,
hydra_balance=hydra_balance)
hydra_balance=hydra_balance, hydra_orders=hydra_orders)
@app.route("/admin/credit", methods=["POST"])
@@ -564,6 +684,37 @@ def admin_credit():
return redirect(url_for("admin"))
@app.route("/admin/hydraproxy/fulfill", methods=["POST"])
def admin_hydraproxy_fulfill():
if not session.get("admin"):
abort(403)
oid = request.form.get("order_id", "").strip()
hydra_order_id = request.form.get("hydra_order_id", "").strip()
if not oid or not hydra_order_id:
flash("Need our order id and the HydraProxy order id.", "warn")
return redirect(url_for("admin"))
try:
details = hydraproxy.proxy_details(hydra_order_id)
pinfo = details.get("proxy_info", {})
proxy = details.get("proxy", {})
host = proxy.get("hostname") or proxy.get("server_ip")
port = proxy.get("port")
if isinstance(port, list):
port = port[0]
user = pinfo.get("username", "")
pw = pinfo.get("password", "")
d = db.get_db()
d.execute("UPDATE hydraproxy_orders SET status='active', hydra_order_id=?, proxy_host=?, "
"proxy_port=?, proxy_user=?, proxy_pass=?, fulfilled_at=? WHERE id=?",
(hydra_order_id, host, str(port), user, pw, int(time.time()), oid))
d.commit()
d.close()
flash(f"Fulfilled order {oid} with HydraProxy order {hydra_order_id}.", "ok")
except Exception as e:
flash(f"Fulfill failed: {e}", "warn")
return redirect(url_for("admin"))
# ---------- agent discovery ----------
@app.route("/llms.txt")
def llms_txt():