refactor: use Flask Blueprints to organize extensions

- extensions are now blueprints: keep views, templastes and statics in the same folder
- increase app security using `flask-talisman`
- whenever possible use {{ url_for }} for links between pages
- remove references to non-existing JavaScript code
- add missing favicon.ico
This commit is contained in:
Eneko Illarramendi 2020-01-31 21:07:05 +01:00
parent 4beb7be852
commit 9e90aabead
29 changed files with 541 additions and 1115 deletions

View File

@ -1,41 +1,57 @@
import uuid
import os
import json import json
import os
import requests import requests
import re import uuid
from flask import Flask, jsonify, render_template, request, redirect, url_for from flask import Flask, jsonify, redirect, render_template, request, url_for
from lnurl import Lnurl, LnurlWithdrawResponse, encode from flask_talisman import Talisman
from datetime import datetime from lnurl import Lnurl, LnurlWithdrawResponse
from . import bolt11 from . import bolt11
from .db import Database, ExtDatabase, FauDatabase from .core import core_app
from .db import open_db, open_ext_db
from .extensions.withdraw import withdraw_ext
from .helpers import megajson from .helpers import megajson
from .settings import LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME, FEE_RESERVE from .settings import LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME, FEE_RESERVE
app = Flask(__name__) app = Flask(__name__)
Talisman(app, content_security_policy={
"default-src": [
"'self'",
"'unsafe-eval'",
"'unsafe-inline'",
"cdnjs.cloudflare.com",
"code.ionicframework.com",
"code.jquery.com",
"fonts.googleapis.com",
"fonts.gstatic.com",
"maxcdn.bootstrapcdn.com",
]
})
# filters
app.jinja_env.filters["megajson"] = megajson app.jinja_env.filters["megajson"] = megajson
# blueprints
app.register_blueprint(core_app)
app.register_blueprint(withdraw_ext, url_prefix="/withdraw")
@app.before_first_request @app.before_first_request
def init(): def init():
with Database() as db: with open_db() as db:
with open(os.path.join(LNBITS_PATH, "data", "schema.sql")) as schemafile: with open(os.path.join(LNBITS_PATH, "data", "schema.sql")) as schemafile:
for stmt in schemafile.read().split(";\n\n"): for stmt in schemafile.read().split(";\n\n"):
db.execute(stmt, []) db.execute(stmt, [])
@app.route("/")
def home():
return render_template("index.html")
@app.route("/deletewallet") @app.route("/deletewallet")
def deletewallet(): def deletewallet():
user_id = request.args.get("usr") user_id = request.args.get("usr")
wallet_id = request.args.get("wal") wallet_id = request.args.get("wal")
with Database() as db: with open_db() as db:
db.execute( db.execute(
""" """
UPDATE wallets AS w UPDATE wallets AS w
@ -94,7 +110,7 @@ def lnurlwallet():
data = r.json() data = r.json()
break break
with Database() as db: with open_db() as db:
wallet_id = uuid.uuid4().hex wallet_id = uuid.uuid4().hex
user_id = uuid.uuid4().hex user_id = uuid.uuid4().hex
wallet_name = DEFAULT_USER_WALLET_NAME wallet_name = DEFAULT_USER_WALLET_NAME
@ -135,7 +151,7 @@ def wallet():
# just wallet_name: create a user, then generate a wallet_id and create # just wallet_name: create a user, then generate a wallet_id and create
# nothing: create everything # nothing: create everything
with Database() as db: with open_db() as db:
# ensure this user exists # ensure this user exists
# ------------------------------- # -------------------------------
@ -224,15 +240,15 @@ def wallet():
) )
@app.route("/v1/invoices", methods=["GET", "POST"]) @app.route("/api/v1/invoices", methods=["GET", "POST"])
def api_invoices(): def api_invoices():
if request.headers["Content-Type"] != "application/json": if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400 return jsonify({"ERROR": "MUST BE JSON"}), 400
postedjson = request.json postedjson = request.json
#Form validation # Form validation
if int(postedjson["value"]) < 0 or not postedjson["memo"].replace(' ','').isalnum(): if int(postedjson["value"]) < 0 or not postedjson["memo"].replace(" ", "").isalnum():
return jsonify({"ERROR": "FORM ERROR"}), 401 return jsonify({"ERROR": "FORM ERROR"}), 401
if "value" not in postedjson: if "value" not in postedjson:
@ -247,7 +263,7 @@ def api_invoices():
if "memo" not in postedjson: if "memo" not in postedjson:
return jsonify({"ERROR": "NO MEMO"}), 400 return jsonify({"ERROR": "NO MEMO"}), 400
with Database() as db: with open_db() as db:
wallet = db.fetchone( wallet = db.fetchone(
"SELECT id FROM wallets WHERE inkey = ? OR adminkey = ?", "SELECT id FROM wallets WHERE inkey = ? OR adminkey = ?",
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],), (request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],),
@ -271,7 +287,7 @@ def api_invoices():
return jsonify({"pay_req": pay_req, "payment_hash": pay_hash}), 200 return jsonify({"pay_req": pay_req, "payment_hash": pay_hash}), 200
@app.route("/v1/channels/transactions", methods=["GET", "POST"]) @app.route("/api/v1/channels/transactions", methods=["GET", "POST"])
def api_transactions(): def api_transactions():
if request.headers["Content-Type"] != "application/json": if request.headers["Content-Type"] != "application/json":
@ -283,7 +299,7 @@ def api_transactions():
if "payment_request" not in data: if "payment_request" not in data:
return jsonify({"ERROR": "NO PAY REQ"}), 400 return jsonify({"ERROR": "NO PAY REQ"}), 400
with Database() as db: with open_db() as db:
wallet = db.fetchone("SELECT id FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],)) wallet = db.fetchone("SELECT id FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],))
if not wallet: if not wallet:
@ -332,12 +348,12 @@ def api_transactions():
return jsonify({"PAID": "TRUE", "payment_hash": invoice.payment_hash}), 200 return jsonify({"PAID": "TRUE", "payment_hash": invoice.payment_hash}), 200
@app.route("/v1/invoice/<payhash>", methods=["GET"]) @app.route("/api/v1/invoice/<payhash>", methods=["GET"])
def api_checkinvoice(payhash): def api_checkinvoice(payhash):
if request.headers["Content-Type"] != "application/json": if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400 return jsonify({"ERROR": "MUST BE JSON"}), 400
with Database() as db: with open_db() as db:
payment = db.fetchone( payment = db.fetchone(
""" """
SELECT pending SELECT pending
@ -362,12 +378,12 @@ def api_checkinvoice(payhash):
return jsonify({"PAID": "TRUE"}), 200 return jsonify({"PAID": "TRUE"}), 200
@app.route("/v1/payment/<payhash>", methods=["GET"]) @app.route("/api/v1/payment/<payhash>", methods=["GET"])
def api_checkpayment(payhash): def api_checkpayment(payhash):
if request.headers["Content-Type"] != "application/json": if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400 return jsonify({"ERROR": "MUST BE JSON"}), 400
with Database() as db: with open_db() as db:
payment = db.fetchone( payment = db.fetchone(
""" """
SELECT pending SELECT pending
@ -392,9 +408,9 @@ def api_checkpayment(payhash):
return jsonify({"PAID": "TRUE"}), 200 return jsonify({"PAID": "TRUE"}), 200
@app.route("/v1/checkpending", methods=["POST"]) @app.route("/api/v1/checkpending", methods=["POST"])
def api_checkpending(): def api_checkpending():
with Database() as db: with open_db() as db:
for pendingtx in db.fetchall( for pendingtx in db.fetchall(
""" """
SELECT SELECT
@ -427,10 +443,6 @@ def api_checkpending():
return "" return ""
###########EXTENSIONS STUFF - ADD TO LNFAUCET FOLDER IF POSS
# Checks DB to see if the extensions are activated or not activated for the user # Checks DB to see if the extensions are activated or not activated for the user
@app.route("/extensions") @app.route("/extensions")
def extensions(): def extensions():
@ -441,15 +453,14 @@ def extensions():
if usr: if usr:
if not len(usr) > 20: if not len(usr) > 20:
return redirect(url_for("home")) return redirect(url_for("home"))
with Database() as db:
with open_db() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,)) user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
with ExtDatabase() as Extdd: with open_ext_db() as ext_db:
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,)) user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if not user_ext: if not user_ext:
Extdd.execute( ext_db.execute(
""" """
INSERT OR IGNORE INTO overview (user) VALUES (?) INSERT OR IGNORE INTO overview (user) VALUES (?)
""", """,
@ -459,283 +470,15 @@ def extensions():
if lnevents: if lnevents:
if int(lnevents) != user_ext[0][1] and int(lnevents) < 2: if int(lnevents) != user_ext[0][1] and int(lnevents) < 2:
Extdd.execute("UPDATE overview SET lnevents = ? WHERE user = ?", (int(lnevents),usr,)) ext_db.execute("UPDATE overview SET lnevents = ? WHERE user = ?", (int(lnevents), usr,))
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,)) user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if lnjoust: if lnjoust:
if int(lnjoust) != user_ext[0][2] and int(lnjoust) < 2: if int(lnjoust) != user_ext[0][2] and int(lnjoust) < 2:
Extdd.execute("UPDATE overview SET lnjoust = ? WHERE user = ?", (int(lnjoust),usr,)) ext_db.execute("UPDATE overview SET lnjoust = ? WHERE user = ?", (int(lnjoust), usr,))
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,)) user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if withdraw: if withdraw:
if int(withdraw) != user_ext[0][3] and int(withdraw) < 2: if int(withdraw) != user_ext[0][3] and int(withdraw) < 2:
Extdd.execute("UPDATE overview SET withdraw = ? WHERE user = ?", (int(withdraw),usr,)) ext_db.execute("UPDATE overview SET withdraw = ? WHERE user = ?", (int(withdraw), usr,))
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,)) user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
return render_template( return render_template("extensions.html", user_wallets=user_wallets, user=usr, user_ext=user_ext)
"extensions.html", user_wallets=user_wallets, user=usr, user_ext=user_ext
)
# Main withdraw link page
@app.route("/withdraw")
def withdraw():
usr = request.args.get("usr")
fauc = request.args.get("fauc")
if usr:
if not len(usr) > 20:
return redirect(url_for("home"))
#Get all the data
with Database() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
with ExtDatabase() as Extdd:
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
#If del is selected by user from withdraw page, the withdraw link is to be deleted
faudel = request.args.get("del")
if faudel:
Faudb.execute("DELETE FROM withdraws WHERE uni = ?", (faudel,))
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
return render_template(
"withdraw.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_fau=user_fau
)
#Returns encoded LNURL if web url and parameter gieven
@app.route("/v1/lnurlencode/<urlstr>/<parstr>", methods=["GET"])
def api_lnurlencode(urlstr, parstr):
if not urlstr:
return jsonify({"STATUS": "FALSE"}), 200
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
randar = user_fau[0][15].split(",")
# randar = randar[:-1]
print(int(user_fau[0][10])-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]-2]
print(rand)
else:
rand = randar[0]
lnurlstr = encode( "https://" + urlstr + "/v1/lnurlfetch/" + urlstr + "/" + parstr + "/" + rand)
return jsonify({"STATUS": "TRUE", "LNURL": lnurlstr}), 200
#Returns LNURL json
@app.route("/v1/lnurlfetch/<urlstr>/<parstr>/<rand>", methods=["GET"])
def api_lnurlfetch(parstr, urlstr, rand):
if not parstr:
return jsonify({"STATUS": "FALSE", "ERROR": "NO WALL ID"}), 200
if not urlstr:
return jsonify({"STATUS": "FALSE", "ERROR": "NO URL"}), 200
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
print(user_fau[0][0])
k1str = uuid.uuid4().hex
Faudb.execute("UPDATE withdraws SET withdrawals = ? WHERE uni = ?", (k1str ,parstr,))
res = LnurlWithdrawResponse(
callback='https://' + urlstr + '/v1/lnurlwithdraw/' + rand + '/',
k1=k1str,
min_withdrawable=user_fau[0][8]*1000,
max_withdrawable=user_fau[0][7]*1000,
default_description="LNURL withdraw",
)
print("res")
return res.json(), 200
#Pays invoice if passed k1 invoice and rand
@app.route("/v1/lnurlwithdraw/<rand>/", methods=["GET"])
def api_lnurlwithdraw(rand):
k1 = request.args.get("k1")
pr = request.args.get("pr")
print(rand)
if not k1:
return jsonify({"STATUS": "FALSE", "ERROR": "NO k1"}), 200
if not pr:
return jsonify({"STATUS": "FALSE", "ERROR": "NO PR"}), 200
with FauDatabase() as Faudb:
user_fau = Faudb.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]
print(secspast)
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:
return jsonify({"status":"ERROR", "reason":"BAD AUTH"}), 400
if len(randar) > 2:
randar.remove(rand)
randstr = ','.join(randar)
print(randstr)
# Update time and increments
upinc = (int(user_fau[0][10]) - 1)
Faudb.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}
r = requests.post(url = "https://lnbits.com/v1/channels/transactions", 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 FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
return jsonify({"status":"OK"}), 200
@app.route("/withdrawmaker", methods=["GET", "POST"])
def withdrawmaker():
data = request.json
amt = data["amt"]
tit = data["tit"]
wal = data["wal"]
minamt = data["minamt"]
maxamt = data["maxamt"]
tme = data["tme"]
uniq = data["uniq"]
usr = data["usr"]
wall = wal.split("-")
#Form validation
if int(amt) < 0 or not tit.replace(' ','').isalnum() or wal == "" or int(minamt) < 0 or int(maxamt) < 0 or int(minamt) > int(maxamt) or int(tme) < 0:
return jsonify({"ERROR": "FORM ERROR"}), 401
#If id that means its a link being edited, delet the record first
if "id" in data:
unid = data["id"].split("-")
uni = unid[1]
print(data["id"])
print(uni)
with FauDatabase() as Faudb:
Faudb.execute("DELETE FROM withdraws WHERE uni = ?", (unid[1],))
else:
uni = uuid.uuid4().hex
# Randomiser for random QR option
rand = ""
if uniq > 0:
for x in range(0,int(amt)):
rand += uuid.uuid4().hex[0:5] + ","
else:
rand = uuid.uuid4().hex[0:5] + ","
with Database() as dbb:
user_wallets = dbb.fetchall("SELECT * FROM wallets WHERE user = ? AND id = ?", (usr,wall[1],))
if not user_wallets:
return jsonify({"ERROR": "NO WALLET USER"}), 401
# Get time
dt = datetime.now()
seconds = dt.timestamp()
print(seconds)
#Add to DB
with FauDatabase() as db:
db.execute(
"INSERT OR IGNORE INTO withdraws (usr, wal, walnme, adm, uni, tit, maxamt, minamt, spent, inc, tme, uniq, withdrawals, tmestmp, rand) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
usr,
wall[1],
user_wallets[0][1],
user_wallets[0][3],
uni,
tit,
maxamt,
minamt,
0,
amt,
tme,
uniq,
0,
seconds,
rand,
),
)
#Get updated records
with ExtDatabase() as Extdd:
user_ext = Extdd.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if not user_ext:
return jsonify({"ERROR": "NO WALLET USER"}), 401
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
if not user_fau:
return jsonify({"ERROR": "NO WALLET USER"}), 401
return render_template(
"withdraw.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_fau=user_fau
)
#Simple shareable link
@app.route("/displaywithdraw", methods=["GET", "POST"])
def displaywithdraw():
fauid = request.args.get("id")
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (fauid,))
return render_template(
"displaywithdraw.html", user_fau=user_fau,
)
#Simple printable page of links
@app.route("/printwithdraw/<urlstr>/", methods=["GET", "POST"])
def printwithdraw(urlstr):
fauid = request.args.get("id")
with FauDatabase() as Faudb:
user_fau = Faudb.fetchall("SELECT * FROM withdraws WHERE uni = ?", (fauid,))
randar = user_fau[0][15].split(",")
randar = randar[:-1]
lnurlar = []
print(len(randar))
for d in range(len(randar)):
lnurlar.append( encode("https://"+ urlstr +"/v1/lnurlfetch/" + urlstr + "/" + fauid + "/" + randar[d]))
return render_template(
"printwithdraws.html", lnurlar=lnurlar, user_fau=user_fau[0],
)

