refactor(withdraw): migrate extension to Vue
This commit is contained in:
@@ -1,201 +1,148 @@
|
||||
import uuid
|
||||
import json
|
||||
import requests
|
||||
|
||||
from flask import jsonify, request, url_for
|
||||
from lnurl import LnurlWithdrawResponse, encode as lnurl_encode
|
||||
from datetime import datetime
|
||||
from flask import g, jsonify, request
|
||||
|
||||
from lnbits.core.crud import get_user, get_wallet
|
||||
from lnbits.core.services import pay_invoice
|
||||
from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request
|
||||
from lnbits.helpers import urlsafe_short_hash, Status
|
||||
|
||||
from lnbits.db import open_ext_db, open_db
|
||||
from lnbits.extensions.withdraw import withdraw_ext
|
||||
from .crud import (
|
||||
create_withdraw_link,
|
||||
get_withdraw_link,
|
||||
get_withdraw_link_by_hash,
|
||||
get_withdraw_links,
|
||||
update_withdraw_link,
|
||||
delete_withdraw_link,
|
||||
)
|
||||
|
||||
|
||||
@withdraw_ext.route("/api/v1/lnurlencode/<urlstr>/<parstr>", methods=["GET"])
|
||||
def api_lnurlencode(urlstr, parstr):
|
||||
"""Returns encoded LNURL if web url and parameter gieven."""
|
||||
@withdraw_ext.route("/api/v1/links", methods=["GET"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_links():
|
||||
wallet_ids = [g.wallet.id]
|
||||
|
||||
if not urlstr:
|
||||
return jsonify({"status": "FALSE"}), 200
|
||||
if "all_wallets" in request.args:
|
||||
wallet_ids = get_user(g.wallet.user).wallet_ids
|
||||
|
||||
with open_ext_db("withdraw") as withdraw_ext_db:
|
||||
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
|
||||
randar = user_fau[0][15].split(",")
|
||||
print(randar)
|
||||
# randar = randar[:-1]
|
||||
# If "Unique links" selected get correct rand, if not there is only one rand
|
||||
if user_fau[0][12] > 0:
|
||||
rand = randar[user_fau[0][10] - 1]
|
||||
else:
|
||||
rand = randar[0]
|
||||
|
||||
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=urlstr, parstr=parstr, rand=rand)
|
||||
|
||||
if "onion" in url:
|
||||
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url)}), 200
|
||||
print(url)
|
||||
|
||||
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url.replace("http://", "https://"))}), 200
|
||||
|
||||
return jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)]), Status.OK
|
||||
|
||||
|
||||
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["GET"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_link_retrieve(link_id):
|
||||
link = get_withdraw_link(link_id)
|
||||
|
||||
@withdraw_ext.route("/api/v1/lnurlfetch/<urlstr>/<parstr>/<rand>", methods=["GET"])
|
||||
def api_lnurlfetch(parstr, urlstr, rand):
|
||||
"""Returns LNURL json."""
|
||||
if not link:
|
||||
return jsonify({"message": "Withdraw link does not exist."}), Status.NOT_FOUND
|
||||
|
||||
if not parstr:
|
||||
return jsonify({"status": "FALSE", "ERROR": "NO WALL ID"}), 200
|
||||
if link.wallet != g.wallet.id:
|
||||
return jsonify({"message": "Not your withdraw link."}), Status.FORBIDDEN
|
||||
|
||||
if not urlstr:
|
||||
return jsonify({"status": "FALSE", "ERROR": "NO URL"}), 200
|
||||
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), Status.OK
|
||||
|
||||
with open_ext_db("withdraw") as withdraw_ext_db:
|
||||
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
|
||||
k1str = uuid.uuid4().hex
|
||||
withdraw_ext_db.execute("UPDATE withdraws SET withdrawals = ? WHERE uni = ?", (k1str, parstr,))
|
||||
|
||||
precallback = url_for("withdraw.api_lnurlwithdraw", _external=True, rand=rand)
|
||||
|
||||
if "onion" in precallback:
|
||||
print(precallback)
|
||||
|
||||
@withdraw_ext.route("/api/v1/links", methods=["POST"])
|
||||
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["PUT"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
@api_validate_post_request(
|
||||
schema={
|
||||
"title": {"type": "string", "empty": False, "required": True},
|
||||
"min_withdrawable": {"type": "integer", "min": 1, "required": True},
|
||||
"max_withdrawable": {"type": "integer", "min": 1, "required": True},
|
||||
"uses": {"type": "integer", "min": 1, "required": True},
|
||||
"wait_time": {"type": "integer", "min": 1, "required": True},
|
||||
"is_unique": {"type": "boolean", "required": True},
|
||||
}
|
||||
)
|
||||
def api_link_create(link_id=None):
|
||||
if g.data["max_withdrawable"] < g.data["min_withdrawable"]:
|
||||
return jsonify({"message": "`max_withdrawable` needs to be at least `min_withdrawable`."}), Status.BAD_REQUEST
|
||||
|
||||
if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat:
|
||||
return jsonify({"message": "Insufficient balance."}), Status.FORBIDDEN
|
||||
|
||||
if link_id:
|
||||
link = get_withdraw_link(link_id)
|
||||
|
||||
if not link:
|
||||
return jsonify({"message": "Withdraw link does not exist."}), Status.NOT_FOUND
|
||||
|
||||
if link.wallet != g.wallet.id:
|
||||
return jsonify({"message": "Not your withdraw link."}), Status.FORBIDDEN
|
||||
|
||||
link = update_withdraw_link(link_id, **g.data)
|
||||
else:
|
||||
precallback = url_for("withdraw.api_lnurlwithdraw", _external=True, rand=rand).replace("http://", "https://")
|
||||
|
||||
res = LnurlWithdrawResponse(
|
||||
callback=precallback,
|
||||
k1=k1str,
|
||||
min_withdrawable=user_fau[0][8] * 1000,
|
||||
max_withdrawable=user_fau[0][7] * 1000,
|
||||
default_description="LNbits LNURL withdraw",
|
||||
)
|
||||
link = create_withdraw_link(wallet_id=g.wallet.id, **g.data)
|
||||
|
||||
return res.json(), 200
|
||||
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), Status.OK if link_id else Status.CREATED
|
||||
|
||||
|
||||
@withdraw_ext.route("/api/v1/lnurlwithdraw/<rand>/", methods=["GET"])
|
||||
def api_lnurlwithdraw(rand):
|
||||
"""Pays invoice if passed k1 invoice and rand."""
|
||||
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
|
||||
@api_check_wallet_macaroon(key_type="invoice")
|
||||
def api_link_delete(link_id):
|
||||
link = get_withdraw_link(link_id)
|
||||
|
||||
k1 = request.args.get("k1")
|
||||
pr = request.args.get("pr")
|
||||
if not link:
|
||||
return jsonify({"message": "Withdraw link does not exist."}), Status.NOT_FOUND
|
||||
|
||||
if not k1:
|
||||
return jsonify({"status": "FALSE", "ERROR": "NO k1"}), 200
|
||||
if link.wallet != g.wallet.id:
|
||||
return jsonify({"message": "Not your withdraw link."}), Status.FORBIDDEN
|
||||
|
||||
if not pr:
|
||||
return jsonify({"status": "FALSE", "ERROR": "NO PR"}), 200
|
||||
delete_withdraw_link(link_id)
|
||||
|
||||
with open_ext_db("withdraw") as withdraw_ext_db:
|
||||
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
|
||||
|
||||
if not user_fau:
|
||||
return jsonify({"status": "ERROR", "reason": "NO AUTH"}), 400
|
||||
|
||||
if user_fau[0][10] < 1:
|
||||
return jsonify({"status": "ERROR", "reason": "withdraw SPENT"}), 400
|
||||
|
||||
# Check withdraw time
|
||||
dt = datetime.now()
|
||||
seconds = dt.timestamp()
|
||||
secspast = seconds - user_fau[0][14]
|
||||
|
||||
if secspast < user_fau[0][11]:
|
||||
return jsonify({"status": "ERROR", "reason": "WAIT " + str(int(user_fau[0][11] - secspast)) + "s"}), 400
|
||||
|
||||
randar = user_fau[0][15].split(",")
|
||||
if rand not in randar:
|
||||
print("huhhh")
|
||||
return jsonify({"status": "ERROR", "reason": "BAD AUTH"}), 400
|
||||
if len(randar) > 2:
|
||||
randar.remove(rand)
|
||||
randstr = ",".join(randar)
|
||||
|
||||
# Update time and increments
|
||||
upinc = int(user_fau[0][10]) - 1
|
||||
withdraw_ext_db.execute(
|
||||
"UPDATE withdraws SET inc = ?, rand = ?, tmestmp = ? WHERE withdrawals = ?", (upinc, randstr, seconds, k1,)
|
||||
)
|
||||
|
||||
header = {"Content-Type": "application/json", "Grpc-Metadata-macaroon": str(user_fau[0][4])}
|
||||
data = {"payment_request": pr}
|
||||
# this works locally but not being served over host, bug, needs fixing
|
||||
# r = requests.post(url="https://lnbits.com/api/v1/channels/transactions", headers=header, data=json.dumps(data))
|
||||
r = requests.post(url=url_for("api_transactions", _external=True), headers=header, data=json.dumps(data))
|
||||
r_json = r.json()
|
||||
|
||||
if "ERROR" in r_json:
|
||||
return jsonify({"status": "ERROR", "reason": r_json["ERROR"]}), 400
|
||||
|
||||
with open_ext_db("withdraw") as withdraw_ext_db:
|
||||
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
|
||||
|
||||
return jsonify({"status": "OK"}), 200
|
||||
|
||||
@withdraw_ext.route("/api/v1/lnurlmaker", methods=["GET","POST"])
|
||||
def api_lnurlmaker():
|
||||
|
||||
if request.headers["Content-Type"] != "application/json":
|
||||
return jsonify({"ERROR": "MUST BE JSON"}), 400
|
||||
|
||||
with open_db() as db:
|
||||
wallet = db.fetchall(
|
||||
"SELECT * FROM wallets WHERE adminkey = ?",
|
||||
(request.headers["Grpc-Metadata-macaroon"],),
|
||||
)
|
||||
if not wallet:
|
||||
return jsonify({"ERROR": "NO KEY"}), 200
|
||||
|
||||
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet[0][0],))[0]
|
||||
print(balance)
|
||||
|
||||
postedjson = request.json
|
||||
print(postedjson["amount"])
|
||||
|
||||
if balance < int(postedjson["amount"]):
|
||||
return jsonify({"ERROR": "NOT ENOUGH FUNDS"}), 200
|
||||
|
||||
uni = uuid.uuid4().hex
|
||||
rand = uuid.uuid4().hex[0:5]
|
||||
|
||||
with open_ext_db("withdraw") as withdraw_ext_db:
|
||||
withdraw_ext_db.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO withdraws
|
||||
(usr, wal, walnme, adm, uni, tit, maxamt, minamt, spent, inc, tme, uniq, withdrawals, tmestmp, rand)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
wallet[0][2],
|
||||
wallet[0][0],
|
||||
wallet[0][1],
|
||||
wallet[0][3],
|
||||
uni,
|
||||
postedjson["memo"],
|
||||
postedjson["amount"],
|
||||
postedjson["amount"],
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
rand,
|
||||
),
|
||||
)
|
||||
|
||||
user_fau = withdraw_ext_db.fetchone("SELECT * FROM withdraws WHERE uni = ?", (uni,))
|
||||
|
||||
if not user_fau:
|
||||
return jsonify({"ERROR": "WITHDRAW NOT MADE"}), 401
|
||||
|
||||
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=request.host, parstr=uni, rand=rand)
|
||||
|
||||
if "onion" in url:
|
||||
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url)}), 200
|
||||
print(url)
|
||||
|
||||
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url.replace("http://", "https://"))}), 200
|
||||
|
||||
return "", Status.NO_CONTENT
|
||||
|
||||
|
||||
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
|
||||
def api_lnurl_response(unique_hash):
|
||||
link = get_withdraw_link_by_hash(unique_hash)
|
||||
|
||||
if not link:
|
||||
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), Status.OK
|
||||
|
||||
link = update_withdraw_link(link.id, k1=urlsafe_short_hash())
|
||||
|
||||
return jsonify(link.lnurl_response.dict()), Status.OK
|
||||
|
||||
|
||||
@withdraw_ext.route("/api/v1/lnurl/cb/<unique_hash>", methods=["GET"])
|
||||
def api_lnurl_callback(unique_hash):
|
||||
link = get_withdraw_link_by_hash(unique_hash)
|
||||
k1 = request.args.get("k1", type=str)
|
||||
payment_request = request.args.get("pr", type=str)
|
||||
now = int(datetime.now().timestamp())
|
||||
|
||||
if not link:
|
||||
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), Status.OK
|
||||
|
||||
if link.is_spent:
|
||||
return jsonify({"status": "ERROR", "reason": "Withdraw is spent."}), Status.OK
|
||||
|
||||
if link.k1 != k1:
|
||||
return jsonify({"status": "ERROR", "reason": "Bad request."}), Status.OK
|
||||
|
||||
if now < link.open_time:
|
||||
return jsonify({"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}), Status.OK
|
||||
|
||||
try:
|
||||
pay_invoice(wallet=get_wallet(link.wallet), bolt11=payment_request, max_sat=link.max_withdrawable)
|
||||
|
||||
changes = {
|
||||
"used": link.used + 1,
|
||||
"open_time": link.wait_time + now,
|
||||
}
|
||||
|
||||
if link.is_unique:
|
||||
changes["unique_hash"] = urlsafe_short_hash()
|
||||
|
||||
update_withdraw_link(link.id, **changes)
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({"status": "ERROR", "reason": str(e)}), Status.OK
|
||||
except PermissionError:
|
||||
return jsonify({"status": "ERROR", "reason": "Withdraw link is empty."}), Status.OK
|
||||
except Exception as e:
|
||||
return jsonify({"status": "ERROR", "reason": str(e)}), Status.OK
|
||||
|
||||
return jsonify({"status": "OK"}), Status.OK
|
||||
|
||||
Reference in New Issue
Block a user