2019-12-14 00:34:19 +01:00
|
|
|
import os
|
2019-12-13 17:56:19 +01:00
|
|
|
import lnurl
|
|
|
|
import requests
|
2019-12-13 22:51:41 +01:00
|
|
|
|
|
|
|
from flask import Flask, jsonify, render_template, request
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
from . import bolt11
|
2019-12-13 23:41:39 +01:00
|
|
|
from .db import Database
|
2019-12-13 17:59:40 +01:00
|
|
|
from .helpers import encrypt
|
2019-12-14 10:13:27 +01:00
|
|
|
from .settings import DATABASE_PATH, LNBITS_PATH, WALLET
|
2019-12-13 17:59:40 +01:00
|
|
|
|
|
|
|
|
2019-12-13 17:56:19 +01:00
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
2019-12-13 23:41:39 +01:00
|
|
|
def db_connect(db_path=DATABASE_PATH):
|
2019-12-13 22:51:41 +01:00
|
|
|
import sqlite3
|
2019-12-13 23:41:39 +01:00
|
|
|
|
2019-12-13 17:56:19 +01:00
|
|
|
con = sqlite3.connect(db_path)
|
|
|
|
return con
|
|
|
|
|
|
|
|
|
2019-12-14 00:34:19 +01:00
|
|
|
@app.before_first_request
|
|
|
|
def init():
|
|
|
|
with Database() as db:
|
|
|
|
with open(os.path.join(LNBITS_PATH, "data/schema.sql")) as schemafile:
|
|
|
|
for stmt in schemafile.read().split("\n\n"):
|
|
|
|
db.execute(stmt, [])
|
|
|
|
|
|
|
|
|
2019-12-13 17:56:19 +01:00
|
|
|
@app.route("/")
|
|
|
|
def home():
|
|
|
|
return render_template("index.html")
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/deletewallet")
|
|
|
|
def deletewallet():
|
|
|
|
thewal = request.args.get("wal")
|
|
|
|
|
2019-12-13 18:14:25 +01:00
|
|
|
with Database() as db:
|
2019-12-14 04:11:41 +01:00
|
|
|
wallet_row = db.fetchone("SELECT * FROM wallets WHERE hash = ?", (thewal,))
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
if not wallet_row:
|
|
|
|
return render_template("index.html")
|
|
|
|
|
|
|
|
db.execute("UPDATE wallets SET user = ? WHERE hash = ?", (f"del{wallet_row[4]}", wallet_row[0]))
|
|
|
|
db.execute("UPDATE wallets SET adminkey = ? WHERE hash = ?", (f"del{wallet_row[5]}", wallet_row[0]))
|
|
|
|
db.execute("UPDATE wallets SET inkey = ? WHERE hash = ?", (f"del{wallet_row[6]}", wallet_row[0]))
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (wallet_row[4],))
|
|
|
|
if user_wallets:
|
|
|
|
return render_template("deletewallet.html", theid=user_wallets[0][4], thewal=user_wallets[0][0])
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 18:14:25 +01:00
|
|
|
return render_template("index.html")
|
2019-12-13 17:56:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/lnurlwallet")
|
|
|
|
def lnurlwallet():
|
|
|
|
|
|
|
|
# put in a function
|
|
|
|
thestr = request.args.get("lightning")
|
|
|
|
lnurll = lnurl.decode(thestr)
|
|
|
|
r = requests.get(url=lnurll)
|
|
|
|
|
|
|
|
data = r.json()
|
|
|
|
|
|
|
|
callback = data["callback"]
|
|
|
|
maxwithdraw = data["maxWithdrawable"]
|
|
|
|
withdraw = int(maxwithdraw / 1000)
|
|
|
|
k1 = data["k1"]
|
|
|
|
|
|
|
|
# get invoice
|
2019-12-14 10:13:27 +01:00
|
|
|
rr = WALLET.create_invoice(withdraw)
|
2019-12-13 17:56:19 +01:00
|
|
|
dataa = rr.json()
|
|
|
|
|
|
|
|
# get callback
|
|
|
|
|
|
|
|
pay_req = dataa["pay_req"]
|
|
|
|
payment_hash = dataa["payment_hash"]
|
|
|
|
|
|
|
|
invurl = callback + "&k1=" + k1 + "&pr=" + pay_req
|
|
|
|
|
|
|
|
rrr = requests.get(url=invurl)
|
|
|
|
dataaa = rrr.json()
|
|
|
|
|
|
|
|
print(dataaa)
|
|
|
|
print("poo")
|
|
|
|
|
|
|
|
if dataaa["status"] == "OK":
|
|
|
|
|
|
|
|
data = ""
|
|
|
|
while data == "":
|
2019-12-14 10:13:27 +01:00
|
|
|
r = WALLET.get_invoice_status(payment_hash)
|
2019-12-13 17:56:19 +01:00
|
|
|
data = r.json()
|
|
|
|
print(r.json())
|
|
|
|
|
2019-12-13 17:59:40 +01:00
|
|
|
adminkey = encrypt(payment_hash)[0:20]
|
|
|
|
inkey = encrypt(adminkey)[0:20]
|
|
|
|
thewal = encrypt(inkey)[0:20]
|
|
|
|
theid = encrypt(thewal)[0:20]
|
2019-12-13 17:56:19 +01:00
|
|
|
thenme = "Bitcoin LN Wallet"
|
|
|
|
|
|
|
|
con = db_connect()
|
|
|
|
cur = con.cursor()
|
|
|
|
|
|
|
|
cur.execute("INSERT INTO accounts (userhash) VALUES ('" + theid + "')")
|
|
|
|
con.commit()
|
|
|
|
cur.close()
|
|
|
|
|
|
|
|
con = db_connect()
|
|
|
|
cur = con.cursor()
|
|
|
|
|
2019-12-13 17:59:40 +01:00
|
|
|
adminkey = encrypt(theid)
|
|
|
|
inkey = encrypt(adminkey)
|
2019-12-13 17:56:19 +01:00
|
|
|
|
|
|
|
cur.execute(
|
2019-12-14 02:59:35 +01:00
|
|
|
"INSERT INTO wallets (hash, name, user, adminkey, inkey) VALUES ('"
|
2019-12-13 17:56:19 +01:00
|
|
|
+ thewal
|
|
|
|
+ "',',0,"
|
|
|
|
+ str(withdraw)
|
|
|
|
+ "','0','"
|
|
|
|
+ thenme
|
|
|
|
+ "','"
|
|
|
|
+ theid
|
|
|
|
+ "','"
|
|
|
|
+ adminkey
|
|
|
|
+ "','"
|
|
|
|
+ inkey
|
|
|
|
+ "')"
|
|
|
|
)
|
|
|
|
con.commit()
|
|
|
|
cur.close()
|
|
|
|
|
|
|
|
con = db_connect()
|
|
|
|
cur = con.cursor()
|
|
|
|
print(thewal)
|
|
|
|
cur.execute("select * from wallets WHERE user = '" + str(theid) + "'")
|
2019-12-13 23:41:39 +01:00
|
|
|
# rows = cur.fetchall()
|
2019-12-13 17:56:19 +01:00
|
|
|
con.commit()
|
|
|
|
cur.close()
|
|
|
|
return render_template(
|
|
|
|
"lnurlwallet.html",
|
|
|
|
len=len("1"),
|
|
|
|
walnme=thenme,
|
|
|
|
walbal=str(withdraw),
|
|
|
|
theid=theid,
|
|
|
|
thewal=thewal,
|
|
|
|
adminkey=adminkey,
|
|
|
|
inkey=inkey,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return render_template("index.html")
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/wallet")
|
|
|
|
def wallet():
|
|
|
|
theid = request.args.get("usr")
|
|
|
|
thewal = request.args.get("wal")
|
|
|
|
thenme = request.args.get("nme")
|
|
|
|
|
|
|
|
if not thewal:
|
|
|
|
return render_template("index.html")
|
|
|
|
|
2019-12-13 18:41:22 +01:00
|
|
|
with Database() as db:
|
2019-12-14 04:11:41 +01:00
|
|
|
user_exists = db.fetchone("SELECT * FROM accounts WHERE userhash = ?", (theid,))
|
|
|
|
|
|
|
|
if not user_exists:
|
|
|
|
# user does not exist: create an account
|
|
|
|
# --------------------------------------
|
|
|
|
|
|
|
|
db.execute("INSERT INTO accounts (userhash) VALUES (?)", (theid,))
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 18:41:22 +01:00
|
|
|
# user exists
|
|
|
|
# -----------
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (theid,))
|
|
|
|
|
|
|
|
if user_wallets:
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 18:41:22 +01:00
|
|
|
# user has wallets
|
|
|
|
# ----------------
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
wallet_row = db.fetchone(
|
|
|
|
"""
|
|
|
|
SELECT
|
|
|
|
(SELECT balance/1000 FROM balances WHERE wallet = wallets.hash),
|
|
|
|
name,
|
|
|
|
adminkey,
|
|
|
|
inkey
|
|
|
|
FROM wallets
|
|
|
|
WHERE user = ? AND hash = ?
|
|
|
|
""",
|
|
|
|
(theid, thewal,),
|
2019-12-13 17:56:19 +01:00
|
|
|
)
|
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
transactions = []
|
|
|
|
|
2019-12-13 17:56:19 +01:00
|
|
|
return render_template(
|
|
|
|
"wallet.html",
|
2019-12-14 04:11:41 +01:00
|
|
|
thearr=user_wallets,
|
|
|
|
len=len(user_wallets),
|
|
|
|
walnme=wallet_row[1],
|
|
|
|
user=theid,
|
|
|
|
walbal=wallet_row[0],
|
2019-12-13 17:56:19 +01:00
|
|
|
theid=theid,
|
|
|
|
thewal=thewal,
|
2019-12-14 04:11:41 +01:00
|
|
|
transactions=transactions,
|
|
|
|
adminkey=wallet_row[2],
|
|
|
|
inkey=wallet_row[3],
|
2019-12-13 17:56:19 +01:00
|
|
|
)
|
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
# user has no wallets
|
|
|
|
# -------------------
|
2019-12-13 18:41:22 +01:00
|
|
|
|
|
|
|
adminkey = encrypt(theid)
|
|
|
|
inkey = encrypt(adminkey)
|
|
|
|
|
|
|
|
db.execute(
|
2019-12-14 04:11:41 +01:00
|
|
|
"INSERT INTO wallets (hash, name, user, adminkey, inkey) VALUES (?, ?, ?, ?, ?)",
|
2019-12-13 18:41:22 +01:00
|
|
|
(thewal, thenme, theid, adminkey, inkey),
|
|
|
|
)
|
|
|
|
|
2019-12-14 04:11:41 +01:00
|
|
|
return render_template(
|
|
|
|
"wallet.html",
|
|
|
|
len=1,
|
|
|
|
walnme=thenme,
|
|
|
|
walbal=0,
|
|
|
|
theid=theid,
|
|
|
|
thewal=thewal,
|
|
|
|
adminkey=adminkey,
|
|
|
|
inkey=inkey,
|
|
|
|
transactions=[],
|
|
|
|
)
|
2019-12-13 18:41:22 +01:00
|
|
|
|
2019-12-13 17:56:19 +01:00
|
|
|
|
|
|
|
@app.route("/v1/invoices", methods=["GET", "POST"])
|
|
|
|
def api_invoices():
|
2019-12-13 19:36:46 +01:00
|
|
|
if request.headers["Content-Type"] != "application/json":
|
2019-12-14 04:11:41 +01:00
|
|
|
return jsonify({"ERROR": "MUST BE JSON"}), 400
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 19:36:46 +01:00
|
|
|
postedjson = request.json
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 19:36:46 +01:00
|
|
|
if "value" not in postedjson:
|
2019-12-14 04:11:41 +01:00
|
|
|
return jsonify({"ERROR": "NO VALUE"}), 400
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 19:36:46 +01:00
|
|
|
if not postedjson["value"].isdigit():
|
2019-12-14 04:11:41 +01:00
|
|
|
return jsonify({"ERROR": "VALUE MUST BE A NUMBER"}), 400
|
|
|
|
|
|
|
|
if postedjson["value"] < 0:
|
|
|
|
return jsonify({"ERROR": "AMOUNTLESS INVOICES NOT SUPPORTED"}), 400
|
2019-12-13 19:36:46 +01:00
|
|
|
|
|
|
|
if "memo" not in postedjson:
|
2019-12-14 04:11:41 +01:00
|
|
|
return jsonify({"ERROR": "NO MEMO"}), 400
|
2019-12-13 19:36:46 +01:00
|
|
|
|
|
|
|
with Database() as db:
|
2019-12-14 02:59:35 +01:00
|
|
|
wallet_row = db.fetchone(
|
|
|
|
"SELECT hash FROM wallets WHERE inkey = ? OR adminkey = ?",
|
|
|
|
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],),
|
|
|
|
)
|
2019-12-13 19:36:46 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if not wallet_row:
|
2019-12-13 19:36:46 +01:00
|
|
|
return jsonify({"ERROR": "NO KEY"}), 200
|
|
|
|
|
2019-12-14 10:13:27 +01:00
|
|
|
r = WALLET.create_invoice(postedjson["value"], postedjson["memo"])
|
2019-12-13 19:36:46 +01:00
|
|
|
data = r.json()
|
|
|
|
|
|
|
|
pay_req = data["pay_req"]
|
|
|
|
payment_hash = data["payment_hash"]
|
|
|
|
|
|
|
|
db.execute(
|
2019-12-14 02:59:35 +01:00
|
|
|
"INSERT INTO apipayments (payhash, amount, wallet, pending, memo) VALUES (?, ?, ?, true, ?)",
|
|
|
|
(payment_hash, postedjson["value"] * 1000, wallet_row[0], postedjson["memo"],),
|
2019-12-13 19:36:46 +01:00
|
|
|
)
|
|
|
|
|
2019-12-13 22:45:03 +01:00
|
|
|
return jsonify({"pay_req": pay_req, "payment_hash": payment_hash}), 200
|
2019-12-13 17:56:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/v1/channels/transactions", methods=["GET", "POST"])
|
|
|
|
def api_transactions():
|
2019-12-13 22:45:03 +01:00
|
|
|
if request.headers["Content-Type"] != "application/json":
|
|
|
|
return jsonify({"ERROR": "MUST BE JSON"}), 200
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
data = request.json
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if "payment_request" not in data:
|
2019-12-13 17:56:19 +01:00
|
|
|
return jsonify({"ERROR": "NO PAY REQ"}), 200
|
|
|
|
|
2019-12-13 22:45:03 +01:00
|
|
|
with Database() as db:
|
2019-12-14 02:59:35 +01:00
|
|
|
wallet_row = db.fetchone(
|
|
|
|
"SELECT hash FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],)
|
|
|
|
)
|
2019-12-13 22:45:03 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if not wallet_row:
|
2019-12-13 22:45:03 +01:00
|
|
|
return jsonify({"ERROR": "BAD AUTH"}), 200
|
|
|
|
|
|
|
|
# TODO: check this unused code
|
|
|
|
# move sats calculation to a helper
|
|
|
|
# ---------------------------------
|
|
|
|
"""
|
|
|
|
s = postedjson["payment_request"]
|
|
|
|
result = re.search("lnbc(.*)1p", s)
|
|
|
|
tempp = result.group(1)
|
|
|
|
|
|
|
|
alpha = ""
|
|
|
|
num = ""
|
|
|
|
|
|
|
|
for i in range(len(tempp)):
|
|
|
|
if tempp[i].isdigit():
|
|
|
|
num = num + tempp[i]
|
|
|
|
else:
|
|
|
|
alpha += tempp[i]
|
|
|
|
sats = ""
|
|
|
|
if alpha == "n":
|
|
|
|
sats = int(num) / 10
|
|
|
|
elif alpha == "u":
|
|
|
|
sats = int(num) * 100
|
|
|
|
elif alpha == "m":
|
|
|
|
sats = int(num) * 100000
|
|
|
|
"""
|
|
|
|
# ---------------------------------
|
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
# decode the invoice
|
|
|
|
invoice = bolt11.decode(data["payment_request"])
|
2019-12-14 04:11:41 +01:00
|
|
|
if invoice.amount == 0:
|
|
|
|
return jsonify({"ERROR": "AMOUNTLESS INVOICES NOT SUPPORTED"}), 400
|
2019-12-13 22:45:03 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
# insert the payment
|
2019-12-13 22:45:03 +01:00
|
|
|
db.execute(
|
2019-12-14 02:59:35 +01:00
|
|
|
"INSERT INTO apipayments (payhash, amount, fee, wallet, pending, memo) VALUES (?, ?, ?, true, ?)'",
|
2019-12-13 22:45:03 +01:00
|
|
|
(
|
2019-12-14 02:59:35 +01:00
|
|
|
invoice.payment_hash,
|
|
|
|
-int(invoice.amount_msat),
|
|
|
|
-int(invoice.amount_msat * 0.01),
|
|
|
|
wallet_row[0],
|
|
|
|
invoice.description,
|
2019-12-13 22:45:03 +01:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
# check balance
|
2019-12-14 04:11:41 +01:00
|
|
|
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet_row[0]))[0]
|
2019-12-14 02:59:35 +01:00
|
|
|
if balance < 0:
|
|
|
|
return jsonify({"ERROR": "INSUFFICIENT BALANCE"}), 403
|
|
|
|
|
|
|
|
# actually send the payment
|
2019-12-14 10:13:27 +01:00
|
|
|
r = WALLET.pay_invoice(data["payment_request"])
|
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if not r.ok:
|
|
|
|
return jsonify({"ERROR": "UNEXPECTED PAYMENT ERROR"}), 500
|
2019-12-13 22:45:03 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
data = r.json()
|
|
|
|
if r.ok and data["error"]:
|
|
|
|
# payment didn't went through, delete it here
|
|
|
|
# (these guarantees specific to lntxbot)
|
|
|
|
db.execute("DELETE FROM apipayments WHERE payhash = ?", (invoice.payment_hash,))
|
|
|
|
return jsonify({"PAID": "FALSE"}), 200
|
2019-12-13 22:45:03 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
# payment went through, not pending anymore, save actual fees
|
2019-12-13 22:45:03 +01:00
|
|
|
db.execute(
|
2019-12-14 02:59:35 +01:00
|
|
|
"UPDATE apipayments SET pending = false, fee = ? WHERE payhash = ?",
|
|
|
|
(data["fee_msat"], invoice.payment_hash,),
|
2019-12-13 22:45:03 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
return jsonify({"PAID": "TRUE"}), 200
|
2019-12-13 17:56:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/v1/invoice/<payhash>", methods=["GET"])
|
|
|
|
def api_checkinvoice(payhash):
|
2019-12-13 23:22:49 +01:00
|
|
|
if request.headers["Content-Type"] != "application/json":
|
|
|
|
return jsonify({"ERROR": "MUST BE JSON"}), 200
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-13 23:22:49 +01:00
|
|
|
with Database() as db:
|
2019-12-14 02:59:35 +01:00
|
|
|
payment_row = db.fetchone(
|
|
|
|
"""
|
|
|
|
SELECT pending FROM apipayments
|
|
|
|
INNER JOIN wallets AS w ON apipayments.wallet = w.hash
|
|
|
|
WHERE payhash = ?
|
2019-12-14 04:11:41 +01:00
|
|
|
AND (w.adminkey = ? OR w.inkey = ?)
|
2019-12-14 02:59:35 +01:00
|
|
|
""",
|
|
|
|
(payhash, request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"]),
|
|
|
|
)
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if not payment_row:
|
|
|
|
return jsonify({"ERROR": "NO INVOICE"}), 404
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if not payment_row[0]: # pending
|
2019-12-13 23:22:49 +01:00
|
|
|
return jsonify({"PAID": "TRUE"}), 200
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 10:13:27 +01:00
|
|
|
r = WALLET.get_invoice_status(payhash)
|
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
if not r.ok:
|
2019-12-13 23:22:49 +01:00
|
|
|
return jsonify({"PAID": "FALSE"}), 400
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
data = r.json()
|
|
|
|
if "preimage" not in data or not data["preimage"]:
|
|
|
|
return jsonify({"PAID": "FALSE"}), 400
|
2019-12-13 17:56:19 +01:00
|
|
|
|
2019-12-14 02:59:35 +01:00
|
|
|
db.execute("UPDATE apipayments SET pending = false WHERE payhash = ?", (payhash,))
|
|
|
|
return jsonify({"PAID": "TRUE"}), 200
|