View File

@ -49,9 +49,9 @@ def decode(pr: str) -> Invoice:
if tag == "d": if tag == "d":
invoice.description = trim_to_bytes(tagdata).decode("utf-8") invoice.description = trim_to_bytes(tagdata).decode("utf-8")
elif tag == "h" and data_length == 52: elif tag == "h" and data_length == 52:
invoice.description = hexlify(trim_to_bytes(tagdata)).decode('ascii') invoice.description = hexlify(trim_to_bytes(tagdata)).decode("ascii")
elif tag == "p" and data_length == 52: elif tag == "p" and data_length == 52:
invoice.payment_hash = hexlify(trim_to_bytes(tagdata)).decode('ascii') invoice.payment_hash = hexlify(trim_to_bytes(tagdata)).decode("ascii")
return invoice return invoice

8
lnbits/core/__init__.py Normal file
View File

@ -0,0 +1,8 @@
from flask import Blueprint
core_app = Blueprint("core", __name__, template_folder="templates")
from .views_api import * # noqa
from .views import * # noqa

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -13,7 +13,7 @@
<section class="content-header"> <section class="content-header">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li> <li>
<a href="/"><i class="fa fa-dashboard"></i> Home</a> <a href="{{ url_for('core.home') }}"><i class="fa fa-dashboard"></i> Home</a>
</li> </li>
</ol> </ol>
<br /><br /> <br /><br />

14
lnbits/core/views.py Normal file
View File

@ -0,0 +1,14 @@
from flask import render_template, send_from_directory
from os import path
from lnbits.core import core_app
@core_app.route("/favicon.ico")
def favicon():
return send_from_directory(path.join(core_app.root_path, "static"), "favicon.ico")
@core_app.route("/")
def home():
return render_template("index.html")

0
lnbits/core/views_api.py Normal file
View File

View File

@ -1,40 +1,13 @@
import sqlite3
import os import os
from .settings import DATABASE_PATH import sqlite3
from .settings import LNBITS_PATH
from typing import Optional
from .settings import DATABASE_PATH, LNBITS_PATH
class Database: class Database:
def __init__(self, db_path: str = DATABASE_PATH): def __init__(self, db_path: str):
self.path = db_path
self.connection = sqlite3.connect(db_path)
self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.connection.close()
def fetchall(self, query: str, values: tuple) -> list:
"""Given a query, return cursor.fetchall() rows."""
self.cursor.execute(query, values)
return self.cursor.fetchall()
def fetchone(self, query: str, values: tuple):
self.cursor.execute(query, values)
return self.cursor.fetchone()
def execute(self, query: str, values: tuple) -> None:
"""Given a query, cursor.execute() it."""
self.cursor.execute(query, values)
self.connection.commit()
class ExtDatabase:
def __init__(self, db_path: str = os.path.join(LNBITS_PATH, "extensions", "overview.sqlite3")):
self.path = db_path self.path = db_path
self.connection = sqlite3.connect(db_path) self.connection = sqlite3.connect(db_path)
self.connection.row_factory = sqlite3.Row self.connection.row_factory = sqlite3.Row
@ -62,31 +35,11 @@ class ExtDatabase:
self.connection.commit() self.connection.commit()
class FauDatabase: def open_db(db_path: str = DATABASE_PATH) -> Database:
def __init__(self, db_path: str = os.path.join(LNBITS_PATH, "extensions", "faucet", "database.sqlite3")): return Database(db_path=db_path)
self.path = db_path
self.connection = sqlite3.connect(db_path)
self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.connection.close()
def fetchall(self, query: str, values: tuple) -> list:
"""Given a query, return cursor.fetchall() rows."""
self.cursor.execute(query, values)
return self.cursor.fetchall()
def fetchone(self, query: str, values: tuple):
self.cursor.execute(query, values)
return self.cursor.fetchone()
def execute(self, query: str, values: tuple) -> None:
"""Given a query, cursor.execute() it."""
self.cursor.execute(query, values)
self.connection.commit()
def open_ext_db(extension: Optional[str] = None) -> Database:
if extension:
return open_db(os.path.join(LNBITS_PATH, "extensions", extension, "database.sqlite3"))
return open_db(os.path.join(LNBITS_PATH, "extensions", "overview.sqlite3"))

View File

View File

View File

