mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-01-18 21:32:38 +01:00
commit
8abdaaea48
1
Pipfile
1
Pipfile
@ -10,6 +10,7 @@ python_version = "3.7"
|
||||
bitstring = "*"
|
||||
lnurl = "*"
|
||||
flask = "*"
|
||||
flask-talisman = "*"
|
||||
requests = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
@ -1,40 +1,57 @@
|
||||
import uuid
|
||||
import os
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
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 flask_talisman import Talisman
|
||||
from lnurl import Lnurl, LnurlWithdrawResponse
|
||||
|
||||
from . import bolt11
|
||||
from .db import Database
|
||||
from .core import core_app
|
||||
from .db import open_db, open_ext_db
|
||||
from .extensions.withdraw import withdraw_ext
|
||||
from .helpers import megajson
|
||||
from .settings import LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME, FEE_RESERVE
|
||||
|
||||
|
||||
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
|
||||
|
||||
# blueprints
|
||||
app.register_blueprint(core_app)
|
||||
app.register_blueprint(withdraw_ext, url_prefix="/withdraw")
|
||||
|
||||
|
||||
@app.before_first_request
|
||||
def init():
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
with open(os.path.join(LNBITS_PATH, "data", "schema.sql")) as schemafile:
|
||||
for stmt in schemafile.read().split(";\n\n"):
|
||||
db.execute(stmt, [])
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
@app.route("/deletewallet")
|
||||
def deletewallet():
|
||||
user_id = request.args.get("usr")
|
||||
wallet_id = request.args.get("wal")
|
||||
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
db.execute(
|
||||
"""
|
||||
UPDATE wallets AS w
|
||||
@ -93,7 +110,7 @@ def lnurlwallet():
|
||||
data = r.json()
|
||||
break
|
||||
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
wallet_id = uuid.uuid4().hex
|
||||
user_id = uuid.uuid4().hex
|
||||
wallet_name = DEFAULT_USER_WALLET_NAME
|
||||
@ -118,7 +135,7 @@ def wallet():
|
||||
usr = request.args.get("usr")
|
||||
wallet_id = request.args.get("wal")
|
||||
wallet_name = request.args.get("nme")
|
||||
|
||||
|
||||
if usr:
|
||||
if not len(usr) > 20:
|
||||
return redirect(url_for("home"))
|
||||
@ -134,7 +151,7 @@ def wallet():
|
||||
# just wallet_name: create a user, then generate a wallet_id and create
|
||||
# nothing: create everything
|
||||
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
# ensure this user exists
|
||||
# -------------------------------
|
||||
|
||||
@ -218,18 +235,22 @@ def wallet():
|
||||
(wallet_id,),
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"wallet.html", user_wallets=user_wallets, wallet=wallet, user=usr, transactions=transactions,
|
||||
)
|
||||
return render_template(
|
||||
"wallet.html", user_wallets=user_wallets, wallet=wallet, user=usr, transactions=transactions,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/v1/invoices", methods=["GET", "POST"])
|
||||
@app.route("/api/v1/invoices", methods=["GET", "POST"])
|
||||
def api_invoices():
|
||||
if request.headers["Content-Type"] != "application/json":
|
||||
return jsonify({"ERROR": "MUST BE JSON"}), 400
|
||||
|
||||
postedjson = request.json
|
||||
|
||||
# Form validation
|
||||
if int(postedjson["value"]) < 0 or not postedjson["memo"].replace(" ", "").isalnum():
|
||||
return jsonify({"ERROR": "FORM ERROR"}), 401
|
||||
|
||||
if "value" not in postedjson:
|
||||
return jsonify({"ERROR": "NO VALUE"}), 400
|
||||
|
||||
@ -242,7 +263,7 @@ def api_invoices():
|
||||
if "memo" not in postedjson:
|
||||
return jsonify({"ERROR": "NO MEMO"}), 400
|
||||
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
wallet = db.fetchone(
|
||||
"SELECT id FROM wallets WHERE inkey = ? OR adminkey = ?",
|
||||
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],),
|
||||
@ -266,17 +287,19 @@ def api_invoices():
|
||||
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():
|
||||
|
||||
if request.headers["Content-Type"] != "application/json":
|
||||
return jsonify({"ERROR": "MUST BE JSON"}), 400
|
||||
|
||||
data = request.json
|
||||
|
||||
print(data)
|
||||
if "payment_request" not in data:
|
||||
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"],))
|
||||
|
||||
if not wallet:
|
||||
@ -325,12 +348,12 @@ def api_transactions():
|
||||
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):
|
||||
if request.headers["Content-Type"] != "application/json":
|
||||
return jsonify({"ERROR": "MUST BE JSON"}), 400
|
||||
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
payment = db.fetchone(
|
||||
"""
|
||||
SELECT pending
|
||||
@ -355,12 +378,12 @@ def api_checkinvoice(payhash):
|
||||
return jsonify({"PAID": "TRUE"}), 200
|
||||
|
||||
|
||||
@app.route("/v1/payment/<payhash>", methods=["GET"])
|
||||
@app.route("/api/v1/payment/<payhash>", methods=["GET"])
|
||||
def api_checkpayment(payhash):
|
||||
if request.headers["Content-Type"] != "application/json":
|
||||
return jsonify({"ERROR": "MUST BE JSON"}), 400
|
||||
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
payment = db.fetchone(
|
||||
"""
|
||||
SELECT pending
|
||||
@ -385,9 +408,9 @@ def api_checkpayment(payhash):
|
||||
return jsonify({"PAID": "TRUE"}), 200
|
||||
|
||||
|
||||
@app.route("/v1/checkpending", methods=["POST"])
|
||||
@app.route("/api/v1/checkpending", methods=["POST"])
|
||||
def api_checkpending():
|
||||
with Database() as db:
|
||||
with open_db() as db:
|
||||
for pendingtx in db.fetchall(
|
||||
"""
|
||||
SELECT
|
||||
@ -418,3 +441,44 @@ def api_checkpending():
|
||||
db.execute("UPDATE apipayments SET pending = 0 WHERE payhash = ?", (payhash,))
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
# Checks DB to see if the extensions are activated or not activated for the user
|
||||
@app.route("/extensions")
|
||||
def extensions():
|
||||
usr = request.args.get("usr")
|
||||
lnevents = request.args.get("lnevents")
|
||||
lnjoust = request.args.get("lnjoust")
|
||||
withdraw = request.args.get("withdraw")
|
||||
if usr:
|
||||
if not len(usr) > 20:
|
||||
return redirect(url_for("home"))
|
||||
|
||||
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,))
|
||||
if not user_ext:
|
||||
ext_db.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO overview (user) VALUES (?)
|
||||
""",
|
||||
(usr,),
|
||||
)
|
||||
return redirect(url_for("extensions", usr=usr))
|
||||
|
||||
if lnevents:
|
||||
if int(lnevents) != user_ext[0][1] and int(lnevents) < 2:
|
||||
ext_db.execute("UPDATE overview SET lnevents = ? WHERE user = ?", (int(lnevents), usr,))
|
||||
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
|
||||
if lnjoust:
|
||||
if int(lnjoust) != user_ext[0][2] and int(lnjoust) < 2:
|
||||
ext_db.execute("UPDATE overview SET lnjoust = ? WHERE user = ?", (int(lnjoust), usr,))
|
||||
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
|
||||
if withdraw:
|
||||
if int(withdraw) != user_ext[0][3] and int(withdraw) < 2:
|
||||
ext_db.execute("UPDATE overview SET withdraw = ? WHERE user = ?", (int(withdraw), usr,))
|
||||
user_ext = ext_db.fetchall("SELECT * FROM overview WHERE user = ?", (usr,))
|
||||
|
||||
return render_template("extensions.html", user_wallets=user_wallets, user=usr, user_ext=user_ext)
|
||||
|
@ -49,9 +49,9 @@ def decode(pr: str) -> Invoice:
|
||||
if tag == "d":
|
||||
invoice.description = trim_to_bytes(tagdata).decode("utf-8")
|
||||
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:
|
||||
invoice.payment_hash = hexlify(trim_to_bytes(tagdata)).decode('ascii')
|
||||
invoice.payment_hash = hexlify(trim_to_bytes(tagdata)).decode("ascii")
|
||||
|
||||
return invoice
|
||||
|
||||
|
8
lnbits/core/__init__.py
Normal file
8
lnbits/core/__init__.py
Normal 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
|
BIN
lnbits/core/static/favicon.ico
Normal file
BIN
lnbits/core/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
@ -1,9 +1,11 @@
|
||||
<!-- @format -->
|
||||
|
||||
{% extends "base.html" %} {% block menuitems %}
|
||||
<li>
|
||||
<a href="/"><i class="fa fa-book"></i> Home</a>
|
||||
</li>
|
||||
|
||||
<li><a href="https://where39.com/"><p>Where39 anon locations</p><img src="static/where39.png" style="width:170px"></a></li>
|
||||
<li><a href="https://github.com/arcbtc/Quickening"><p>The Quickening <$8 PoS</p><img src="static/quick.gif" style="width:170px"></a></li>
|
||||
<li><a href="http://jigawatt.co/"><p>Buy BTC stamps + electronics</p><img src="static/stamps.jpg" style="width:170px"></a></li>
|
||||
<li><a href="mailto:ben@arc.wales"><h3>Advertise here!</h3></a></li>
|
||||
{% endblock %} {% block body %}
|
||||
<!-- Right side column. Contains the navbar and content of the page -->
|
||||
<div class="content-wrapper">
|
||||
@ -11,16 +13,17 @@
|
||||
<section class="content-header">
|
||||
<ol class="breadcrumb">
|
||||
<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>
|
||||
</ol>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="alert alert-danger alert-dismissable">
|
||||
<h4>
|
||||
Warning - Wallet is still in BETA and very, very #reckless, please be
|
||||
careful with your funds!
|
||||
TESTING ONLY - wallet is still in BETA and very unstable
|
||||
</h4>
|
||||
</div>
|
||||
</div></div></div>
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
14
lnbits/core/views.py
Normal file
14
lnbits/core/views.py
Normal 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
0
lnbits/core/views_api.py
Normal file
17
lnbits/db.py
17
lnbits/db.py
@ -1,10 +1,13 @@
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from .settings import DATABASE_PATH
|
||||
from typing import Optional
|
||||
|
||||
from .settings import DATABASE_PATH, LNBITS_PATH
|
||||
|
||||
|
||||
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
|
||||
@ -30,3 +33,13 @@ class Database:
|
||||
"""Given a query, cursor.execute() it."""
|
||||
self.cursor.execute(query, values)
|
||||
self.connection.commit()
|
||||
|
||||
|
||||
def open_db(db_path: str = DATABASE_PATH) -> Database:
|
||||
return Database(db_path=db_path)
|
||||
|
||||
|
||||
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"))
|
||||
|
0
lnbits/extensions/events/README.md
Normal file
0
lnbits/extensions/events/README.md
Normal 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
|
0
lnbits/extensions/joust/README.md
Normal file
0
lnbits/extensions/joust/README.md
Normal 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
|
@ -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
|
BIN
lnbits/extensions/overview.sqlite3
Normal file
BIN
lnbits/extensions/overview.sqlite3
Normal file
Binary file not shown.
0
lnbits/extensions/withdraw/README.md
Normal file
0
lnbits/extensions/withdraw/README.md
Normal file
8
lnbits/extensions/withdraw/__init__.py
Normal file
8
lnbits/extensions/withdraw/__init__.py
Normal 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
|
0
lnbits/extensions/withdraw/crud.py
Normal file
0
lnbits/extensions/withdraw/crud.py
Normal file
BIN
lnbits/extensions/withdraw/database.sqlite3
Normal file
BIN
lnbits/extensions/withdraw/database.sqlite3
Normal file
Binary file not shown.
530
lnbits/extensions/withdraw/templates/withdraw/display.html
Normal file
530
lnbits/extensions/withdraw/templates/withdraw/display.html
Normal file
@ -0,0 +1,530 @@
|
||||
<!-- @format -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>LNBits Wallet</title>
|
||||
<meta
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
name="viewport"
|
||||
/>
|
||||
<!-- Bootstrap 3.3.2 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}"
|
||||
/>
|
||||
<!-- FontAwesome 4.3.0 -->
|
||||
<link
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<!-- Ionicons 2.0.0 -->
|
||||
<link
|
||||
href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
|
||||
<!-- Theme style -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
|
||||
/>
|
||||
<!-- AdminLTE Skins. Choose a skin from the css/skins
|
||||
folder instead of downloading all of them to reduce the load. -->
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
|
||||
/>
|
||||
|
||||
<!-- Morris chart -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='plugins/morris/morris.css') }}"
|
||||
/>
|
||||
|
||||
<!-- jvectormap -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
|
||||
/>
|
||||
|
||||
<!-- bootstrap wysihtml5 - text editor -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}"
|
||||
/>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<style>
|
||||
.small-box > .small-box-footer {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#loadingMessage {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#output {
|
||||
margin-top: 20px;
|
||||
background: #eee;
|
||||
padding: 10px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#output div {
|
||||
padding-bottom: 10px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#noQRFound {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- jQuery 2.1.3 -->
|
||||
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
|
||||
<!-- jQuery UI 1.11.2 -->
|
||||
<script
|
||||
src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
|
||||
<script>
|
||||
$.widget.bridge('uibutton', $.ui.button)
|
||||
</script>
|
||||
<!-- Bootstrap 3.3.2 JS -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Morris.js charts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Sparkline -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/sparkline/jquery.sparkline.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- jvectormap -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-world-mill-en.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- jQuery Knob Chart -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Bootstrap WYSIHTML5 -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Slimscroll -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- FastClick -->
|
||||
<script src="{{ url_for('static', filename='plugins/fastclick/fastclick.min.js') }}"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='dist/js/app.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
<!-- AdminLTE dashboard demo (This is only for demo purposes) -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='dist/js/pages/dashboard.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
<!-- AdminLTE for demo purposes -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='dist/js/demo.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/datatables/jquery.dataTables.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"
|
||||
/>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jscam/JS.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jscam/qrcode.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bolt11/decoder.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bolt11/utils.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
//GOOFY CSS HACK TO GO DARK
|
||||
|
||||
.skin-blue .wrapper {
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li.active > a {
|
||||
color: #fff;
|
||||
background:#1f2234;
|
||||
border-left-color:#8964a9;
|
||||
}
|
||||
|
||||
.skin-blue .main-header .navbar {
|
||||
background-color:
|
||||
#2e507d;
|
||||
}
|
||||
|
||||
.content-wrapper, .right-side {
|
||||
background-color:
|
||||
#1f2234;
|
||||
}
|
||||
.skin-blue .main-header .logo {
|
||||
background-color:
|
||||
#1f2234;
|
||||
color:
|
||||
#fff;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li.header {
|
||||
color:
|
||||
#4b646f;
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li > .treeview-menu {
|
||||
margin: 0 1px;
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li > a {
|
||||
border-left: 3px solid
|
||||
transparent;
|
||||
margin-right: 1px;
|
||||
}
|
||||
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
|
||||
|
||||
color: #fff;
|
||||
background:#3e355a;
|
||||
border-left-color:#8964a9;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.skin-blue .main-header .logo:hover {
|
||||
background:
|
||||
#3e355a;
|
||||
}
|
||||
|
||||
.skin-blue .main-header .navbar .sidebar-toggle:hover {
|
||||
background-color:
|
||||
#3e355a;
|
||||
}
|
||||
.main-footer {
|
||||
background-color: #1f2234;
|
||||
padding: 15px;
|
||||
color: #fff;
|
||||
border-top: 0px;
|
||||
}
|
||||
|
||||
.skin-blue .main-header .navbar {
|
||||
background-color: #1f2234;
|
||||
}
|
||||
|
||||
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
|
||||
background-color:
|
||||
#1f2234 !important;
|
||||
}
|
||||
.alert-danger, .alert-error {
|
||||
|
||||
border-color: #fff;
|
||||
border: 1px solid
|
||||
|
||||
#fff;
|
||||
border-radius: 7px;
|
||||
|
||||
}
|
||||
|
||||
.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:
|
||||
#f6f6f6;
|
||||
background-color: #3e355a;
|
||||
}
|
||||
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
|
||||
background-color:
|
||||
#3e355a !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
background-color: #333646;
|
||||
border-top: 3px solid #8964a9;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
.table-striped > tbody > tr:nth-of-type(2n+1) {
|
||||
background-color:
|
||||
#333646;
|
||||
}
|
||||
|
||||
.box-header {
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.box.box-danger {
|
||||
border-top-color: #8964a9;
|
||||
}
|
||||
.box.box-primary {
|
||||
border-top-color: #8964a9;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #8964a9;
|
||||
}
|
||||
.box-header.with-border {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a:hover, a:active, a:focus {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
}
|
||||
// .modal.in .modal-dialog{
|
||||
// color:#000;
|
||||
// }
|
||||
|
||||
.form-control {
|
||||
|
||||
background-color:#333646;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
.box-footer {
|
||||
|
||||
border-top: none;
|
||||
|
||||
background-color:
|
||||
#333646;
|
||||
}
|
||||
.modal-footer {
|
||||
|
||||
border-top: none;
|
||||
|
||||
}
|
||||
.modal-content {
|
||||
|
||||
background-color:
|
||||
#333646;
|
||||
}
|
||||
.modal.in .modal-dialog {
|
||||
|
||||
background-color: #333646;
|
||||
|
||||
}
|
||||
|
||||
.layout-boxed {
|
||||
background: none;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
background-color:
|
||||
#3e355a;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
|
||||
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body class="skin-blue layout-boxed sidebar-collapse sidebar-open">
|
||||
<div class="wrapper">
|
||||
<header class="main-header">
|
||||
<!-- Logo -->
|
||||
<a href="{{ url_for('core.home') }}" class="logo"><b>LN</b>bits</a>
|
||||
<!-- Header Navbar: style can be found in header.less -->
|
||||
<nav class="navbar navbar-static-top" role="navigation">
|
||||
<!-- Sidebar toggle button-->
|
||||
<a
|
||||
href="#"
|
||||
class="sidebar-toggle"
|
||||
data-toggle="offcanvas"
|
||||
role="button"
|
||||
>
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
</a>
|
||||
<div class="navbar-custom-menu">
|
||||
<ul class="nav navbar-nav">
|
||||
<!-- Messages: style can be found in dropdown.less-->
|
||||
<li class="dropdown messages-menu">
|
||||
|
||||
{% block messages %}{% endblock %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<aside class="main-sidebar">
|
||||
<!-- sidebar: style can be found in sidebar.less -->
|
||||
<section class="sidebar" style="height: auto;">
|
||||
<!-- Sidebar user panel -->
|
||||
|
||||
<!-- /.search form -->
|
||||
<!-- sidebar menu: : style can be found in sidebar.less -->
|
||||
<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/><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://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>
|
||||
|
||||
</ul>
|
||||
</section>
|
||||
<!-- /.sidebar -->
|
||||
</aside>
|
||||
|
||||
<!-- Right side column. Contains the navbar and content of the page -->
|
||||
<div class="content-wrapper">
|
||||
<!-- Content Header (Page header) -->
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
LNURL Withdraw Link
|
||||
<small>Use LNURL compatible bitcoin wallet</small>
|
||||
</h1>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content"><br/><br/>
|
||||
<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/>
|
||||
<div style="width:55%;word-wrap: break-word;" id="qrcodetxt"></div> <br/></center>
|
||||
|
||||
</section><!-- /.content -->
|
||||
</div><!-- /.content-wrapper -->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
function getAjax(url, thekey, success) {
|
||||
var xhr = window.XMLHttpRequest
|
||||
? new XMLHttpRequest()
|
||||
: new ActiveXObject('Microsoft.XMLHTTP')
|
||||
xhr.open('GET', url, true)
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState > 3 && xhr.status == 200) {
|
||||
success(xhr.responseText)
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
|
||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||
|
||||
xhr.send()
|
||||
return xhr
|
||||
}
|
||||
|
||||
|
||||
function drawwithdraw(data) {
|
||||
|
||||
|
||||
console.log(data)
|
||||
|
||||
|
||||
getAjax('/withdraw/api/v1/lnurlencode/'+ window.location.hostname + "/" + data, "filla", function(datab) {
|
||||
if (JSON.parse(datab).status == 'TRUE') {
|
||||
console.log(JSON.parse(datab).status)
|
||||
lnurlfau = (JSON.parse(datab).lnurl)
|
||||
|
||||
|
||||
new QRCode(document.getElementById('qrcode'), {
|
||||
text: lnurlfau,
|
||||
width: 300,
|
||||
height: 300,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
})
|
||||
document.getElementById("qrcode").style.backgroundColor = "white";
|
||||
document.getElementById("qrcode").style.padding = "20px";
|
||||
|
||||
document.getElementById('qrcodetxt').innerHTML = lnurlfau + "<br/><br/>"
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
data = "Failed to build LNURL"
|
||||
}
|
||||
})
|
||||
}
|
||||
drawwithdraw("{{ user_fau[0][5] }}")
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</html>
|
498
lnbits/extensions/withdraw/templates/withdraw/index.html
Normal file
498
lnbits/extensions/withdraw/templates/withdraw/index.html
Normal file
@ -0,0 +1,498 @@
|
||||
<!-- @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="{{ url_for('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>
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
{% if user_ext[0][3] %}
|
||||
<li>
|
||||
<a href="{{ url_for('withdraw.index') }}?usr={{ user_ext[0][0]}}"
|
||||
><i class="fa fa-plus"></i> LNURLw</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ url_for('extensions') }}?usr={{ user }}"
|
||||
>Manager </a>
|
||||
</li>
|
||||
|
||||
|
||||
</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>
|
||||
Withdraw link maker
|
||||
<small>powered by LNURL</small>
|
||||
|
||||
</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li>
|
||||
<a href="{{ url_for('wallet') }}?usr={{ user }}"><i class="fa fa-dashboard"></i> Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('extensions') }}?usr={{ user }}"><li class="fa fa-dashboard">Extensions</li></a>
|
||||
</li>
|
||||
<li>
|
||||
<i class="active" class="fa fa-dashboard">Withdraw link maker</i>
|
||||
</li>
|
||||
</ol>
|
||||
<br /><br />
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<!-- Small boxes (Stat box) -->
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<!-- general form elements -->
|
||||
<div class="box box-primary">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title"> Make a link</h3>
|
||||
</div><!-- /.box-header -->
|
||||
<!-- form start -->
|
||||
<form role="form">
|
||||
<div class="box-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Link title</label>
|
||||
<input id="tit" type="text" pattern="^[A-Za-z]+$" class="form-control" >
|
||||
</div>
|
||||
<!-- select -->
|
||||
<div class="form-group">
|
||||
<label>Select a wallet</label>
|
||||
<select id="wal" class="form-control">
|
||||
<option></option>
|
||||
{% for w in user_wallets %}
|
||||
<option>{{w.name}}-{{w.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Max withdraw:</label>
|
||||
<input id="maxamt" type="number" class="form-control" placeholder="1"></input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Min withdraw:</label>
|
||||
<input id="minamt" type="number" class="form-control" placeholder="1"></input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exampleInputPassword1">Amount of uses:</label>
|
||||
<input id="amt" type="number" class="form-control" placeholder="1"></input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exampleInputPassword1">Time between withdrawals:</label>
|
||||
<input id="tme" type="number" class="form-control" placeholder="0" max="86400"></input>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input id="uniq" type="checkbox"> Unique links
|
||||
</label>
|
||||
</div>
|
||||
</div><!-- /.box-body -->
|
||||
|
||||
<div class="box-footer">
|
||||
|
||||
<button onclick="postfau()" type="button" class="btn btn-info">Create link(s)</button><p style="color:red;" id="error"></p>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- /.box -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<!-- general form elements -->
|
||||
<div class="box box-primary">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Select a link</h3>
|
||||
</div><!-- /.box-header -->
|
||||
<form role="form">
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
|
||||
<select class="form-control" id="fauselect" onchange="drawwithdraw()">
|
||||
<option></option>
|
||||
{% for w in user_fau %}
|
||||
<option id="{{w.uni}}" >{{w.tit}}-{{w.uni}}-{{w.inc}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<center> <br/><div id="qrcode" style="width:340px" ></div><br/><div style="width:75%;word-wrap: break-word;" id="qrcodetxt" ></div></center>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div><!-- /.box -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Withdraw links <b id="withdraws"></b></h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body no-padding">
|
||||
<table id="pagnation" class="table table-bordered table-striped">
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th style="width:15%">Link/ID</th>
|
||||
<th style="width:15%">Max Withdraw</th>
|
||||
<th style="width:15%">No. uses</th>
|
||||
<th style="width:15%">Wait</th>
|
||||
<th style="width:15%">Wallet</th>
|
||||
<th style="width:10%">Edit</th>
|
||||
<th style="width:10%">Del</th>
|
||||
</tr>
|
||||
<tbody id="transactions"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="editlink"></div>
|
||||
|
||||
<!-- /.content -->
|
||||
</section>
|
||||
|
||||
<script>
|
||||
window.user = {{ user | megajson | safe }}
|
||||
window.user_wallets = {{ user_wallets | megajson | safe }}
|
||||
window.user_ext = {{ user_ext | megajson | safe }}
|
||||
window.user_fau = {{ user_fau | megajson | safe }}
|
||||
|
||||
const user_fau = window.user_fau
|
||||
console.log(user_fau)
|
||||
|
||||
|
||||
function drawChart(user_fau) {
|
||||
var transactionsHTML = ''
|
||||
|
||||
for (var i = 0; i < user_fau.length; i++) {
|
||||
var tx = user_fau[i]
|
||||
console.log(tx.nme)
|
||||
// make the transactions table
|
||||
transactionsHTML =
|
||||
"<tr><td style='width: 50%'>" +
|
||||
tx.tit +
|
||||
'</td><td >' +
|
||||
"<a href='" + "{{ url_for('withdraw.display') }}?id=" + tx.uni + "'>" + tx.uni.substring(0, 4) + "...</a>" +
|
||||
'</td><td>' +
|
||||
tx.maxamt +
|
||||
'</td><td>' +
|
||||
tx.inc +
|
||||
'</td><td>' +
|
||||
tx.tme +
|
||||
'</td><td>' +
|
||||
"<a href='{{ url_for('wallet') }}?usr="+ user +"'>" + tx.uni.substring(0, 4) + "...</a>" +
|
||||
'</td><td>' +
|
||||
"<i onclick='editlink("+ i +")'' class='fa fa-edit'></i>" +
|
||||
'</td><td>' +
|
||||
"<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>' +
|
||||
transactionsHTML
|
||||
document.getElementById('transactions').innerHTML = transactionsHTML
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (user_fau.length) {
|
||||
drawChart(user_fau)
|
||||
}
|
||||
|
||||
|
||||
//draws withdraw QR code
|
||||
function drawwithdraw() {
|
||||
|
||||
|
||||
walname = document.getElementById("fauselect").value
|
||||
|
||||
thewithdraw = walname.split("-");
|
||||
console.log(window.location.hostname + "-" + thewithdraw[1])
|
||||
|
||||
getAjax("/withdraw/api/v1/lnurlencode/"+ window.location.hostname + "/" + thewithdraw[1], "filla", function(datab) {
|
||||
if (JSON.parse(datab).STATUS == 'TRUE') {
|
||||
console.log(JSON.parse(datab).STATUS)
|
||||
lnurlfau = (JSON.parse(datab).LNURL)
|
||||
|
||||
|
||||
new QRCode(document.getElementById('qrcode'), {
|
||||
text: lnurlfau,
|
||||
width: 300,
|
||||
height: 300,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
})
|
||||
|
||||
if (thewithdraw[2] > 0){
|
||||
document.getElementById('qrcodetxt').innerHTML = lnurlfau
|
||||
+
|
||||
"<a target='_blank' href='{{ url_for('withdraw.display') }}?id=" + thewithdraw[1] + "'><h4>Shareable link</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.padding = "20px";
|
||||
}
|
||||
else{
|
||||
document.getElementById('qrcode').innerHTML = ""
|
||||
document.getElementById('qrcodetxt').innerHTML = "<h1>No more uses left in link!</h1><br/><br/>"
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
thewithdraw[1] = "Failed to build LNURL"
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
function postfau(){
|
||||
|
||||
wal = document.getElementById('wal').value
|
||||
tit = document.getElementById('tit').value
|
||||
amt = document.getElementById('amt').value
|
||||
maxamt = document.getElementById('maxamt').value
|
||||
minamt = document.getElementById('minamt').value
|
||||
tme = document.getElementById('tme').value
|
||||
uniq = document.getElementById('uniq').checked
|
||||
|
||||
|
||||
if (tit == "") {
|
||||
document.getElementById("error").innerHTML = "Only use letters in title"
|
||||
return amt
|
||||
}
|
||||
if (wal == "") {
|
||||
document.getElementById("error").innerHTML = "No wallet selected"
|
||||
return amt
|
||||
}
|
||||
|
||||
if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) {
|
||||
document.getElementById("error").innerHTML = "Max 15 - 1000000 and must be higher than min"
|
||||
return amt
|
||||
}
|
||||
if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) {
|
||||
document.getElementById("error").innerHTML = "Min 1 - 1000000 and must be lower than max"
|
||||
return amt
|
||||
}
|
||||
|
||||
if (isNaN(amt) || amt < 1 || amt > 1000) {
|
||||
document.getElementById("error").innerHTML = "Amount of uses must be between 1 - 1000"
|
||||
return amt
|
||||
}
|
||||
|
||||
if (isNaN(tme) || tme < 1 || tme > 86400) {
|
||||
document.getElementById("error").innerHTML = "Max waiting time 1 day (86400 secs)"
|
||||
return amt
|
||||
}
|
||||
|
||||
|
||||
postAjax(
|
||||
"{{ url_for('withdraw.create') }}",
|
||||
JSON.stringify({"tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}),
|
||||
"filla",
|
||||
|
||||
function(data) { location.replace("{{ url_for('withdraw.index') }}?usr=" + user)
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function editlink(linknum){
|
||||
|
||||
faudetails = user_fau[linknum]
|
||||
|
||||
console.log(faudetails)
|
||||
wallpick = ""
|
||||
|
||||
checkbox = ""
|
||||
if (faudetails.uniq == 1){
|
||||
checkbox = "checked"}
|
||||
|
||||
document.getElementById('editlink').innerHTML = "<div class='row'>"+
|
||||
"<div class='col-md-6'>"+
|
||||
" <!-- general form elements -->"+
|
||||
"<div class='box box-primary'>"+
|
||||
"<div class='box-header'>"+
|
||||
"<h3 class='box-title'> Edit: <i id='unid'>" + faudetails.tit + "-" + faudetails.uni + "</i> </h3>"+
|
||||
"<div class='box-tools pull-right'>" +
|
||||
"<button class='btn btn-box-tool' data-widget='remove'><i class='fa fa-times'></i></button>" +
|
||||
"</div>" +
|
||||
" </div><!-- /.box-header -->"+
|
||||
" <!-- form start -->"+
|
||||
"<form role='form'>"+
|
||||
"<div class='box-body'>"+
|
||||
"<div class='col-sm-3 col-md-4'>"+
|
||||
"<div class='form-group'>"+
|
||||
"<label for='exampleInputEmail1'>Link title</label>"+
|
||||
"<input id='edittit' type='text' class='form-control' value='"+
|
||||
faudetails.tit +
|
||||
"'></input> </div>"+
|
||||
" </div>"+
|
||||
" <div class='col-sm-4 col-md-4'>"+
|
||||
" <!-- select -->"+
|
||||
" <div class='form-group'>"+
|
||||
" <label>Select a wallet</label>"+
|
||||
"<select id='editwal' class='form-control'>"+
|
||||
" <option>" + faudetails.walnme + "-" + faudetails.wal + "</option>"+
|
||||
" {% for w in user_wallets %}"+
|
||||
|
||||
" <option>{{w.name}}-{{w.id}}</option>"+
|
||||
|
||||
" {% endfor %}"+
|
||||
" </select>"+
|
||||
" </div>"+
|
||||
" </div>"+
|
||||
" <div class='col-sm-3 col-md-4'>"+
|
||||
"<div class='form-group'>"+
|
||||
" <label for='exampleInputPassword1'>Time between withdrawals:</label>"+
|
||||
" <input id='edittme' type='number' class='form-control' placeholder='0' max='86400' value='"+
|
||||
faudetails.tme +
|
||||
"'></input>"+
|
||||
"</div> </div>"+
|
||||
" <div class='col-sm-3 col-md-4'>"+
|
||||
"<div class='form-group'>"+
|
||||
"<label for='exampleInputEmail1'>Max withdraw:</label>"+
|
||||
" <input id='editmaxamt' type='number' class='form-control' placeholder='1' value='"+
|
||||
faudetails.maxamt +
|
||||
"'></input>"+
|
||||
" </div></div>"+
|
||||
" <div class='col-sm-3 col-md-4'>"+
|
||||
" <div class='form-group'>"+
|
||||
" <label for='exampleInputEmail1'>Min withdraw:</label>"+
|
||||
" <input id='editminamt' type='number' class='form-control' placeholder='1' value='"+
|
||||
faudetails.minamt +
|
||||
"'></input>"+
|
||||
" </div></div>"+
|
||||
" <div class='col-sm-3 col-md-4'>"+
|
||||
"<div class='form-group'>"+
|
||||
" <label for='exampleInputPassword1'>Amount of uses:</label>"+
|
||||
" <input id='editamt' type='number' class='form-control' placeholder='1' value='"+
|
||||
faudetails.inc +
|
||||
"'></input>"+
|
||||
" </div> </div>"+
|
||||
" <div class='col-sm-3 col-md-4'>"+
|
||||
" <div class='checkbox'>"+
|
||||
"<label data-toggle='tooltip' title='Some tooltip text!'><input id='edituniq' type='checkbox' "+
|
||||
checkbox +
|
||||
">"+
|
||||
"Unique links</label>"+
|
||||
"</div></div><!-- /.box-body -->"+
|
||||
" </div><br/>"+
|
||||
" <div class='box-footer'>"+
|
||||
" <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>"
|
||||
|
||||
|
||||
}
|
||||
|
||||
function editlinkcont(){
|
||||
|
||||
unid = document.getElementById('unid').innerHTML
|
||||
wal = document.getElementById('editwal').value
|
||||
tit = document.getElementById('edittit').value
|
||||
amt = document.getElementById('editamt').value
|
||||
maxamt = document.getElementById('editmaxamt').value
|
||||
minamt = document.getElementById('editminamt').value
|
||||
tme = document.getElementById('edittme').value
|
||||
uniq = document.getElementById('edituniq').checked
|
||||
|
||||
|
||||
|
||||
if (tit == "") {
|
||||
document.getElementById("error2").innerHTML = "Only use letters in title"
|
||||
return amt
|
||||
}
|
||||
if (wal == "") {
|
||||
document.getElementById("error2").innerHTML = "No wallet selected"
|
||||
return amt
|
||||
}
|
||||
|
||||
if (isNaN(maxamt) || maxamt < 10 || maxamt > 1000000) {
|
||||
document.getElementById("error2").innerHTML = "Max 10 - 1000000 and must be higher than min"
|
||||
return amt
|
||||
}
|
||||
if (isNaN(minamt) || minamt < 1 || minamt > 1000000 || minamt > maxamt) {
|
||||
document.getElementById("error2").innerHTML = "Min 1 - 1000000 and must be lower than max"
|
||||
return amt
|
||||
}
|
||||
|
||||
if (isNaN(amt) || amt < 1 || amt > 1000) {
|
||||
document.getElementById("error2").innerHTML = "Amount of uses must be between 1 - 1000"
|
||||
return amt
|
||||
}
|
||||
|
||||
if (isNaN(tme) || tme < 1 || tme > 86400) {
|
||||
document.getElementById("error2").innerHTML = "Max waiting time 1 day (86400 secs)"
|
||||
return amt
|
||||
}
|
||||
|
||||
|
||||
postAjax(
|
||||
"{{ url_for('withdraw.create') }}",
|
||||
JSON.stringify({"id": unid, "tit": tit, "amt": amt, "maxamt": maxamt, "minamt": minamt, "tme": tme, "wal": wal, "usr": user, "uniq": uniq}),
|
||||
"filla",
|
||||
|
||||
function(data) { location.replace("{{ url_for('withdraw.index') }}?usr=" + user)
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
291
lnbits/extensions/withdraw/templates/withdraw/print.html
Normal file
291
lnbits/extensions/withdraw/templates/withdraw/print.html
Normal file
@ -0,0 +1,291 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="background-color:grey;">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>LNBits Wallet</title>
|
||||
<meta
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
name="viewport"
|
||||
/>
|
||||
<!-- Bootstrap 3.3.2 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}"
|
||||
/>
|
||||
<!-- FontAwesome 4.3.0 -->
|
||||
<link
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<!-- Ionicons 2.0.0 -->
|
||||
<link
|
||||
href="https://code.ionicframework.com/ionicons/2.0.0/css/ionicons.min.css"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
|
||||
<!-- Theme style -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='dist/css/AdminLTE.min.css') }}"
|
||||
/>
|
||||
<!-- AdminLTE Skins. Choose a skin from the css/skins
|
||||
folder instead of downloading all of them to reduce the load. -->
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='dist/css/skins/_all-skins.min.css') }}"
|
||||
/>
|
||||
|
||||
<!-- Morris chart -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='plugins/morris/morris.css') }}"
|
||||
/>
|
||||
|
||||
<!-- jvectormap -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.css') }}"
|
||||
/>
|
||||
|
||||
<!-- bootstrap wysihtml5 - text editor -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="screen"
|
||||
href="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}"
|
||||
/>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<style>
|
||||
.small-box > .small-box-footer {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#loadingMessage {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#output {
|
||||
margin-top: 20px;
|
||||
background: #eee;
|
||||
padding: 10px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#output div {
|
||||
padding-bottom: 10px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#noQRFound {
|
||||
text-align: center;
|
||||
}
|
||||
.layout-boxed {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- jQuery 2.1.3 -->
|
||||
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
|
||||
<!-- jQuery UI 1.11.2 -->
|
||||
<script
|
||||
src="https://code.jquery.com/ui/1.11.2/jquery-ui.min.js"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
|
||||
<script>
|
||||
$.widget.bridge('uibutton', $.ui.button)
|
||||
</script>
|
||||
<!-- Bootstrap 3.3.2 JS -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Morris.js charts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Sparkline -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/sparkline/jquery.sparkline.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- jvectormap -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-1.2.2.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jvectormap/jquery-jvectormap-world-mill-en.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- jQuery Knob Chart -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Bootstrap WYSIHTML5 -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Slimscroll -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- FastClick -->
|
||||
<script src="{{ url_for('static', filename='plugins/fastclick/fastclick.min.js') }}"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='dist/js/app.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
<!-- AdminLTE dashboard demo (This is only for demo purposes) -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='dist/js/pages/dashboard.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
<!-- AdminLTE for demo purposes -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='dist/js/demo.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/datatables/jquery.dataTables.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css"
|
||||
/>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jscam/JS.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/jscam/qrcode.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bolt11/decoder.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bolt11/utils.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
</head>
|
||||
<body class="skin-white layout-boxed sidebar-collapse sidebar-open">
|
||||
<div class="wrapper">
|
||||
|
||||
<style>
|
||||
body {
|
||||
width: 210mm;
|
||||
/* to centre page on screen*/
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-color:white;
|
||||
}
|
||||
.layout-boxed .wrapper {
|
||||
box-shadow: none;
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="allqrs"></div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
function getAjax(url, thekey, success) {
|
||||
var xhr = window.XMLHttpRequest
|
||||
? new XMLHttpRequest()
|
||||
: new ActiveXObject('Microsoft.XMLHTTP')
|
||||
xhr.open('GET', url, true)
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState > 3 && xhr.status == 200) {
|
||||
success(xhr.responseText)
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
|
||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||
|
||||
xhr.send()
|
||||
return xhr
|
||||
}
|
||||
|
||||
window.user_fau = {{ user_fau | megajson | safe }}
|
||||
|
||||
|
||||
function drawwithdraw(data, id) {
|
||||
|
||||
new QRCode(document.getElementById(id), {
|
||||
text: data,
|
||||
width: 120,
|
||||
height: 120,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
} )
|
||||
|
||||
|
||||
}
|
||||
lnurlar = {{ lnurlar|tojson }}
|
||||
lnurlamt = user_fau["inc"]
|
||||
console.log(user_fau)
|
||||
allqr = ""
|
||||
|
||||
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>"
|
||||
}
|
||||
|
||||
document.getElementById("allqrs").innerHTML = allqr
|
||||
|
||||
if (typeof lnurlar[1] != 'undefined'){
|
||||
for (i = 0; i < lnurlamt; i++) {
|
||||
drawwithdraw(lnurlar[i], "qrcode" + i)
|
||||
}
|
||||
window.print()
|
||||
}
|
||||
else{
|
||||
for (i = 0; i < lnurlamt; i++) {
|
||||
drawwithdraw(lnurlar[0], "qrcode" + i)
|
||||
}
|
||||
window.print()
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</html>
|
160
lnbits/extensions/withdraw/views.py
Normal file
160
lnbits/extensions/withdraw/views.py
Normal 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],)
|
116
lnbits/extensions/withdraw/views_api.py
Normal file
116
lnbits/extensions/withdraw/views_api.py
Normal 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
|
@ -3,14 +3,11 @@ import sqlite3
|
||||
|
||||
|
||||
class MegaEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if type(o) == sqlite3.Row:
|
||||
val = {}
|
||||
for k in o.keys():
|
||||
val[k] = o[k]
|
||||
return val
|
||||
return o
|
||||
def default(self, obj):
|
||||
if isinstance(obj, sqlite3.Row):
|
||||
return {k: obj[k] for k in obj.keys()}
|
||||
return obj
|
||||
|
||||
|
||||
def megajson(o):
|
||||
return json.dumps(o, cls=MegaEncoder)
|
||||
def megajson(obj):
|
||||
return json.dumps(obj, cls=MegaEncoder)
|
||||
|
@ -1,15 +1,15 @@
|
||||
import os
|
||||
|
||||
from .wallets import LntxbotWallet # OR LndWallet OR OpennodeWallet
|
||||
from .wallets import OpenNodeWallet # OR LndWallet OR OpennodeWallet
|
||||
|
||||
#WALLET = OpenNodeWallet(endpoint=os.getenv("OPENNODE_API_ENDPOINT"),admin_key=os.getenv("OPENNODE_ADMIN_KEY"),invoice_key=os.getenv("OPENNODE_INVOICE_KEY"))
|
||||
WALLET = LntxbotWallet(endpoint=os.getenv("LNTXBOT_API_ENDPOINT"),admin_key=os.getenv("LNTXBOT_ADMIN_KEY"),invoice_key=os.getenv("LNTXBOT_INVOICE_KEY"))
|
||||
WALLET = OpenNodeWallet(endpoint=os.getenv("OPENNODE_API_ENDPOINT"),admin_key=os.getenv("OPENNODE_ADMIN_KEY"),invoice_key=os.getenv("OPENNODE_INVOICE_KEY"))
|
||||
#WALLET = LntxbotWallet(endpoint=os.getenv("LNTXBOT_API_ENDPOINT"),admin_key=os.getenv("LNTXBOT_ADMIN_KEY"),invoice_key=os.getenv("LNTXBOT_INVOICE_KEY"))
|
||||
#WALLET = LndWallet(endpoint=os.getenv("LND_API_ENDPOINT"),admin_macaroon=os.getenv("LND_ADMIN_MACAROON"),invoice_macaroon=os.getenv("LND_INVOICE_MACAROON"),read_macaroon=os.getenv("LND_READ_MACAROON"))
|
||||
#WALLET = LNPayWallet(endpoint=os.getenv("LNPAY_API_ENDPOINT"),admin_key=os.getenv("LNPAY_ADMIN_KEY"),invoice_key=os.getenv("LNPAY_INVOICE_KEY"),api_key=os.getenv("LNPAY_API_KEY"),read_key=os.getenv("LNPAY_READ_KEY"))
|
||||
|
||||
|
||||
LNBITS_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
DATABASE_PATH = os.getenv("DATABASE_PATH") or os.path.join(LNBITS_PATH, "data", "database.sqlite3")
|
||||
DEFAULT_USER_WALLET_NAME = os.getenv("DEFAULT_USER_WALLET_NAME") or "Bitcoin LN Wallet"
|
||||
DATABASE_PATH = os.getenv("DATABASE_PATH", os.path.join(LNBITS_PATH, "data", "database.sqlite3"))
|
||||
|
||||
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))
|
||||
|
@ -48,7 +48,7 @@ function getAjax(url, thekey, success) {
|
||||
}
|
||||
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey)
|
||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||
|
||||
|
||||
xhr.send()
|
||||
return xhr
|
||||
}
|
||||
@ -90,10 +90,10 @@ function sendfundspaste() {
|
||||
'<br/>Memo: ' +
|
||||
outmemo +
|
||||
'</h3>' +
|
||||
"<div class='input-group input-group-sm'><input type='text' id='invoiceinput' class='form-control' value='" +
|
||||
invoice +
|
||||
"<div class='input-group input-group-sm'><input type='text' id='invoiceinput' class='form-control' value='" +
|
||||
invoice +
|
||||
"'><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(" +
|
||||
JSON.stringify(invoice) +
|
||||
")'>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;'>"+
|
||||
"<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>" +
|
||||
"<div class='modal-footer'>"+
|
||||
"<div class='modal-footer'>"+
|
||||
"<input type='button' id='getinvoice' onclick='received()' class='btn btn-primary' value='Create invoice' />" +
|
||||
'</div></div><br/>'+
|
||||
"</div></div></div>"
|
||||
@ -120,7 +120,7 @@ function received() {
|
||||
memo = document.getElementById('memo').value
|
||||
amount = document.getElementById('amount').value
|
||||
postAjax(
|
||||
'/v1/invoices',
|
||||
'/api/v1/invoices',
|
||||
JSON.stringify({value: amount, memo: memo}),
|
||||
wallet.inkey,
|
||||
function(data) {
|
||||
@ -147,15 +147,15 @@ function received() {
|
||||
document.getElementById("qrcode").style.padding = "20px";
|
||||
|
||||
|
||||
setInterval(function(){
|
||||
getAjax('/v1/invoice/' + thehash, wallet.inkey, function(datab) {
|
||||
setInterval(function(){
|
||||
getAjax('/api/v1/invoice/' + thehash, wallet.inkey, function(datab) {
|
||||
console.log(JSON.parse(datab).PAID)
|
||||
if (JSON.parse(datab).PAID == 'TRUE') {
|
||||
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
|
||||
}
|
||||
})}, 3000);
|
||||
|
||||
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -172,7 +172,7 @@ function processing() {
|
||||
"<h3><b>Processing...</b></br/></br/></br/></h3></div>"+
|
||||
"</div></div></div>"
|
||||
|
||||
|
||||
|
||||
window.top.location.href = "lnurlwallet?lightning=" + getQueryVariable("lightning");
|
||||
}
|
||||
|
||||
@ -195,15 +195,15 @@ function sendfunds(invoice) {
|
||||
'<h3><b>Processing...</b></h3><</br/></br/></br/></div> ';
|
||||
|
||||
postAjax(
|
||||
'/v1/channels/transactions',
|
||||
'/api/v1/channels/transactions',
|
||||
JSON.stringify({payment_request: invoice}),
|
||||
wallet.adminkey,
|
||||
|
||||
function(data) {
|
||||
thehash = JSON.parse(data).payment_hash
|
||||
|
||||
setInterval(function(){
|
||||
getAjax('/v1/payment/' + thehash, wallet.adminkey, function(datab) {
|
||||
setInterval(function(){
|
||||
getAjax('/api/v1/payment/' + thehash, wallet.adminkey, function(datab) {
|
||||
console.log(JSON.parse(datab).PAID)
|
||||
if (JSON.parse(datab).PAID == 'TRUE') {
|
||||
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user
|
||||
@ -220,7 +220,7 @@ function scanQRsend() {
|
||||
"<div class='modal-content'>"+
|
||||
"<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>" +
|
||||
"<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/>"
|
||||
var video = document.createElement('video')
|
||||
var canvasElement = document.getElementById('canvas')
|
||||
@ -270,7 +270,7 @@ function scanQRsend() {
|
||||
outmemo = theinvoice.data.tags[1].value
|
||||
outstr = JSON.stringify(code.data.split(":")[1])
|
||||
}
|
||||
|
||||
|
||||
if (code.data.substring(0, 4).toUpperCase() != "LNBC"){
|
||||
document.getElementById('sendfunds2').innerHTML =
|
||||
"<div class='row'><div class='col-md-6'>" +
|
||||
@ -279,7 +279,7 @@ function scanQRsend() {
|
||||
'</br/></br/></div></div>'
|
||||
}
|
||||
else{
|
||||
|
||||
|
||||
theinvoice = decode(code.data)
|
||||
outmemo = theinvoice.data.tags[1].value
|
||||
outstr = JSON.stringify(code.data)
|
||||
@ -301,7 +301,7 @@ function scanQRsend() {
|
||||
'<br/>Memo: ' +
|
||||
outmemo +
|
||||
'</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 +
|
||||
"'><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(" +
|
||||
@ -324,7 +324,7 @@ function scanQRsend() {
|
||||
function copyfunc(){
|
||||
var copyText = document.getElementById("invoiceinput");
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999);
|
||||
copyText.setSelectionRange(0, 99999);
|
||||
document.execCommand("copy");
|
||||
|
||||
}
|
||||
@ -414,7 +414,56 @@ if (transactions.length) {
|
||||
}
|
||||
|
||||
if (wallet) {
|
||||
postAjax('/v1/checkpending', '', wallet.adminkey, function(data) {})
|
||||
postAjax('/api/v1/checkpending', '', wallet.adminkey, function(data) {})
|
||||
}
|
||||
|
||||
|
||||
function download_csv(csv, filename) {
|
||||
var csvFile;
|
||||
var downloadLink;
|
||||
|
||||
// CSV FILE
|
||||
csvFile = new Blob([csv], {type: "text/csv"});
|
||||
|
||||
// Download link
|
||||
downloadLink = document.createElement("a");
|
||||
|
||||
// File name
|
||||
downloadLink.download = filename;
|
||||
|
||||
// We have to create a link to the file
|
||||
downloadLink.href = window.URL.createObjectURL(csvFile);
|
||||
|
||||
// Make sure that the link is not displayed
|
||||
downloadLink.style.display = "none";
|
||||
|
||||
// Add the link to your DOM
|
||||
document.body.appendChild(downloadLink);
|
||||
|
||||
// Lanzamos
|
||||
downloadLink.click();
|
||||
}
|
||||
|
||||
function export_table_to_csv(html, filename) {
|
||||
var csv = [];
|
||||
var rows = document.querySelectorAll("table tr");
|
||||
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = [], cols = rows[i].querySelectorAll("td, th");
|
||||
|
||||
for (var j = 0; j < cols.length; j++)
|
||||
row.push(cols[j].innerText);
|
||||
|
||||
csv.push(row.join(","));
|
||||
}
|
||||
|
||||
// Download CSV
|
||||
download_csv(csv.join("\n"), filename);
|
||||
}
|
||||
|
||||
function exportbut(){
|
||||
var html = document.querySelector("table").outerHTML;
|
||||
export_table_to_csv(html, "table.csv");
|
||||
}
|
||||
|
||||
|
||||
|
9
lnbits/static/dist/js/pages/dashboard.js
vendored
9
lnbits/static/dist/js/pages/dashboard.js
vendored
@ -9,10 +9,10 @@
|
||||
$(function () {
|
||||
|
||||
//Activate the iCheck Plugin
|
||||
$('input[type="checkbox"]').iCheck({
|
||||
/*$('input[type="checkbox"]').iCheck({
|
||||
checkboxClass: 'icheckbox_flat-blue',
|
||||
radioClass: 'iradio_flat-blue'
|
||||
});
|
||||
});*/
|
||||
//Make the dashboard widgets sortable Using jquery UI
|
||||
$(".connectedSortable").sortable({
|
||||
placeholder: "sort-highlight",
|
||||
@ -33,6 +33,7 @@ $(function () {
|
||||
//bootstrap WYSIHTML5 - text editor
|
||||
$(".textarea").wysihtml5();
|
||||
|
||||
/*
|
||||
$('.daterange').daterangepicker(
|
||||
{
|
||||
ranges: {
|
||||
@ -48,7 +49,7 @@ $(function () {
|
||||
},
|
||||
function (start, end) {
|
||||
alert("You chose: " + start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
|
||||
});
|
||||
});*/
|
||||
|
||||
/* jQueryKnob */
|
||||
$(".knob").knob();
|
||||
@ -235,4 +236,4 @@ $(function () {
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
BIN
lnbits/static/note.jpg
Normal file
BIN
lnbits/static/note.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
BIN
lnbits/static/quick.gif
Normal file
BIN
lnbits/static/quick.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
BIN
lnbits/static/stamps.jpg
Normal file
BIN
lnbits/static/stamps.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
lnbits/static/where39.png
Normal file
BIN
lnbits/static/where39.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
@ -23,7 +23,7 @@
|
||||
/>
|
||||
<!-- Ionicons 2.0.0 -->
|
||||
<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"
|
||||
type="text/css"
|
||||
/>
|
||||
@ -34,7 +34,7 @@
|
||||
media="screen"
|
||||
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. -->
|
||||
|
||||
<link
|
||||
@ -43,13 +43,6 @@
|
||||
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 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
@ -64,20 +57,6 @@
|
||||
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 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
@ -129,7 +108,7 @@
|
||||
<script src="{{ url_for('static', filename='plugins/jQuery/jQuery-2.1.3.min.js') }}"></script>
|
||||
<!-- jQuery UI 1.11.2 -->
|
||||
<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"
|
||||
></script>
|
||||
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
|
||||
@ -142,7 +121,7 @@
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- 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
|
||||
src="{{ url_for('static', filename='plugins/morris/morris.min.js') }}"
|
||||
type="text/javascript"
|
||||
@ -166,26 +145,11 @@
|
||||
src="{{ url_for('static', filename='plugins/knob/jquery.knob.js') }}"
|
||||
type="text/javascript"
|
||||
></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 -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- iCheck -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/iCheck/icheck.min.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<!-- Slimscroll -->
|
||||
<script
|
||||
src="{{ url_for('static', filename='plugins/slimScroll/jquery.slimscroll.min.js') }}"
|
||||
@ -237,12 +201,208 @@
|
||||
src="{{ url_for('static', filename='plugins/bolt11/utils.js') }}"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
<style>
|
||||
|
||||
//GOOFY CSS HACK TO GO DARK
|
||||
|
||||
.skin-blue .wrapper {
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li.active > a {
|
||||
color: #fff;
|
||||
background:#1f2234;
|
||||
border-left-color:#8964a9;
|
||||
}
|
||||
|
||||
.skin-blue .main-header .navbar {
|
||||
background-color:
|
||||
#2e507d;
|
||||
}
|
||||
|
||||
.content-wrapper, .right-side {
|
||||
background-color:
|
||||
#1f2234;
|
||||
}
|
||||
.skin-blue .main-header .logo {
|
||||
background-color:
|
||||
#1f2234;
|
||||
color:
|
||||
#fff;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li.header {
|
||||
color:
|
||||
#4b646f;
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
.skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side {
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li > .treeview-menu {
|
||||
margin: 0 1px;
|
||||
background:
|
||||
#1f2234;
|
||||
}
|
||||
|
||||
.skin-blue .sidebar-menu > li > a {
|
||||
border-left: 3px solid
|
||||
transparent;
|
||||
margin-right: 1px;
|
||||
}
|
||||
.skin-blue .sidebar-menu > li > a:hover, .skin-blue .sidebar-menu > li.active > a {
|
||||
|
||||
color: #fff;
|
||||
background:#3e355a;
|
||||
border-left-color:#8964a9;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.skin-blue .main-header .logo:hover {
|
||||
background:
|
||||
#3e355a;
|
||||
}
|
||||
|
||||
.skin-blue .main-header .navbar .sidebar-toggle:hover {
|
||||
background-color:
|
||||
#3e355a;
|
||||
}
|
||||
.main-footer {
|
||||
background-color: #1f2234;
|
||||
padding: 15px;
|
||||
color: #fff;
|
||||
border-top: 0px;
|
||||
}
|
||||
|
||||
.skin-blue .main-header .navbar {
|
||||
background-color: #1f2234;
|
||||
}
|
||||
|
||||
.bg-red, .callout.callout-danger, .alert-danger, .alert-error, .label-danger, .modal-danger .modal-body {
|
||||
background-color:
|
||||
#1f2234 !important;
|
||||
}
|
||||
.alert-danger, .alert-error {
|
||||
|
||||
border-color: #fff;
|
||||
border: 1px solid
|
||||
|
||||
#fff;
|
||||
border-radius: 7px;
|
||||
|
||||
}
|
||||
|
||||
.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:
|
||||
#f6f6f6;
|
||||
background-color: #3e355a;
|
||||
}
|
||||
.bg-aqua, .callout.callout-info, .alert-info, .label-info, .modal-info .modal-body {
|
||||
background-color:
|
||||
#3e355a !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
background-color: #333646;
|
||||
border-top: 3px solid #8964a9;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
.table-striped > tbody > tr:nth-of-type(2n+1) {
|
||||
background-color:
|
||||
#333646;
|
||||
}
|
||||
|
||||
.box-header {
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.box.box-danger {
|
||||
border-top-color: #8964a9;
|
||||
}
|
||||
.box.box-primary {
|
||||
border-top-color: #8964a9;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #8964a9;
|
||||
}
|
||||
.box-header.with-border {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a:hover, a:active, a:focus {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
}
|
||||
// .modal.in .modal-dialog{
|
||||
// color:#000;
|
||||
// }
|
||||
|
||||
.form-control {
|
||||
|
||||
background-color:#333646;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
.box-footer {
|
||||
|
||||
border-top: none;
|
||||
|
||||
background-color:
|
||||
#333646;
|
||||
}
|
||||
.modal-footer {
|
||||
|
||||
border-top: none;
|
||||
|
||||
}
|
||||
.modal-content {
|
||||
|
||||
background-color:
|
||||
#333646;
|
||||
}
|
||||
.modal.in .modal-dialog {
|
||||
|
||||
background-color: #333646;
|
||||
|
||||
}
|
||||
|
||||
.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;
|
||||
line-height: 1;
|
||||
color:
|
||||
#fff;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
background-color: #1f2234;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="skin-blue">
|
||||
<div class="wrapper">
|
||||
<header class="main-header">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="logo"><b>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 -->
|
||||
<nav class="navbar navbar-static-top" role="navigation">
|
||||
<!-- Sidebar toggle button-->
|
||||
|
165
lnbits/templates/extensions.html
Normal file
165
lnbits/templates/extensions.html
Normal file
@ -0,0 +1,165 @@
|
||||
<!-- @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>
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</a>
|
||||
<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] %}
|
||||
<li>
|
||||
<a href="withdraw?usr={{ user_ext[0][0]}}"
|
||||
><i class="fa fa-plus"></i> LNURLw</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="extensions?usr={{ user }}"
|
||||
>Manager </a>
|
||||
</li>
|
||||
|
||||
|
||||
</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][3] %}
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-blue">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
LNURLw
|
||||
</h3>
|
||||
<p>
|
||||
Make LNURL withdraw links
|
||||
</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-beer"></i>
|
||||
</div>
|
||||
<a href="extensions?usr={{user}}&withdraw=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-blue">
|
||||
|
||||
<div class="inner">
|
||||
<a href="withdraw?usr={{user}}" style="color: inherit;">
|
||||
<h3>
|
||||
LNURLw
|
||||
</h3>
|
||||
<p>
|
||||
Make LNURL withdraw links
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="icon">
|
||||
|
||||
<i class="ion ion-beer"></i>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
<a href="extensions?usr={{user}}&withdraw=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 %}
|
BIN
lnbits/templates/note.jpg
Normal file
BIN
lnbits/templates/note.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
@ -29,11 +29,13 @@
|
||||
</li>
|
||||
|
||||
<li class="treeview">
|
||||
<a href="#">
|
||||
<a href="extensions?usr={{ user }}">
|
||||
<i class="fa fa-th"></i> <span>Extensions</span>
|
||||
<small class="label pull-right bg-green">coming soon</small>
|
||||
</a>
|
||||
</li>
|
||||
<i class="fa fa-angle-left pull-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
{% endblock %} {% block body %}
|
||||
<!-- Right side column. Contains the navbar and content of the page -->
|
||||
<div class="content-wrapper">
|
||||
@ -56,11 +58,14 @@
|
||||
</li>
|
||||
</ol>
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="alert alert-danger alert-dismissable">
|
||||
<h4>
|
||||
Bookmark to save your wallet. Wallet is in BETA, use with caution.
|
||||
Wallet in BETA, use with caution.
|
||||
</h4>
|
||||
</div>
|
||||
</div></div>
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
@ -88,18 +93,18 @@
|
||||
<button
|
||||
onclick="sendfundsinput()"
|
||||
class="btn btn-block btn-primary btn-lg"
|
||||
data-toggle="modal"
|
||||
data-target=".sends"
|
||||
data-toggle="modal"
|
||||
data-target=".sends"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<button
|
||||
onclick="receive()"
|
||||
<button
|
||||
onclick="receive()"
|
||||
class="btn btn-block btn-primary btn-lg"
|
||||
data-toggle="modal"
|
||||
data-target=".receives"
|
||||
data-toggle="modal"
|
||||
data-target=".receives"
|
||||
>
|
||||
Receive
|
||||
</button>
|
||||
@ -112,12 +117,12 @@
|
||||
<div id="sendfunds"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Transactions <b id="demo"></b></h3>
|
||||
<h3 class="box-title">Transactions <small style="padding-left:30px;cursor: pointer;" onclick="exportbut()"><i>(Export to CSV)</i></small></h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body no-padding">
|
||||
@ -168,7 +173,7 @@
|
||||
<div class="box-body" style="word-wrap: break-word;">
|
||||
<b>Admin key: </b><i>{{ wallet.adminkey }}</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
|
||||
<code
|
||||
>{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i
|
||||
@ -182,7 +187,7 @@
|
||||
|
||||
Check invoice:<br />
|
||||
Check an invoice:<br /><code
|
||||
>GET /v1/invoice/*payment_hash*</code
|
||||
>GET /api/v1/invoice/*payment_hash*</code
|
||||
><br />Header
|
||||
<code
|
||||
>{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i
|
||||
|
@ -16,7 +16,7 @@ class LndWallet(Wallet):
|
||||
r = post(
|
||||
url=f"{self.endpoint}/v1/invoices",
|
||||
headers=self.auth_admin,
|
||||
json={"value": "100", "memo": memo, "private": True}, # , "private": True},
|
||||
json={"value": "100", "memo": memo, "private": True},
|
||||
)
|
||||
|
||||
if r.ok:
|
||||
|
@ -12,18 +12,16 @@ class LNPayWallet(Wallet):
|
||||
self.auth_invoice = invoice_key
|
||||
self.auth_read = read_key
|
||||
self.auth_api = {"X-Api-Key": api_key}
|
||||
|
||||
|
||||
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse:
|
||||
payment_hash, payment_request = None, None
|
||||
print(f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice")
|
||||
|
||||
r = post(
|
||||
url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice",
|
||||
headers=self.auth_api,
|
||||
json={"num_satoshis": f"{amount}", "memo": memo},
|
||||
json={"num_satoshis": f"{amount}", "memo": memo},
|
||||
)
|
||||
print(r.json())
|
||||
|
||||
if r.ok:
|
||||
data = r.json()
|
||||
payment_hash, payment_request = data["id"], data["payment_request"]
|
||||
@ -32,9 +30,10 @@ class LNPayWallet(Wallet):
|
||||
|
||||
def pay_invoice(self, bolt11: str) -> PaymentResponse:
|
||||
r = post(
|
||||
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw",
|
||||
headers=self.auth_api,
|
||||
json={"payment_request": bolt11})
|
||||
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw",
|
||||
headers=self.auth_api,
|
||||
json={"payment_request": bolt11},
|
||||
)
|
||||
|
||||
return PaymentResponse(r, not r.ok)
|
||||
|
||||
@ -45,8 +44,8 @@ class LNPayWallet(Wallet):
|
||||
return TxStatus(r, None)
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
statuses = {0: None, 1: True, -1: False}
|
||||
|
||||
return TxStatus(r, statuses[r.json()["settled"]])
|
||||
|
@ -3,14 +3,16 @@ bitstring==3.1.6
|
||||
certifi==2019.11.28
|
||||
chardet==3.0.4
|
||||
click==7.0
|
||||
flask-talisman==0.7.0
|
||||
flask==1.1.1
|
||||
idna==2.8
|
||||
itsdangerous==1.1.0
|
||||
jinja2==2.10.3
|
||||
lnurl==0.3.1
|
||||
jinja2==2.11.1
|
||||
lnurl==0.3.2
|
||||
markupsafe==1.1.1
|
||||
pydantic==1.3
|
||||
pydantic==1.4
|
||||
requests==2.22.0
|
||||
six==1.14.0
|
||||
typing-extensions==3.7.4.1 ; python_version < '3.8'
|
||||
urllib3==1.25.7
|
||||
werkzeug==0.16.0
|
||||
urllib3==1.25.8
|
||||
werkzeug==0.16.1
|
||||
|
Loading…
Reference in New Issue
Block a user