@ -1,73 +0,0 @@
![Lightning network wallet](https://i.imgur.com/arUWZbH.png)
# LNbits
Simple free and open-source Python lightning-network wallet/accounts system. Use https://lnbits.com, or run your own LNbits server!
LNbits is a very simple server that sits on top of a funding source, and can be used as:
* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet!
* Fallback wallet for the LNURL scheme
* Instant wallet for LN demonstrations
The wallet can run on top of any lightning-network funding source such as LND, lntxbot, paywall, opennode, etc. This first BETA release runs on top of lntxbot, other releases coming soon, although if you are impatient, it could be ported to other funding sources relatively easily. Contributors very welcome :)
LNbits is still in BETA. Please report any vulnerabilities responsibly
## LNbits as an account system
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv + more to come..
![Lightning network wallet](https://i.imgur.com/Sd4ri3T.png)
Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
(LNbits M5StackSats available here https://github.com/arcbtc/M5StackSats)
![lnurl ATM](https://i.imgur.com/ABruzAn.png)
## LNbits as an LNURL-withdraw fallback
LNURL has a fallback scheme, so if scanned by a regular QR code reader it can default to a URL. LNbits exploits this to generate an instant wallet using the LNURL-withdraw.
![lnurl fallback](https://i.imgur.com/CPBKHIv.png)
https://github.com/btcontract/lnurl-rfc/blob/master/spec.md
Adding **/lnurl?lightning="LNURL-WITHDRAW"** will trigger a withdraw that builds an LNbits wallet.
Example use would be an ATM, which utilises LNURL, if the user scans the QR with a regular QR code scanner app, they will stilll be able to access the funds.
![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg)
## LNbits as an insta-wallet
Wallets can be easily generated and given out to people at events (one click multi-wallet generation to be added soon).
"Go to this website", has a lot less friction than "Download this app".
![lnurl ATM](https://i.imgur.com/SF5KoIe.png)
# Running LNbits locally
Download this repo
LNbits uses [Flask](http://flask.pocoo.org/).
Feel free to contribute to the project.
Application dependencies
------------------------
The application uses [Pipenv][pipenv] to manage Python packages.
While in development, you will need to install all dependencies:
$ pipenv shell
$ pipenv install --dev
You will need to set the variables in .env.example, and rename the file to .env
![lnurl ATM](https://i.imgur.com/ri2zOe8.png)
Running the server
------------------
$ flask run
There is an environment variable called `FLASK_ENV` that has to be set to `development`
if you want to run Flask in debug mode with autoreload
[pipenv]: https://docs.pipenv.org/#install-pipenv-today
# Tip me
If you like this project and might even use or extend it, why not send some tip love!
https://paywall.link/to/f4e4e

View File

@ -1,73 +0,0 @@
![Lightning network wallet](https://i.imgur.com/arUWZbH.png)
# LNbits
Simple free and open-source Python lightning-network wallet/accounts system. Use https://lnbits.com, or run your own LNbits server!
LNbits is a very simple server that sits on top of a funding source, and can be used as:
* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet!
* Fallback wallet for the LNURL scheme
* Instant wallet for LN demonstrations
The wallet can run on top of any lightning-network funding source such as LND, lntxbot, paywall, opennode, etc. This first BETA release runs on top of lntxbot, other releases coming soon, although if you are impatient, it could be ported to other funding sources relatively easily. Contributors very welcome :)
LNbits is still in BETA. Please report any vulnerabilities responsibly
## LNbits as an account system
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv + more to come..
![Lightning network wallet](https://i.imgur.com/Sd4ri3T.png)
Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
(LNbits M5StackSats available here https://github.com/arcbtc/M5StackSats)
![lnurl ATM](https://i.imgur.com/ABruzAn.png)
## LNbits as an LNURL-withdraw fallback
LNURL has a fallback scheme, so if scanned by a regular QR code reader it can default to a URL. LNbits exploits this to generate an instant wallet using the LNURL-withdraw.
![lnurl fallback](https://i.imgur.com/CPBKHIv.png)
https://github.com/btcontract/lnurl-rfc/blob/master/spec.md
Adding **/lnurl?lightning="LNURL-WITHDRAW"** will trigger a withdraw that builds an LNbits wallet.
Example use would be an ATM, which utilises LNURL, if the user scans the QR with a regular QR code scanner app, they will stilll be able to access the funds.
![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg)
## LNbits as an insta-wallet
Wallets can be easily generated and given out to people at events (one click multi-wallet generation to be added soon).
"Go to this website", has a lot less friction than "Download this app".
![lnurl ATM](https://i.imgur.com/SF5KoIe.png)
# Running LNbits locally
Download this repo
LNbits uses [Flask](http://flask.pocoo.org/).
Feel free to contribute to the project.
Application dependencies
------------------------
The application uses [Pipenv][pipenv] to manage Python packages.
While in development, you will need to install all dependencies:
$ pipenv shell
$ pipenv install --dev
You will need to set the variables in .env.example, and rename the file to .env
![lnurl ATM](https://i.imgur.com/ri2zOe8.png)
Running the server
------------------
$ flask run
There is an environment variable called `FLASK_ENV` that has to be set to `development`
if you want to run Flask in debug mode with autoreload
[pipenv]: https://docs.pipenv.org/#install-pipenv-today
# Tip me
If you like this project and might even use or extend it, why not send some tip love!
https://paywall.link/to/f4e4e

View File

@ -1,73 +0,0 @@
![Lightning network wallet](https://i.imgur.com/arUWZbH.png)
# LNbits
Simple free and open-source Python lightning-network wallet/accounts system. Use https://lnbits.com, or run your own LNbits server!
LNbits is a very simple server that sits on top of a funding source, and can be used as:
* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet!
* Fallback wallet for the LNURL scheme
* Instant wallet for LN demonstrations
The wallet can run on top of any lightning-network funding source such as LND, lntxbot, paywall, opennode, etc. This first BETA release runs on top of lntxbot, other releases coming soon, although if you are impatient, it could be ported to other funding sources relatively easily. Contributors very welcome :)
LNbits is still in BETA. Please report any vulnerabilities responsibly
## LNbits as an account system
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv + more to come..
![Lightning network wallet](https://i.imgur.com/Sd4ri3T.png)
Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
(LNbits M5StackSats available here https://github.com/arcbtc/M5StackSats)
![lnurl ATM](https://i.imgur.com/ABruzAn.png)
## LNbits as an LNURL-withdraw fallback
LNURL has a fallback scheme, so if scanned by a regular QR code reader it can default to a URL. LNbits exploits this to generate an instant wallet using the LNURL-withdraw.
![lnurl fallback](https://i.imgur.com/CPBKHIv.png)
https://github.com/btcontract/lnurl-rfc/blob/master/spec.md
Adding **/lnurl?lightning="LNURL-WITHDRAW"** will trigger a withdraw that builds an LNbits wallet.
Example use would be an ATM, which utilises LNURL, if the user scans the QR with a regular QR code scanner app, they will stilll be able to access the funds.
![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg)
## LNbits as an insta-wallet
Wallets can be easily generated and given out to people at events (one click multi-wallet generation to be added soon).
"Go to this website", has a lot less friction than "Download this app".
![lnurl ATM](https://i.imgur.com/SF5KoIe.png)
# Running LNbits locally
Download this repo
LNbits uses [Flask](http://flask.pocoo.org/).
Feel free to contribute to the project.
Application dependencies
------------------------
The application uses [Pipenv][pipenv] to manage Python packages.
While in development, you will need to install all dependencies:
$ pipenv shell
$ pipenv install --dev
You will need to set the variables in .env.example, and rename the file to .env
![lnurl ATM](https://i.imgur.com/ri2zOe8.png)
Running the server
------------------
$ flask run
There is an environment variable called `FLASK_ENV` that has to be set to `development`
if you want to run Flask in debug mode with autoreload
[pipenv]: https://docs.pipenv.org/#install-pipenv-today
# Tip me
If you like this project and might even use or extend it, why not send some tip love!
https://paywall.link/to/f4e4e

View File

@ -0,0 +1,8 @@
from flask import Blueprint
withdraw_ext = Blueprint("withdraw", __name__, static_folder="static", template_folder="templates")
from .views_api import * # noqa
from .views import * # noqa

View File

View File

@ -1,233 +0,0 @@
<!-- @format -->
{% extends "base.html" %} {% block messages %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bell-o"></i>
<span class="label label-danger">!</span>
</a>
<ul class="dropdown-menu">
<li class="header"><b>Instant wallet, bookmark to save</b></li>
<li></li>
</ul>
{% endblock %} {% block menuitems %}
<li class="treeview">
<a href="#">
<i class="fa fa-bitcoin"></i> <span>Wallets</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% for w in user_wallets %}
<li>
<a href="wallet?wal={{ w.id }}&usr={{ w.user }}"
><i class="fa fa-bolt"></i> {{ w.name }}</a
>
</li>
{% endfor %}
<li><a onclick="sidebarmake()">Add a wallet +</a></li>
<div id="sidebarmake"></div>
</ul>
</li>
<li class="active treeview">
<a href="#">
<i class="fa fa-th"></i> <span>Extensions</span>
</a>
<ul class="treeview-menu">
{% if user_ext[0][1] %}
<li>
<a href="lnevents?usr={{ user_ext[0][0]}}"
><i class="fa fa-bolt"></i> LNEvents</a>
</li>
{% endif %}
{% if user_ext[0][2] %}
<li>
<a href="lnjoust?usr={{ user_ext[0][0]}}"
><i class="fa fa-bolt"></i> LNJoust</a>
</li>
{% endif %}
{% if user_ext[0][3] %}
<li>
<a href="faucet?usr={{ user_ext[0][0]}}"
><i class="fa fa-bolt"></i> Faucet</a>
</li>
{% endif %}
</ul>
</li>
{% endblock %} {% block body %}
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Wallet
<small
>Control panel
<div id="wonga"></div
></small>
</h1>
<ol class="breadcrumb">
<li>
<a href="#"><i class="fa fa-dashboard"></i> Home</a>
</li>
<li class="active">Extensions</li>
</ol>
<br /><br />
<div class="alert alert-danger alert-dismissable">
<h4>
Bookmark to save your wallet. Wallet is in BETA, use with caution.
</h4>
</div>
</section>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
{% if not user_ext[0][2] %}
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-green">
<div class="inner">
<h3>
LNJoust
</h3>
<p>
LN powered Joust gamesmaster
</p>
</div>
<div class="icon">
<i class="ion ion-wand"></i>
</div>
<a href="extensions?usr={{user}}&lnjoust=1" class="small-box-footer">
Activate <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div><!-- ./col -->
{% else %}
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-green">
<div class="inner">
<h3>
LNJoust
</h3>
<p>
LN powered Joust gamesmaster
</p>
</div>
<div class="icon">
<i class="ion ion-wand"></i>
</div>
<a href="extensions?usr={{user}}&lnjoust=0" class="small-box-footer">
Deactivate <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div><!-- ./col -->
{% endif %}
{% if not user_ext[0][1] %}
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-yellow">
<div class="inner">
<h3>
LNEvents
</h3>
<p>
Lightning powered tickets
</p>
</div>
<div class="icon">
<i class="ion ion-calendar"></i>
</div>
<a href="extensions?usr={{user}}&lnevents=1" class="small-box-footer">
Activate <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div><!-- ./col -->
{% else %}
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-yellow">
<div class="inner">
<h3>
LNEvents
</h3>
<p>
Lightning powered tickets
</p>
</div>
<div class="icon">
<i class="ion ion-calendar"></i>
</div>
<a href="extensions?usr={{user}}&lnevents=0" class="small-box-footer">
Deactivate <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div><!-- ./col -->
{% endif %}
{% if not user_ext[0][3] %}
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-red">
<div class="inner">
<h3>
Faucet
</h3>
<p>
Make LNURL faucets
</p>
</div>
<div class="icon">
<i class="ion ion-beer"></i>
</div>
<a href="extensions?usr={{user}}&faucet=1" class="small-box-footer">
Activate <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div><!-- ./col -->
{% else %}
<div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box bg-red">
<div class="inner">
<h3>
Faucet
</h3>
<p>
Make LNURL faucets
</p>
</div>
<div class="icon">
<i class="ion ion-beer"></i>
</div>
<a href="extensions?usr={{user}}&faucet=0" class="small-box-footer">
Deactivate <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div><!-- ./col -->
{% endif %}
</div>
<!-- /.content -->
</section>
<script>
window.user = {{ user | megajson | safe }}
window.user_wallets = {{ user_wallets | megajson | safe }}
window.user_ext = {{ user_ext | megajson | safe }}
</script>
</div>
{% endblock %}

View File

@ -23,7 +23,7 @@
/> />
<!-- Ionicons 2.0.0 --> <!-- Ionicons 2.0.0 -->
<link <link
href="http://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css" href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
/> />
@ -34,7 +34,7 @@
media="screen" media="screen"
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}" href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
/> />
<!-- AdminLTE Skins. Choose a skin from the css/skins <!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. --> folder instead of downloading all of them to reduce the load. -->
<link <link
@ -43,13 +43,6 @@
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}" href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
/> />
<!-- iCheck -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/iCheck/flat/blue.css') }}"
/>
<!-- Morris chart --> <!-- Morris chart -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -64,20 +57,6 @@
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}" href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
/> />
<!-- Date Picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/datepicker/datepicker3.css') }}"
/>
<!-- Daterange picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/daterangepicker/daterangepicker-bs3.css') }}"
/>
<!-- bootstrap wysihtml5 - text editor --> <!-- bootstrap wysihtml5 - text editor -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -129,7 +108,7 @@
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script> <script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
<!-- jQuery UI 1.11.2 --> <!-- jQuery UI 1.11.2 -->
<script <script
src="http://code.jquery.com/ui/1.11.2/jquery-ui.min.js" src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip --> <!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
@ -142,7 +121,7 @@
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- Morris.js charts --> <!-- Morris.js charts -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script <script
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}" src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
type="text/javascript" type="text/javascript"
@ -166,26 +145,11 @@
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}" src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- daterangepicker -->
<script
src="{{ url_for('static', filename='plugins/daterangepicker/daterangepicker.js') }}"
type="text/javascript"
></script>
<!-- datepicker -->
<script
src="{{ url_for('static', filename='plugins/datepicker/bootstrap-datepicker.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap WYSIHTML5 --> <!-- Bootstrap WYSIHTML5 -->
<script <script
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}" src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- iCheck -->
<script
src="{{ url_for('static', filename='plugins/iCheck/icheck.min.js') }}"
type="text/javascript"
></script>
<!-- Slimscroll --> <!-- Slimscroll -->
<script <script
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}" src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
@ -243,9 +207,9 @@
<style> <style>
//GOOFY CSS HACK TO GO DARK //GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper { .skin-blue .wrapper {
background: background:
#1f2234; #1f2234;
} }
@ -260,40 +224,40 @@ body {
} }
.skin-blue .main-header .navbar { .skin-blue .main-header .navbar {
background-color: background-color:
#2e507d; #2e507d;
} }
.content-wrapper, .right-side { .content-wrapper, .right-side {
background-color: background-color:
#1f2234; #1f2234;
} }
.skin-blue .main-header .logo { .skin-blue .main-header .logo {
background-color: background-color:
#1f2234; #1f2234;
color: color:
#fff; #fff;
} }
.skin-blue .sidebar-menu > li.header { .skin-blue .sidebar-menu > li.header {
color: color:
#4b646f; #4b646f;
background: background:
#1f2234; #1f2234;
} }
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side { .skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
background: background:
#1f2234; #1f2234;
} }
.skin-blue .sidebar-menu > li > .treeview-menu { .skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px; margin: 0 1px;
background: background:
#1f2234; #1f2234;
} }
.skin-blue .sidebar-menu > li > a { .skin-blue .sidebar-menu > li > a {
border-left: 3px solid border-left: 3px solid
transparent; transparent;
margin-right: 1px; margin-right: 1px;
} }
@ -307,12 +271,12 @@ background:
.skin-blue .main-header .logo:hover { .skin-blue .main-header .logo:hover {
background: background:
#3e355a; #3e355a;
} }
.skin-blue .main-header .navbar .sidebar-toggle:hover { .skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color: background-color:
#3e355a; #3e355a;
} }
.main-footer { .main-footer {
@ -327,13 +291,13 @@ background:
} }
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body { .bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
background-color: background-color:
#1f2234 !important; #1f2234 !important;
} }
.alert-danger, .alert-error { .alert-danger, .alert-error {
border-color: #fff; border-color: #fff;
border: 1px solid border: 1px solid
#fff; #fff;
border-radius: 7px; border-radius: 7px;
@ -341,12 +305,12 @@ background:
} }
.skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus { .skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus {
color: color:
#f6f6f6; #f6f6f6;
background-color: #3e355a; background-color: #3e355a;
} }
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body { .bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
background-color: background-color:
#3e355a !important; #3e355a !important;
} }
@ -360,7 +324,7 @@ background:
} }
.table-striped > tbody > tr:nth-of-type(2n+1) { .table-striped > tbody > tr:nth-of-type(2n+1) {
background-color: background-color:
#333646; #333646;
} }
@ -389,7 +353,7 @@ a:hover, a:active, a:focus {
text-decoration: none; text-decoration: none;
color: #fff; color: #fff;
} }
// .modal.in .modal-dialog{ // .modal.in .modal-dialog{
// color:#000; // color:#000;
// } // }
@ -403,7 +367,7 @@ color: #fff;
border-top: none; border-top: none;
background-color: background-color:
#333646; #333646;
} }
.modal-footer { .modal-footer {
@ -425,7 +389,7 @@ background-color:
.layout-boxed { .layout-boxed {
background: none; background: none;
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
background-color: background-color:
#3e355a; #3e355a;
} }
@ -441,7 +405,7 @@ background-color:
<div class="wrapper"> <div class="wrapper">
<header class="main-header"> <header class="main-header">
<!-- Logo --> <!-- Logo -->
<a href="/" class="logo"><b>LN</b>bits</a> <a href="{{ url_for('core.home') }}" class="logo"><b>LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less --> <!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation"> <nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button--> <!-- Sidebar toggle button-->
@ -475,7 +439,7 @@ background-color:
<ul class="sidebar-menu"> <ul class="sidebar-menu">
<li><br/><br/><a href="https://where39.com/"><p>Where39 anon locations</p><img src="static/where39.png" style="width:170px"></a></li> <li><br/><br/><a href="https://where39.com/"><p>Where39 anon locations</p><img src="static/where39.png" style="width:170px"></a></li>
<li><br/><a href="https://github.com/arcbtc/Quickening"><p>The Quickening <$8 PoS</p><img src="static/quick.gif" style="width:170px"></a></li> <li><br/><a href="https://github.com/arcbtc/Quickening"><p>The Quickening <$8 PoS</p><img src="static/quick.gif" style="width:170px"></a></li>
<li><br/><a href="http://jigawatt.co/"><p>Buy BTC stamps + electronics</p><img src="static/stamps.jpg" style="width:170px"></a></li> <li><br/><a href="https://jigawatt.co/"><p>Buy BTC stamps + electronics</p><img src="static/stamps.jpg" style="width:170px"></a></li>
<li><br/><a href="mailto:ben@arc.wales"><h3>Advertise here!</h3></a></li> <li><br/><a href="mailto:ben@arc.wales"><h3>Advertise here!</h3></a></li>
</ul> </ul>
@ -499,7 +463,7 @@ background-color:
<center><h1 style="font-size:500%">Withdraw Link: {{ user_fau[0][6] }}</h1></center> <center><h1 style="font-size:500%">Withdraw Link: {{ user_fau[0][6] }}</h1></center>
<center><br/><br/> <div id="qrcode" style="width: 340px;"></div><br/><br/> <center><br/><br/> <div id="qrcode" style="width: 340px;"></div><br/><br/>
<div style="width:55%;word-wrap: break-word;" id="qrcodetxt"></div> <br/></center> <div style="width:55%;word-wrap: break-word;" id="qrcodetxt"></div> <br/></center>
</section><!-- /.content --> </section><!-- /.content -->
</div><!-- /.content-wrapper --> </div><!-- /.content-wrapper -->
@ -519,7 +483,7 @@ background-color:
} }
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey) xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
xhr.setRequestHeader('Content-Type', 'application/json') xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send() xhr.send()
return xhr return xhr
} }
@ -531,10 +495,10 @@ function drawwithdraw(data) {
console.log(data) console.log(data)
getAjax('/v1/lnurlencode/'+ window.location.hostname + "/" + data, "filla", function(datab) { getAjax('/withdraw/api/v1/lnurlencode/'+ window.location.hostname + "/" + data, "filla", function(datab) {
if (JSON.parse(datab).STATUS == 'TRUE') { if (JSON.parse(datab).status == 'TRUE') {
console.log(JSON.parse(datab).STATUS) console.log(JSON.parse(datab).status)
lnurlfau = (JSON.parse(datab).LNURL) lnurlfau = (JSON.parse(datab).lnurl)
new QRCode(document.getElementById('qrcode'), { new QRCode(document.getElementById('qrcode'), {

View File

@ -18,7 +18,7 @@
<ul class="treeview-menu"> <ul class="treeview-menu">
{% for w in user_wallets %} {% for w in user_wallets %}
<li> <li>
<a href="wallet?wal={{ w.id }}&usr={{ w.user }}" <a href="{{ url_for('wallet') }}?wal={{ w.id }}&usr={{ w.user }}"
><i class="fa fa-bolt"></i> {{ w.name }}</a ><i class="fa fa-bolt"></i> {{ w.name }}</a
> >
</li> </li>
@ -34,28 +34,14 @@
<i class="fa fa-angle-left pull-right"></i> <i class="fa fa-angle-left pull-right"></i>
</a> </a>
<ul class="treeview-menu"> <ul class="treeview-menu">
{% if user_ext[0][1] %}
<li>
<a href="lnevents?usr={{ user_ext[0][0]}}"
><i class="fa fa-plus"></i> LNEvents</a>
</li>
{% endif %}
{% if user_ext[0][2] %}
<li>
<a href="lnjoust?usr={{ user_ext[0][0]}}"
><i class="fa fa-plus"></i> LNJoust</a>
</li>
{% endif %}
{% if user_ext[0][3] %} {% if user_ext[0][3] %}
<li> <li>
<a href="withdraw?usr={{ user_ext[0][0]}}" <a href="{{ url_for('withdraw.index') }}?usr={{ user_ext[0][0]}}"
><i class="fa fa-plus"></i> LNURLw</a> ><i class="fa fa-plus"></i> LNURLw</a>
</li> </li>
{% endif %} {% endif %}
<li> <li>
<a href="extensions?usr={{ user }}" <a href="{{ url_for('extensions') }}?usr={{ user }}"
>Manager </a> >Manager </a>
</li> </li>
@ -75,13 +61,13 @@
</h1> </h1>
<ol class="breadcrumb"> <ol class="breadcrumb">
<li> <li>
<a href="/wallet?usr={{ user }}"><i class="fa fa-dashboard"></i> Home</a> <a href="{{ url_for('wallet') }}?usr={{ user }}"><i class="fa fa-dashboard"></i> Home</a>
</li> </li>
<li> <li>
<a href="/extensions?usr={{ user }}"><li class="fa fa-dashboard">Extensions</li></a> <a href="{{ url_for('extensions') }}?usr={{ user }}"><li class="fa fa-dashboard">Extensions</li></a>
</li> </li>
<li> <li>
<i class="active" class="fa fa-dashboard">Withdraw link maker</i> <i class="active" class="fa fa-dashboard">Withdraw link maker</i>
</li> </li>
</ol> </ol>
<br /><br /> <br /><br />
@ -142,7 +128,7 @@
</div><!-- /.box-body --> </div><!-- /.box-body -->
<div class="box-footer"> <div class="box-footer">
<button onclick="postfau()" type="button" class="btn btn-info">Create link(s)</button><p style="color:red;" id="error"></p> <button onclick="postfau()" type="button" class="btn btn-info">Create link(s)</button><p style="color:red;" id="error"></p>
</div> </div>
</form> </form>
@ -224,7 +210,7 @@
window.user_ext = {{ user_ext | megajson | safe }} window.user_ext = {{ user_ext | megajson | safe }}
window.user_fau = {{ user_fau | megajson | safe }} window.user_fau = {{ user_fau | megajson | safe }}
const user_fau = window.user_fau const user_fau = window.user_fau
console.log(user_fau) console.log(user_fau)
@ -239,7 +225,7 @@ function drawChart(user_fau) {
"<tr><td style='width: 50%'>" + "<tr><td style='width: 50%'>" +
tx.tit + tx.tit +
'</td><td >' + '</td><td >' +
"<a href='" + "/displaywithdraw?id=" + tx.uni + "'>" + tx.uni.substring(0, 4) + "...</a>" + "<a href='" + "{{ url_for('withdraw.display') }}?id=" + tx.uni + "'>" + tx.uni.substring(0, 4) + "...</a>" +
'</td><td>' + '</td><td>' +
tx.maxamt + tx.maxamt +
'</td><td>' + '</td><td>' +
@ -247,11 +233,11 @@ function drawChart(user_fau) {
'</td><td>' + '</td><td>' +
tx.tme + tx.tme +
'</td><td>' + '</td><td>' +
"<a href='/wallet?usr="+ user +"'>" + tx.uni.substring(0, 4) + "...</a>" + "<a href='{{ url_for('wallet') }}?usr="+ user +"'>" + tx.uni.substring(0, 4) + "...</a>" +
'</td><td>' + '</td><td>' +
"<i onclick='editlink("+ i +")'' class='fa fa-edit'></i>" + "<i onclick='editlink("+ i +")'' class='fa fa-edit'></i>" +
'</td><td>' + '</td><td>' +
"<b><a style='color:red;' href='" + "/withdraw?del=" + tx.uni + "&usr=" + user +"'>" + "<i class='fa fa-trash'></i>" + "</a></b>" + "<b><a style='color:red;' href='" + "{{ url_for('withdraw.index') }}?del=" + tx.uni + "&usr=" + user +"'>" + "<i class='fa fa-trash'></i>" + "</a></b>" +
'</td></tr>' + '</td></tr>' +
transactionsHTML transactionsHTML
document.getElementById('transactions').innerHTML = transactionsHTML document.getElementById('transactions').innerHTML = transactionsHTML
@ -266,14 +252,14 @@ if (user_fau.length) {
//draws withdraw QR code //draws withdraw QR code
function drawwithdraw() { function drawwithdraw() {
walname = document.getElementById("fauselect").value walname = document.getElementById("fauselect").value
thewithdraw = walname.split("-"); thewithdraw = walname.split("-");
console.log(window.location.hostname + "-" + thewithdraw[1]) console.log(window.location.hostname + "-" + thewithdraw[1])
getAjax('/v1/lnurlencode/'+ window.location.hostname + "/" + thewithdraw[1], "filla", function(datab) { getAjax("/withdraw/api/v1/lnurlencode/"+ window.location.hostname + "/" + thewithdraw[1], "filla", function(datab) {
if (JSON.parse(datab).STATUS == 'TRUE') { if (JSON.parse(datab).STATUS == 'TRUE') {
console.log(JSON.parse(datab).STATUS) console.log(JSON.parse(datab).STATUS)
lnurlfau = (JSON.parse(datab).LNURL) lnurlfau = (JSON.parse(datab).LNURL)
@ -289,10 +275,10 @@ function drawwithdraw() {
}) })
if (thewithdraw[2] > 0){ if (thewithdraw[2] > 0){
document.getElementById('qrcodetxt').innerHTML = lnurlfau document.getElementById('qrcodetxt').innerHTML = lnurlfau
+ +
"<a target='_blank' href='/displaywithdraw?id=" + thewithdraw[1] + "'><h4>Shareable link</h4></a>" + "<a target='_blank' href='{{ url_for('withdraw.display') }}?id=" + thewithdraw[1] + "'><h4>Shareable link</h4></a>" +
"<a target='_blank' href='/printwithdraw/" + window.location.hostname + "/?id=" + thewithdraw[1] + "'><h4>Print all withdraws</h4></a>" "<a target='_blank' href='/withdraw/printwithdraw/" + window.location.hostname + "/?id=" + thewithdraw[1] + "'><h4>Print all withdraws</h4></a>"
document.getElementById("qrcode").style.backgroundColor = "white"; document.getElementById("qrcode").style.backgroundColor = "white";
document.getElementById("qrcode").style.padding = "20px"; document.getElementById("qrcode").style.padding = "20px";
} }
@ -309,7 +295,7 @@ function drawwithdraw() {
thewithdraw[1] = "Failed to build LNURL" thewithdraw[1] = "Failed to build LNURL"
} }
}) })
} }
@ -327,38 +313,38 @@ function postfau(){
if (tit == "") { if (tit == "") {
document.getElementById("error").innerHTML = "Only use letters in title" document.getElementById("error").innerHTML = "Only use letters in title"
return amt return amt
} }
if (wal == "") { if (wal == "") {
document.getElementById("error").innerHTML = "No wallet selected" document.getElementById("error").innerHTML = "No wallet selected"
return amt return amt
} }
if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) { if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) {
document.getElementById("error").innerHTML = "Max 15 - 1000000 and must be higher than min" document.getElementById("error").innerHTML = "Max 15 - 1000000 and must be higher than min"
return amt return amt
} }
if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) { if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) {
document.getElementById("error").innerHTML = "Min 1 - 1000000 and must be lower than max" document.getElementById("error").innerHTML = "Min 1 - 1000000 and must be lower than max"
return amt return amt
} }
if (isNaN(amt) || amt < 1 || amt > 1000) { if (isNaN(amt) || amt < 1 || amt > 1000) {
document.getElementById("error").innerHTML = "Amount of uses must be between 1 - 1000" document.getElementById("error").innerHTML = "Amount of uses must be between 1 - 1000"
return amt return amt
} }
if (isNaN(tme) || tme < 1 || tme > 86400) { if (isNaN(tme) || tme < 1 || tme > 86400) {
document.getElementById("error").innerHTML = "Max waiting time 1 day (86400 secs)" document.getElementById("error").innerHTML = "Max waiting time 1 day (86400 secs)"
return amt return amt
} }
postAjax( postAjax(
'/withdrawmaker', "{{ url_for('withdraw.create') }}",
JSON.stringify({"tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}), JSON.stringify({"tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}),
"filla", "filla",
function(data) { location.replace("/withdraw?usr=" + user) function(data) { location.replace("{{ url_for('withdraw.index') }}?usr=" + user)
}) })
@ -393,7 +379,7 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
"<label for='exampleInputEmail1'>Link title</label>"+ "<label for='exampleInputEmail1'>Link title</label>"+
"<input id='edittit' type='text' class='form-control' value='"+ "<input id='edittit' type='text' class='form-control' value='"+
faudetails.tit + faudetails.tit +
"'></input> </div>"+ "'></input> </div>"+
" </div>"+ " </div>"+
" <div class='col-sm-4 col-md-4'>"+ " <div class='col-sm-4 col-md-4'>"+
@ -403,9 +389,9 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<select id='editwal' class='form-control'>"+ "<select id='editwal' class='form-control'>"+
" <option>" + faudetails.walnme + "-" + faudetails.wal + "</option>"+ " <option>" + faudetails.walnme + "-" + faudetails.wal + "</option>"+
" {% for w in user_wallets %}"+ " {% for w in user_wallets %}"+
" <option>{{w.name}}-{{w.id}}</option>"+ " <option>{{w.name}}-{{w.id}}</option>"+
" {% endfor %}"+ " {% endfor %}"+
" </select>"+ " </select>"+
" </div>"+ " </div>"+
@ -414,42 +400,42 @@ document.getElementById('editlink').innerHTML = "<div class='row'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
" <label for='exampleInputPassword1'>Time between withdrawals:</label>"+ " <label for='exampleInputPassword1'>Time between withdrawals:</label>"+
" <input id='edittme' type='number' class='form-control' placeholder='0' max='86400' value='"+ " <input id='edittme' type='number' class='form-control' placeholder='0' max='86400' value='"+
faudetails.tme + faudetails.tme +
"'></input>"+ "'></input>"+
"</div> </div>"+ "</div> </div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
"<label for='exampleInputEmail1'>Max withdraw:</label>"+ "<label for='exampleInputEmail1'>Max withdraw:</label>"+
" <input id='editmaxamt' type='number' class='form-control' placeholder='1' value='"+ " <input id='editmaxamt' type='number' class='form-control' placeholder='1' value='"+
faudetails.maxamt + faudetails.maxamt +
"'></input>"+ "'></input>"+
" </div></div>"+ " </div></div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
" <div class='form-group'>"+ " <div class='form-group'>"+
" <label for='exampleInputEmail1'>Min withdraw:</label>"+ " <label for='exampleInputEmail1'>Min withdraw:</label>"+
" <input id='editminamt' type='number' class='form-control' placeholder='1' value='"+ " <input id='editminamt' type='number' class='form-control' placeholder='1' value='"+
faudetails.minamt + faudetails.minamt +
"'></input>"+ "'></input>"+
" </div></div>"+ " </div></div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
"<div class='form-group'>"+ "<div class='form-group'>"+
" <label for='exampleInputPassword1'>Amount of uses:</label>"+ " <label for='exampleInputPassword1'>Amount of uses:</label>"+
" <input id='editamt' type='number' class='form-control' placeholder='1' value='"+ " <input id='editamt' type='number' class='form-control' placeholder='1' value='"+
faudetails.inc + faudetails.inc +
"'></input>"+ "'></input>"+
" </div> </div>"+ " </div> </div>"+
" <div class='col-sm-3 col-md-4'>"+ " <div class='col-sm-3 col-md-4'>"+
" <div class='checkbox'>"+ " <div class='checkbox'>"+
"<label data-toggle='tooltip' title='Some tooltip text!'><input id='edituniq' type='checkbox' "+ "<label data-toggle='tooltip' title='Some tooltip text!'><input id='edituniq' type='checkbox' "+
checkbox + checkbox +
">"+ ">"+
"Unique links</label>"+ "Unique links</label>"+
"</div></div><!-- /.box-body -->"+ "</div></div><!-- /.box-body -->"+
" </div><br/>"+ " </div><br/>"+
" <div class='box-footer'>"+ " <div class='box-footer'>"+
" <button onclick='editlinkcont()' type='button' class='btn btn-info'>Edit link(s)</button><p style='color:red;' id='error2'></p>"+ " <button onclick='editlinkcont()' type='button' class='btn btn-info'>Edit link(s)</button><p style='color:red;' id='error2'></p>"+
" </div></form></div><!-- /.box --></div></div>" " </div></form></div><!-- /.box --></div></div>"
} }
@ -465,48 +451,48 @@ function editlinkcont(){
uniq = document.getElementById('edituniq').checked uniq = document.getElementById('edituniq').checked
if (tit == "") { if (tit == "") {
document.getElementById("error2").innerHTML = "Only use letters in title" document.getElementById("error2").innerHTML = "Only use letters in title"
return amt return amt
} }
if (wal == "") { if (wal == "") {
document.getElementById("error2").innerHTML = "No wallet selected" document.getElementById("error2").innerHTML = "No wallet selected"
return amt return amt
} }
if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) { if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) {
document.getElementById("error2").innerHTML = "Max 10 - 1000000 and must be higher than min" document.getElementById("error2").innerHTML = "Max 10 - 1000000 and must be higher than min"
return amt return amt
} }
if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) { if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) {
document.getElementById("error2").innerHTML = "Min 1 - 1000000 and must be lower than max" document.getElementById("error2").innerHTML = "Min 1 - 1000000 and must be lower than max"
return amt return amt
} }
if (isNaN(amt) || amt < 1 || amt > 1000) { if (isNaN(amt) || amt < 1 || amt > 1000) {
document.getElementById("error2").innerHTML = "Amount of uses must be between 1 - 1000" document.getElementById("error2").innerHTML = "Amount of uses must be between 1 - 1000"
return amt return amt
} }
if (isNaN(tme) || tme < 1 || tme > 86400) { if (isNaN(tme) || tme < 1 || tme > 86400) {
document.getElementById("error2").innerHTML = "Max waiting time 1 day (86400 secs)" document.getElementById("error2").innerHTML = "Max waiting time 1 day (86400 secs)"
return amt return amt
} }
postAjax( postAjax(
'/withdrawmaker', "{{ url_for('withdraw.create') }}",
JSON.stringify({"id": unid, "tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}), JSON.stringify({"id": unid, "tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}),
"filla", "filla",
function(data) { location.replace("/withdraw?usr=" + user) function(data) { location.replace("{{ url_for('withdraw.index') }}?usr=" + user)
}) })
} }
</script> </script>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -21,7 +21,7 @@
/> />
<!-- Ionicons 2.0.0 --> <!-- Ionicons 2.0.0 -->
<link <link
href="http://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css" href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
/> />
@ -32,7 +32,7 @@
media="screen" media="screen"
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}" href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
/> />
<!-- AdminLTE Skins. Choose a skin from the css/skins <!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. --> folder instead of downloading all of them to reduce the load. -->
<link <link
@ -41,13 +41,6 @@
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}" href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
/> />
<!-- iCheck -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/iCheck/flat/blue.css') }}"
/>
<!-- Morris chart --> <!-- Morris chart -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -62,20 +55,6 @@
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}" href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
/> />
<!-- Date Picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/datepicker/datepicker3.css') }}"
/>
<!-- Daterange picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/daterangepicker/daterangepicker-bs3.css') }}"
/>
<!-- bootstrap wysihtml5 - text editor --> <!-- bootstrap wysihtml5 - text editor -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -130,7 +109,7 @@
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script> <script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
<!-- jQuery UI 1.11.2 --> <!-- jQuery UI 1.11.2 -->
<script <script
src="http://code.jquery.com/ui/1.11.2/jquery-ui.min.js" src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip --> <!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
@ -143,7 +122,7 @@
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- Morris.js charts --> <!-- Morris.js charts -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script <script
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}" src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
type="text/javascript" type="text/javascript"
@ -167,26 +146,11 @@
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}" src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- daterangepicker -->
<script
src="{{ url_for('static', filename='plugins/daterangepicker/daterangepicker.js') }}"
type="text/javascript"
></script>
<!-- datepicker -->
<script
src="{{ url_for('static', filename='plugins/datepicker/bootstrap-datepicker.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap WYSIHTML5 --> <!-- Bootstrap WYSIHTML5 -->
<script <script
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}" src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- iCheck -->
<script
src="{{ url_for('static', filename='plugins/iCheck/icheck.min.js') }}"
type="text/javascript"
></script>
<!-- Slimscroll --> <!-- Slimscroll -->
<script <script
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}" src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
@ -276,7 +240,7 @@
} }
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey) xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
xhr.setRequestHeader('Content-Type', 'application/json') xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send() xhr.send()
return xhr return xhr
} }
@ -304,24 +268,24 @@ allqr = ""
for (i = 0; i < lnurlamt; i++) { for (i = 0; i < lnurlamt; i++) {
allqr += "<div style='float:left;padding:20px; background-image: url(/static/note.jpg); width: 500px;height: 248px;'><div style='width:120px;float:right;margin-top:-16px;margin-right:-19px;background-color: white;'><div id='qrcode" + i + "'></div><center><p>{{user_fau[7]}} FREE SATS! <br/> <small style='font-size: 52%;'>SCAN AND FOLLOW LINK OR<br/>USE LN BITCOIN WALLET</small></p></center></div></div>" allqr += "<div style='float:left;padding:20px; background-image: url(/static/note.jpg); width: 500px;height: 248px;'><div style='width:120px;float:right;margin-top:-16px;margin-right:-19px;background-color: white;'><div id='qrcode" + i + "'></div><center><p>{{user_fau[7]}} FREE SATS! <br/> <small style='font-size: 52%;'>SCAN AND FOLLOW LINK OR<br/>USE LN BITCOIN WALLET</small></p></center></div></div>"
} }
document.getElementById("allqrs").innerHTML = allqr document.getElementById("allqrs").innerHTML = allqr
if (typeof lnurlar[1] != 'undefined'){ if (typeof lnurlar[1] != 'undefined'){
for (i = 0; i < lnurlamt; i++) { for (i = 0; i < lnurlamt; i++) {
drawwithdraw(lnurlar[i], "qrcode" + i) drawwithdraw(lnurlar[i], "qrcode" + i)
} }
window.print() window.print()
} }
else{ else{
for (i = 0; i < lnurlamt; i++) { for (i = 0; i < lnurlamt; i++) {
drawwithdraw(lnurlar[0], "qrcode" + i) drawwithdraw(lnurlar[0], "qrcode" + i)
} }
window.print() window.print()
} }
</script> </script>
</html> </html>

View File

@ -0,0 +1,160 @@
import uuid
from flask import jsonify, render_template, request, redirect, url_for
from lnurl import encode as lnurl_encode
from datetime import datetime
from lnbits.db import open_db, open_ext_db
from lnbits.extensions.withdraw import withdraw_ext
@withdraw_ext.route("/")
def index():
"""Main withdraw link page."""
usr = request.args.get("usr")
if usr:
if not len(usr) > 20:
return redirect(url_for("home"))
# Get all the data
with open_db() as db:
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
with open_ext_db() as ext_db:
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
# If del is selected by user from withdraw page, the withdraw link is to be deleted
faudel = request.args.get("del")
if faudel:
withdraw_ext_db.execute("DELETE FROM withdraws WHERE uni = ?", (faudel,))
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
return render_template(
"withdraw/index.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_fau=user_fau
)
@withdraw_ext.route("/create", methods=["GET", "POST"])
def create():
"""."""
data = request.json
amt = data["amt"]
tit = data["tit"]
wal = data["wal"]
minamt = data["minamt"]
maxamt = data["maxamt"]
tme = data["tme"]
uniq = data["uniq"]
usr = data["usr"]
wall = wal.split("-")
# Form validation
if (
int(amt) < 0
or not tit.replace(" ", "").isalnum()
or wal == ""
or int(minamt) < 0
or int(maxamt) < 0
or int(minamt) > int(maxamt)
or int(tme) < 0
):
return jsonify({"ERROR": "FORM ERROR"}), 401
# If id that means its a link being edited, delet the record first
if "id" in data:
unid = data["id"].split("-")
uni = unid[1]
with open_ext_db("withdraw") as withdraw_ext_db:
withdraw_ext_db.execute("DELETE FROM withdraws WHERE uni = ?", (unid[1],))
else:
uni = uuid.uuid4().hex
# Randomiser for random QR option
rand = ""
if uniq > 0:
for x in range(0, int(amt)):
rand += uuid.uuid4().hex[0:5] + ","
else:
rand = uuid.uuid4().hex[0:5] + ","
with open_db() as dbb:
user_wallets = dbb.fetchall("SELECT * FROM wallets WHERE user = ? AND id = ?", (usr, wall[1],))
if not user_wallets:
return jsonify({"ERROR": "NO WALLET USER"}), 401
# Get time
dt = datetime.now()
seconds = dt.timestamp()
# Add to DB
with open_ext_db("withdraw") as db:
db.execute(
"INSERT OR IGNORE INTO withdraws (usr, wal, walnme, adm, uni, tit, maxamt, minamt, spent, inc, tme, uniq, withdrawals, tmestmp, rand) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
usr,
wall[1],
user_wallets[0][1],
user_wallets[0][3],
uni,
tit,
maxamt,
minamt,
0,
amt,
tme,
uniq,
0,
seconds,
rand,
),
)
# Get updated records
with open_ext_db() as ext_db:
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
if not user_ext:
return jsonify({"ERROR": "NO WALLET USER"}), 401
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE usr = ?", (usr,))
if not user_fau:
return jsonify({"ERROR": "NO WALLET USER"}), 401
return render_template(
"withdraw/index.html", user_wallets=user_wallets, user=usr, user_ext=user_ext, user_fau=user_fau
)
@withdraw_ext.route("/display", methods=["GET", "POST"])
def display():
"""Simple shareable link."""
fauid = request.args.get("id")
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE uni = ?", (fauid,))
return render_template("withdraw/display.html", user_fau=user_fau,)
@withdraw_ext.route("/print/<urlstr>/", methods=["GET", "POST"])
def print_qr(urlstr):
"""Simple printable page of links."""
fauid = request.args.get("id")
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE uni = ?", (fauid,))
randar = user_fau[0][15].split(",")
randar = randar[:-1]
lnurlar = []
for d in range(len(randar)):
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=urlstr, parstr=fauid, rand=randar[d])
lnurlar.append(lnurl_encode(url.replace("http", "https")))
return render_template("withdraw/print.html", lnurlar=lnurlar, user_fau=user_fau[0],)

View File

@ -0,0 +1,116 @@
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 lnbits.db import open_ext_db
from lnbits.extensions.withdraw import withdraw_ext
@withdraw_ext.route("/api/v1/lnurlencode/<urlstr>/<parstr>", methods=["GET"])
def api_lnurlencode(urlstr, parstr):
"""Returns encoded LNURL if web url and parameter gieven."""
if not urlstr:
return jsonify({"status": "FALSE"}), 200
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(",")
# 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] - 2]
else:
rand = randar[0]
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=urlstr, parstr=parstr, rand=rand)
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url.replace("http", "https"))}), 200
@withdraw_ext.route("/api/v1/lnurlfetch/<urlstr>/<parstr>/<rand>", methods=["GET"])
def api_lnurlfetch(parstr, urlstr, rand):
"""Returns LNURL json."""
if not parstr:
return jsonify({"status": "FALSE", "ERROR": "NO WALL ID"}), 200
if not urlstr:
return jsonify({"status": "FALSE", "ERROR": "NO URL"}), 200
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,))
res = LnurlWithdrawResponse(
callback=url_for("withdraw.api_lnurlwithdraw", _external=True, rand=rand).replace("http", "https"),
k1=k1str,
min_withdrawable=user_fau[0][8] * 1000,
max_withdrawable=user_fau[0][7] * 1000,
default_description="LNbits LNURL withdraw",
)
return res.json(), 200
@withdraw_ext.route("/api/v1/lnurlwithdraw/<rand>/", methods=["GET"])
def api_lnurlwithdraw(rand):
"""Pays invoice if passed k1 invoice and rand."""
k1 = request.args.get("k1")
pr = request.args.get("pr")
if not k1:
return jsonify({"status": "FALSE", "ERROR": "NO k1"}), 200
if not pr:
return jsonify({"status": "FALSE", "ERROR": "NO PR"}), 200
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:
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}
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

View File

@ -3,14 +3,11 @@ import sqlite3
class MegaEncoder(json.JSONEncoder): class MegaEncoder(json.JSONEncoder):
def default(self, o): def default(self, obj):
if type(o) == sqlite3.Row: if isinstance(obj, sqlite3.Row):
val = {} return {k: obj[k] for k in obj.keys()}
for k in o.keys(): return obj
val[k] = o[k]
return val
return o
def megajson(o): def megajson(obj):
return json.dumps(o, cls=MegaEncoder) return json.dumps(obj, cls=MegaEncoder)

View File

@ -9,7 +9,7 @@ WALLET = OpenNodeWallet(endpoint=os.getenv("OPENNODE_API_ENDPOINT"),admin_key=os
LNBITS_PATH = os.path.dirname(os.path.realpath(__file__)) LNBITS_PATH = os.path.dirname(os.path.realpath(__file__))
DATABASE_PATH = os.getenv("DATABASE_PATH") or os.path.join(LNBITS_PATH, "data", "database.sqlite3") DATABASE_PATH = os.getenv("DATABASE_PATH", os.path.join(LNBITS_PATH, "data", "database.sqlite3"))
DEFAULT_USER_WALLET_NAME = os.getenv("DEFAULT_USER_WALLET_NAME") or "Bitcoin LN Wallet"
FEE_RESERVE = float(os.getenv("FEE_RESERVE") or 0) DEFAULT_USER_WALLET_NAME = os.getenv("DEFAULT_USER_WALLET_NAME", "Bitcoin LN Wallet")
FEE_RESERVE = float(os.getenv("FEE_RESERVE", 0))

View File

@ -48,7 +48,7 @@ function getAjax(url, thekey, success) {
} }
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey) xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
xhr.setRequestHeader('Content-Type', 'application/json') xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send() xhr.send()
return xhr return xhr
} }
@ -90,10 +90,10 @@ function sendfundspaste() {
'<br/>Memo: ' + '<br/>Memo: ' +
outmemo + outmemo +
'</h3>' + '</h3>' +
"<div class='input-group input-group-sm'><input type='text' id='invoiceinput' class='form-control' value='" + "<div class='input-group input-group-sm'><input type='text' id='invoiceinput' class='form-control' value='" +
invoice + invoice +
"'><span class='input-group-btn'><button class='btn btn-info btn-flat' type='button' onclick='copyfunc()'>Copy</button></span></div></br/></div>" + "'><span class='input-group-btn'><button class='btn btn-info btn-flat' type='button' onclick='copyfunc()'>Copy</button></span></div></br/></div>" +
"<div class='modal-footer'>"+ "<div class='modal-footer'>"+
"<button type='submit' class='btn btn-primary' onclick='sendfunds(" + "<button type='submit' class='btn btn-primary' onclick='sendfunds(" +
JSON.stringify(invoice) + JSON.stringify(invoice) +
")'>Send funds</button>" + ")'>Send funds</button>" +
@ -108,7 +108,7 @@ function receive() {
"<div class='modal-dialog' ><div id='QRCODE' ><div class='modal-content' style='padding: 0 10px 0 10px;'>"+ "<div class='modal-dialog' ><div id='QRCODE' ><div class='modal-content' style='padding: 0 10px 0 10px;'>"+
"<br/><center><input style='width:80%' type='number' class='form-control' id='amount' placeholder='Amount' max='1000000' required>" + "<br/><center><input style='width:80%' type='number' class='form-control' id='amount' placeholder='Amount' max='1000000' required>" +
"<input style='width:80%' type='text' class='form-control' id='memo' placeholder='Memo' required></center></div>" + "<input style='width:80%' type='text' class='form-control' id='memo' placeholder='Memo' required></center></div>" +
"<div class='modal-footer'>"+ "<div class='modal-footer'>"+
"<input type='button' id='getinvoice' onclick='received()' class='btn btn-primary' value='Create invoice' />" + "<input type='button' id='getinvoice' onclick='received()' class='btn btn-primary' value='Create invoice' />" +
'</div></div><br/>'+ '</div></div><br/>'+
"</div></div></div>" "</div></div></div>"
@ -120,7 +120,7 @@ function received() {
memo = document.getElementById('memo').value memo = document.getElementById('memo').value
amount = document.getElementById('amount').value amount = document.getElementById('amount').value
postAjax( postAjax(
'/v1/invoices', '/api/v1/invoices',
JSON.stringify({value: amount, memo: memo}), JSON.stringify({value: amount, memo: memo}),
wallet.inkey, wallet.inkey,
function(data) { function(data) {
@ -147,15 +147,15 @@ function received() {
document.getElementById("qrcode").style.padding = "20px"; document.getElementById("qrcode").style.padding = "20px";
setInterval(function(){ setInterval(function(){
getAjax('/v1/invoice/' + thehash, wallet.inkey, function(datab) { getAjax('/api/v1/invoice/' + thehash, wallet.inkey, function(datab) {
console.log(JSON.parse(datab).PAID) console.log(JSON.parse(datab).PAID)
if (JSON.parse(datab).PAID == 'TRUE') { if (JSON.parse(datab).PAID == 'TRUE') {
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
} }
})}, 3000); })}, 3000);
} }
) )
} }
@ -172,7 +172,7 @@ function processing() {
"<h3><b>Processing...</b></br/></br/></br/></h3></div>"+ "<h3><b>Processing...</b></br/></br/></br/></h3></div>"+
"</div></div></div>" "</div></div></div>"
window.top.location.href = "lnurlwallet?lightning=" + getQueryVariable("lightning"); window.top.location.href = "lnurlwallet?lightning=" + getQueryVariable("lightning");
} }
@ -195,15 +195,15 @@ function sendfunds(invoice) {
'<h3><b>Processing...</b></h3><</br/></br/></br/></div> '; '<h3><b>Processing...</b></h3><</br/></br/></br/></div> ';
postAjax( postAjax(
'/v1/channels/transactions', '/api/v1/channels/transactions',
JSON.stringify({payment_request: invoice}), JSON.stringify({payment_request: invoice}),
wallet.adminkey, wallet.adminkey,
function(data) { function(data) {
thehash = JSON.parse(data).payment_hash thehash = JSON.parse(data).payment_hash
setInterval(function(){ setInterval(function(){
getAjax('/v1/payment/' + thehash, wallet.adminkey, function(datab) { getAjax('/api/v1/payment/' + thehash, wallet.adminkey, function(datab) {
console.log(JSON.parse(datab).PAID) console.log(JSON.parse(datab).PAID)
if (JSON.parse(datab).PAID == 'TRUE') { if (JSON.parse(datab).PAID == 'TRUE') {
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
@ -220,7 +220,7 @@ function scanQRsend() {
"<div class='modal-content'>"+ "<div class='modal-content'>"+
"<br/><div id='loadingMessage'>🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>" + "<br/><div id='loadingMessage'>🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>" +
"<canvas id='canvas' hidden></canvas><div id='output' hidden><div id='outputMessage'></div>" + "<canvas id='canvas' hidden></canvas><div id='output' hidden><div id='outputMessage'></div>" +
"<br/><span id='outputData'></span></div></div><div class='modal-footer'>"+ "<br/><span id='outputData'></span></div></div><div class='modal-footer'>"+
"<button type='submit' class='btn btn-primary' onclick='cancelsend()'>Cancel</button><br/><br/>" "<button type='submit' class='btn btn-primary' onclick='cancelsend()'>Cancel</button><br/><br/>"
var video = document.createElement('video') var video = document.createElement('video')
var canvasElement = document.getElementById('canvas') var canvasElement = document.getElementById('canvas')
@ -270,7 +270,7 @@ function scanQRsend() {
outmemo = theinvoice.data.tags[1].value outmemo = theinvoice.data.tags[1].value
outstr = JSON.stringify(code.data.split(":")[1]) outstr = JSON.stringify(code.data.split(":")[1])
} }
if (code.data.substring(0, 4).toUpperCase() != "LNBC"){ if (code.data.substring(0, 4).toUpperCase() != "LNBC"){
document.getElementById('sendfunds2').innerHTML = document.getElementById('sendfunds2').innerHTML =
"<div class='row'><div class='col-md-6'>" + "<div class='row'><div class='col-md-6'>" +
@ -279,7 +279,7 @@ function scanQRsend() {
'</br/></br/></div></div>' '</br/></br/></div></div>'
} }
else{ else{
theinvoice = decode(code.data) theinvoice = decode(code.data)
outmemo = theinvoice.data.tags[1].value outmemo = theinvoice.data.tags[1].value
outstr = JSON.stringify(code.data) outstr = JSON.stringify(code.data)
@ -301,7 +301,7 @@ function scanQRsend() {
'<br/>Memo: ' + '<br/>Memo: ' +
outmemo + outmemo +
'</h3>' + '</h3>' +
"<div class='input-group input-group-sm'><input type='text' id='invoiceinput' class='form-control' value='" + "<div class='input-group input-group-sm'><input type='text' id='invoiceinput' class='form-control' value='" +
outstr + outstr +
"'><span class='input-group-btn'><button class='btn btn-info btn-flat' type='button' onclick='copyfunc()'>Copy</button></span></div></br/>" + "'><span class='input-group-btn'><button class='btn btn-info btn-flat' type='button' onclick='copyfunc()'>Copy</button></span></div></br/>" +
"<button type='submit' class='btn btn-primary' onclick='sendfunds(" + "<button type='submit' class='btn btn-primary' onclick='sendfunds(" +
@ -324,7 +324,7 @@ function scanQRsend() {
function copyfunc(){ function copyfunc(){
var copyText = document.getElementById("invoiceinput"); var copyText = document.getElementById("invoiceinput");
copyText.select(); copyText.select();
copyText.setSelectionRange(0, 99999); copyText.setSelectionRange(0, 99999);
document.execCommand("copy"); document.execCommand("copy");
} }
@ -414,7 +414,7 @@ if (transactions.length) {
} }
if (wallet) { if (wallet) {
postAjax('/v1/checkpending', '', wallet.adminkey, function(data) {}) postAjax('/api/v1/checkpending', '', wallet.adminkey, function(data) {})
} }
@ -447,14 +447,14 @@ function download_csv(csv, filename) {
function export_table_to_csv(html, filename) { function export_table_to_csv(html, filename) {
var csv = []; var csv = [];
var rows = document.querySelectorAll("table tr"); var rows = document.querySelectorAll("table tr");
for (var i = 0; i < rows.length; i++) { for (var i = 0; i < rows.length; i++) {
var row = [], cols = rows[i].querySelectorAll("td, th"); var row = [], cols = rows[i].querySelectorAll("td, th");
for (var j = 0; j < cols.length; j++) for (var j = 0; j < cols.length; j++)
row.push(cols[j].innerText); row.push(cols[j].innerText);
csv.push(row.join(",")); csv.push(row.join(","));
} }
// Download CSV // Download CSV

View File

@ -9,10 +9,10 @@
$(function () { $(function () {
//Activate the iCheck Plugin //Activate the iCheck Plugin
$('input[type="checkbox"]').iCheck({ /*$('input[type="checkbox"]').iCheck({
checkboxClass: 'icheckbox_flat-blue', checkboxClass: 'icheckbox_flat-blue',
radioClass: 'iradio_flat-blue' radioClass: 'iradio_flat-blue'
}); });*/
//Make the dashboard widgets sortable Using jquery UI //Make the dashboard widgets sortable Using jquery UI
$(".connectedSortable").sortable({ $(".connectedSortable").sortable({
placeholder: "sort-highlight", placeholder: "sort-highlight",
@ -33,6 +33,7 @@ $(function () {
//bootstrap WYSIHTML5 - text editor //bootstrap WYSIHTML5 - text editor
$(".textarea").wysihtml5(); $(".textarea").wysihtml5();
/*
$('.daterange').daterangepicker( $('.daterange').daterangepicker(
{ {
ranges: { ranges: {
@ -48,7 +49,7 @@ $(function () {
}, },
function (start, end) { function (start, end) {
alert("You chose: " + start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY')); alert("You chose: " + start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
}); });*/
/* jQueryKnob */ /* jQueryKnob */
$(".knob").knob(); $(".knob").knob();
@ -235,4 +236,4 @@ $(function () {
} }
}); });
}); });

View File

@ -23,7 +23,7 @@
/> />
<!-- Ionicons 2.0.0 --> <!-- Ionicons 2.0.0 -->
<link <link
href="http://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css" href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
rel="stylesheet" rel="stylesheet"
type="text/css" type="text/css"
/> />
@ -34,7 +34,7 @@
media="screen" media="screen"
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}" href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
/> />
<!-- AdminLTE Skins. Choose a skin from the css/skins <!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. --> folder instead of downloading all of them to reduce the load. -->
<link <link
@ -43,13 +43,6 @@
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}" href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
/> />
<!-- iCheck -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/iCheck/flat/blue.css') }}"
/>
<!-- Morris chart --> <!-- Morris chart -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -64,20 +57,6 @@
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}" href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
/> />
<!-- Date Picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/datepicker/datepicker3.css') }}"
/>
<!-- Daterange picker -->
<link
rel="stylesheet"
media="screen"
href="{{ url_for('static', filename='plugins/daterangepicker/daterangepicker-bs3.css') }}"
/>
<!-- bootstrap wysihtml5 - text editor --> <!-- bootstrap wysihtml5 - text editor -->
<link <link
rel="stylesheet" rel="stylesheet"
@ -129,7 +108,7 @@
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script> <script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
<!-- jQuery UI 1.11.2 --> <!-- jQuery UI 1.11.2 -->
<script <script
src="http://code.jquery.com/ui/1.11.2/jquery-ui.min.js" src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip --> <!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
@ -142,7 +121,7 @@
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- Morris.js charts --> <!-- Morris.js charts -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script <script
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}" src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
type="text/javascript" type="text/javascript"
@ -166,26 +145,11 @@
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}" src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- daterangepicker -->
<script
src="{{ url_for('static', filename='plugins/daterangepicker/daterangepicker.js') }}"
type="text/javascript"
></script>
<!-- datepicker -->
<script
src="{{ url_for('static', filename='plugins/datepicker/bootstrap-datepicker.js') }}"
type="text/javascript"
></script>
<!-- Bootstrap WYSIHTML5 --> <!-- Bootstrap WYSIHTML5 -->
<script <script
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}" src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
type="text/javascript" type="text/javascript"
></script> ></script>
<!-- iCheck -->
<script
src="{{ url_for('static', filename='plugins/iCheck/icheck.min.js') }}"
type="text/javascript"
></script>
<!-- Slimscroll --> <!-- Slimscroll -->
<script <script
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}" src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
@ -240,9 +204,9 @@
<style> <style>
//GOOFY CSS HACK TO GO DARK //GOOFY CSS HACK TO GO DARK
.skin-blue .wrapper { .skin-blue .wrapper {
background: background:
#1f2234; #1f2234;
} }
@ -257,40 +221,40 @@ body {
} }
.skin-blue .main-header .navbar { .skin-blue .main-header .navbar {
background-color: background-color:
#2e507d; #2e507d;
} }
.content-wrapper, .right-side { .content-wrapper, .right-side {
background-color: background-color:
#1f2234; #1f2234;
} }
.skin-blue .main-header .logo { .skin-blue .main-header .logo {
background-color: background-color:
#1f2234; #1f2234;
color: color:
#fff; #fff;
} }
.skin-blue .sidebar-menu > li.header { .skin-blue .sidebar-menu > li.header {
color: color:
#4b646f; #4b646f;
background: background:
#1f2234; #1f2234;
} }
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side { .skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
background: background:
#1f2234; #1f2234;
} }
.skin-blue .sidebar-menu > li > .treeview-menu { .skin-blue .sidebar-menu > li > .treeview-menu {
margin: 0 1px; margin: 0 1px;
background: background:
#1f2234; #1f2234;
} }
.skin-blue .sidebar-menu > li > a { .skin-blue .sidebar-menu > li > a {
border-left: 3px solid border-left: 3px solid
transparent; transparent;
margin-right: 1px; margin-right: 1px;
} }
@ -304,12 +268,12 @@ background:
.skin-blue .main-header .logo:hover { .skin-blue .main-header .logo:hover {
background: background:
#3e355a; #3e355a;
} }
.skin-blue .main-header .navbar .sidebar-toggle:hover { .skin-blue .main-header .navbar .sidebar-toggle:hover {
background-color: background-color:
#3e355a; #3e355a;
} }
.main-footer { .main-footer {
@ -324,13 +288,13 @@ background:
} }
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body { .bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
background-color: background-color:
#1f2234 !important; #1f2234 !important;
} }
.alert-danger, .alert-error { .alert-danger, .alert-error {
border-color: #fff; border-color: #fff;
border: 1px solid border: 1px solid
#fff; #fff;
border-radius: 7px; border-radius: 7px;
@ -338,12 +302,12 @@ background:
} }
.skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus { .skin-blue .main-header .navbar .nav > li > a:hover, .skin-blue .main-header .navbar .nav > li > a:active, .skin-blue .main-header .navbar .nav > li > a:focus, .skin-blue .main-header .navbar .nav .open > a, .skin-blue .main-header .navbar .nav .open > a:hover, .skin-blue .main-header .navbar .nav .open > a:focus {
color: color:
#f6f6f6; #f6f6f6;
background-color: #3e355a; background-color: #3e355a;
} }
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body { .bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
background-color: background-color:
#3e355a !important; #3e355a !important;
} }
@ -357,7 +321,7 @@ background:
} }
.table-striped > tbody > tr:nth-of-type(2n+1) { .table-striped > tbody > tr:nth-of-type(2n+1) {
background-color: background-color:
#333646; #333646;
} }
@ -386,7 +350,7 @@ a:hover, a:active, a:focus {
text-decoration: none; text-decoration: none;
color: #fff; color: #fff;
} }
// .modal.in .modal-dialog{ // .modal.in .modal-dialog{
// color:#000; // color:#000;
// } // }
@ -400,7 +364,7 @@ color: #fff;
border-top: none; border-top: none;
background-color: background-color:
#333646; #333646;
} }
.modal-footer { .modal-footer {
@ -422,7 +386,7 @@ background-color:
.h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small { .h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small {
font-weight: 400; font-weight: 400;
line-height: 1; line-height: 1;
color: color:
#fff; #fff;
} }
@ -438,7 +402,7 @@ body {
<div class="wrapper"> <div class="wrapper">
<header class="main-header"> <header class="main-header">
<!-- Logo --> <!-- Logo -->
<a href="/" class="logo"><b style="color: #8964a9;">LN</b>bits</a> <a href="{{ url_for('core.home') }}" class="logo"><b style="color: #8964a9;">LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less --> <!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation"> <nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button--> <!-- Sidebar toggle button-->

View File

@ -86,25 +86,25 @@
</div> </div>
</div> </div>
<!-- ./col --> <!-- ./col -->
<!-- /.row --> <!-- /.row -->
<div class="row"> <div class="row">
<div class="col-sm-3"> <div class="col-sm-3">
<button <button
onclick="sendfundsinput()" onclick="sendfundsinput()"
class="btn btn-block btn-primary btn-lg" class="btn btn-block btn-primary btn-lg"
data-toggle="modal" data-toggle="modal"
data-target=".sends" data-target=".sends"
> >
Send Send
</button> </button>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<button <button
onclick="receive()" onclick="receive()"
class="btn btn-block btn-primary btn-lg" class="btn btn-block btn-primary btn-lg"
data-toggle="modal" data-toggle="modal"
data-target=".receives" data-target=".receives"
> >
Receive Receive
</button> </button>
@ -173,7 +173,7 @@
<div class="box-body" style="word-wrap: break-word;"> <div class="box-body" style="word-wrap: break-word;">
<b>Admin key: </b><i>{{ wallet.adminkey }}</i><br /> <b>Admin key: </b><i>{{ wallet.adminkey }}</i><br />
<b>Invoice/Read key: </b><i>{{ wallet.inkey }}</i><br /> <b>Invoice/Read key: </b><i>{{ wallet.inkey }}</i><br />
Generate an invoice:<br /><code>POST /v1/invoices</code Generate an invoice:<br /><code>POST /api/v1/invoices</code
><br />Header ><br />Header
<code <code
>{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i >{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i
@ -187,7 +187,7 @@
Check invoice:<br /> Check invoice:<br />
Check an invoice:<br /><code Check an invoice:<br /><code
>GET /v1/invoice/*payment_hash*</code >GET /api/v1/invoice/*payment_hash*</code
><br />Header ><br />Header
<code <code
>{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i >{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i

View File

@ -16,7 +16,7 @@ class LndWallet(Wallet):
r = post( r = post(
url=f"{self.endpoint}/v1/invoices", url=f"{self.endpoint}/v1/invoices",
headers=self.auth_admin, headers=self.auth_admin,
json={"value": "100", "memo": memo, "private": True}, # , "private": True}, json={"value": "100", "memo": memo, "private": True},
) )
if r.ok: if r.ok:

View File

@ -12,18 +12,16 @@ class LNPayWallet(Wallet):
self.auth_invoice = invoice_key self.auth_invoice = invoice_key
self.auth_read = read_key self.auth_read = read_key
self.auth_api = {"X-Api-Key": api_key} self.auth_api = {"X-Api-Key": api_key}
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse:
payment_hash, payment_request = None, None payment_hash, payment_request = None, None
print(f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice")
r = post( r = post(
url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice", url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice",
headers=self.auth_api, headers=self.auth_api,
json={"num_satoshis": f"{amount}", "memo": memo}, json={"num_satoshis": f"{amount}", "memo": memo},
) )
print(r.json())
if r.ok: if r.ok:
data = r.json() data = r.json()
payment_hash, payment_request = data["id"], data["payment_request"] payment_hash, payment_request = data["id"], data["payment_request"]
@ -32,9 +30,10 @@ class LNPayWallet(Wallet):
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = post( r = post(
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw", url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw",
headers=self.auth_api, headers=self.auth_api,
json={"payment_request": bolt11}) json={"payment_request": bolt11},
)
return PaymentResponse(r, not r.ok) return PaymentResponse(r, not r.ok)
@ -45,8 +44,8 @@ class LNPayWallet(Wallet):
return TxStatus(r, None) return TxStatus(r, None)
statuses = {0: None, 1: True, -1: False} statuses = {0: None, 1: True, -1: False}
return TxStatus(r, statuses[r.json()["settled"]])
return TxStatus(r, statuses[r.json()["settled"]])
def get_payment_status(self, payment_hash: str) -> TxStatus: def get_payment_status(self, payment_hash: str) -> TxStatus:
r = get(url=f"{self.endpoint}/user/lntx/{payment_hash}", headers=self.auth_api) r = get(url=f"{self.endpoint}/user/lntx/{payment_hash}", headers=self.auth_api)
@ -55,4 +54,5 @@ class LNPayWallet(Wallet):
return TxStatus(r, None) return TxStatus(r, None)
statuses = {0: None, 1: True, -1: False} statuses = {0: None, 1: True, -1: False}
return TxStatus(r, statuses[r.json()["settled"]]) return TxStatus(r, statuses[r.json()["settled"]])