diff --git a/.env.example b/.env.example index c7da39ea..f26ebe31 100644 --- a/.env.example +++ b/.env.example @@ -1,20 +1,31 @@ FLASK_APP=lnbits FLASK_ENV=development +LNBITS_SITE_TITLE=LNbits LNBITS_WITH_ONION=0 LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_FEE_RESERVE=0 -LNBITS_BACKEND_WALLET_CLASS="LntxbotWallet" +LNBITS_BACKEND_WALLET_CLASS=LntxbotWallet + +CLIGHTNING_RPC="/home/arc/.lightning/bitcoin/lightning-rpc" + +LNBITS_ENDPOINT=127.0.0.1:5000 +LNBITS_INVOICE_MACAROON=LNBITS_INVOICE_MACAROON +LNBITS_ADMIN_MACAROON=LNBITS_ADMIN_MACAROON LND_GRPC_ENDPOINT=127.0.0.1 LND_GRPC_PORT=11009 -LND_CERT='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert' -LND_ADMIN_MACAROON='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon' -LND_INVOICE_MACAROON='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon' -LND_READ_MACAROON='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/read.macaroon' +LND_CERT="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" +LND_ADMIN_MACAROON="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon" +LND_INVOICE_MACAROON="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon" +LND_READ_MACAROON="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/read.macaroon" -CLIGHTNING_RPC='/home/arc/.lightning/bitcoin/lightning-rpc' +LNPAY_API_ENDPOINT=https://lnpay.co/v1/ +LNPAY_API_KEY=LNPAY_API_KEY +LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY +LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY +LNPAY_READ_KEY=LNPAY_READ_KEY LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/ LNTXBOT_ADMIN_KEY=LNTXBOT_ADMIN_KEY @@ -23,13 +34,3 @@ LNTXBOT_INVOICE_KEY=LNTXBOT_INVOICE_KEY OPENNODE_API_ENDPOINT=https://api.opennode.com/ OPENNODE_ADMIN_KEY=OPENNODE_ADMIN_KEY OPENNODE_INVOICE_KEY=OPENNODE_INVOICE_KEY - -LNPAY_API_ENDPOINT=https://lnpay.co/v1/ -LNPAY_API_KEY=LNPAY_API_KEY -LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY -LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY -LNPAY_READ_KEY=LNPAY_READ_KEY - -LNBITS_ENDPOINT=127.0.0.1:5000 -LNBITS_INVOICE_MACAROON=LNBITS_INVOICE_MACAROON -LNBITS_ADMIN_MACAROON=LNBITS_ADMIN_MACAROON diff --git a/Pipfile b/Pipfile index ba0f00d4..9d7322cb 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,7 @@ gunicorn = "*" pylightning = "*" pyscss = "*" requests = "*" +shortuuid = "*" [dev-packages] black = "==19.10b0" diff --git a/lnbits/__init__.py b/lnbits/__init__.py index 039c3abb..f92d050f 100644 --- a/lnbits/__init__.py +++ b/lnbits/__init__.py @@ -58,6 +58,7 @@ for ext in valid_extensions: app.jinja_env.globals["DEBUG"] = app.config["DEBUG"] app.jinja_env.globals["EXTENSIONS"] = valid_extensions +app.jinja_env.globals["SITE_TITLE"] = getenv("LNBITS_SITE_TITLE", "LNbits") app.jinja_env.filters["megajson"] = megajson diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 1bd8ad3b..bd92e379 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -1,5 +1,5 @@ -from uuid import uuid4 from typing import List, Optional +from uuid import uuid4 from lnbits.db import open_db from lnbits.settings import DEFAULT_WALLET_NAME, FEE_RESERVE diff --git a/lnbits/core/utils.py b/lnbits/core/services.py similarity index 67% rename from lnbits/core/utils.py rename to lnbits/core/services.py index b8ff96a9..b191d595 100644 --- a/lnbits/core/utils.py +++ b/lnbits/core/services.py @@ -1,9 +1,10 @@ -from typing import Tuple +from typing import Optional, Tuple from lnbits.bolt11 import decode as bolt11_decode from lnbits.settings import WALLET, FEE_RESERVE from .crud import create_payment +from .models import Wallet def create_invoice(*, wallet_id: str, amount: int, memo: str) -> Tuple[str, str]: @@ -21,14 +22,23 @@ def create_invoice(*, wallet_id: str, amount: int, memo: str) -> Tuple[str, str] return checking_id, payment_request -def pay_invoice(*, wallet_id: str, bolt11: str) -> str: +def pay_invoice(*, wallet: Wallet, bolt11: str, max_sat: Optional[int] = None) -> str: try: invoice = bolt11_decode(bolt11) ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(bolt11) + if invoice.amount_msat == 0: + raise ValueError("Amountless invoices not supported.") + + if max_sat and invoice.amount_msat > max_sat * 1000: + raise ValueError("Amount in invoice is too high.") + + if invoice.amount_msat > wallet.balance_msat: + raise PermissionError("Insufficient balance.") + if ok: create_payment( - wallet_id=wallet_id, + wallet_id=wallet.id, checking_id=checking_id, amount=-invoice.amount_msat, memo=invoice.description, @@ -42,3 +52,7 @@ def pay_invoice(*, wallet_id: str, bolt11: str) -> str: raise Exception(error_message or "Unexpected backend error.") return checking_id + + +def check_payment(*, checking_id: str) -> str: + pass diff --git a/lnbits/core/templates/core/_api_docs.html b/lnbits/core/templates/core/_api_docs.html index d3d4c8f3..28998949 100644 --- a/lnbits/core/templates/core/_api_docs.html +++ b/lnbits/core/templates/core/_api_docs.html @@ -35,7 +35,7 @@ + class="q-pb-md"> GET /api/v1/payments/<checking_id> diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index dd56cede..f8ebeba6 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -8,12 +8,9 @@ {% endassets %} {% endblock %} -{% block drawer %} -{% endblock %} - {% block page %}
-
+
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 75f8f4d8..f205b6d0 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -9,6 +9,7 @@ {% block scripts %} {{ window_vars(user, wallet) }} + {% assets filters='rjsmin', output='__bundle__/core/chart.js', 'vendor/moment@2.24.0/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %} @@ -17,7 +18,6 @@ {% assets filters='rjsmin', output='__bundle__/core/wallet.js', 'vendor/bolt11/utils.js', 'vendor/bolt11/decoder.js', - 'vendor/vue-qrcode@1.0.2/vue-qrcode.min.js', 'vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.js', 'core/js/wallet.js' %} @@ -26,7 +26,7 @@ {% block page %}
-
+

{% raw %}{{ fbalance }}{% endraw %} sat

@@ -105,9 +105,10 @@
-
+
+ Renew keys
LNbits wallet
Wallet name: {{ wallet.name }}
Wallet ID: {{ wallet.id }}
@@ -139,12 +140,12 @@
- - + + + label="Amount (sat) *"> -
-
- + + + - -
- Copy invoice - Close -
+ + +
+
+ Copy invoice + Close
- +
g.wallet.balance_msat: - return jsonify({"message": "Insufficient balance."}), Status.FORBIDDEN - - checking_id = pay_invoice(wallet_id=g.wallet.id, bolt11=g.data["bolt11"]) - + checking_id = pay_invoice(wallet=g.wallet, bolt11=g.data["bolt11"]) + except ValueError as e: + return jsonify({"message": str(e)}), Status.BAD_REQUEST + except PermissionError as e: + return jsonify({"message": str(e)}), Status.FORBIDDEN except Exception as e: return jsonify({"message": str(e)}), Status.INTERNAL_SERVER_ERROR diff --git a/lnbits/extensions/amilk/config.json b/lnbits/extensions/amilk/config.json index 01959207..09faf8af 100644 --- a/lnbits/extensions/amilk/config.json +++ b/lnbits/extensions/amilk/config.json @@ -2,5 +2,5 @@ "name": "AMilk", "short_description": "Assistant Faucet Milker", "icon": "room_service", - "contributors": ["eillarra"] + "contributors": ["arcbtc"] } diff --git a/lnbits/extensions/amilk/templates/amilk/index.html b/lnbits/extensions/amilk/templates/amilk/index.html index 65277140..7952c81e 100644 --- a/lnbits/extensions/amilk/templates/amilk/index.html +++ b/lnbits/extensions/amilk/templates/amilk/index.html @@ -80,7 +80,7 @@
- + diff --git a/lnbits/extensions/events/config.json.wip b/lnbits/extensions/events/config.json.wip new file mode 100644 index 00000000..738e264f --- /dev/null +++ b/lnbits/extensions/events/config.json.wip @@ -0,0 +1,6 @@ +{ + "name": "Events", + "short_description": "LN tickets for events.", + "icon": "local_activity", + "contributors": ["arcbtc"] +} diff --git a/lnbits/extensions/events/migrations.py b/lnbits/extensions/events/migrations.py new file mode 100644 index 00000000..04435aa5 --- /dev/null +++ b/lnbits/extensions/events/migrations.py @@ -0,0 +1,5 @@ +from lnbits.db import open_ext_db + + +def migrate(): + print("pending") diff --git a/lnbits/extensions/joust/README.md b/lnbits/extensions/joust/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/lnbits/extensions/paywall/crud.py b/lnbits/extensions/paywall/crud.py index 55a874ff..2fcf5a0a 100644 --- a/lnbits/extensions/paywall/crud.py +++ b/lnbits/extensions/paywall/crud.py @@ -1,15 +1,14 @@ -from base64 import urlsafe_b64encode -from uuid import uuid4 from typing import List, Optional, Union from lnbits.db import open_ext_db +from lnbits.helpers import urlsafe_short_hash from .models import Paywall def create_paywall(*, wallet_id: str, url: str, memo: str, amount: int) -> Paywall: with open_ext_db("paywall") as db: - paywall_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') + paywall_id = urlsafe_short_hash() db.execute( """ INSERT INTO paywalls (id, wallet, url, memo, amount) diff --git a/lnbits/extensions/tpos/crud.py b/lnbits/extensions/tpos/crud.py index 96077941..6bfed42f 100644 --- a/lnbits/extensions/tpos/crud.py +++ b/lnbits/extensions/tpos/crud.py @@ -1,15 +1,14 @@ -from base64 import urlsafe_b64encode -from uuid import uuid4 from typing import List, Optional, Union from lnbits.db import open_ext_db +from lnbits.helpers import urlsafe_short_hash from .models import TPoS def create_tpos(*, wallet_id: str, name: str, currency: str) -> TPoS: with open_ext_db("tpos") as db: - tpos_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') + tpos_id = urlsafe_short_hash() db.execute( """ INSERT INTO tposs (id, wallet, name, currency) diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py index b13f412e..98b473bb 100644 --- a/lnbits/extensions/tpos/views.py +++ b/lnbits/extensions/tpos/views.py @@ -1,12 +1,12 @@ -from flask import g, abort, render_template -from lnbits.core.crud import get_wallet - -from lnbits.decorators import check_user_exists, validate_uuids -from lnbits.extensions.tpos import tpos_ext -from lnbits.helpers import Status import requests -import json +from flask import g, abort, render_template + +from lnbits.core.crud import get_wallet +from lnbits.decorators import check_user_exists, validate_uuids +from lnbits.helpers import Status + +from lnbits.extensions.tpos import tpos_ext from .crud import get_tpos diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 3712711c..90535ebc 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -1,14 +1,15 @@ from flask import g, jsonify, request from lnbits.core.crud import get_user -from lnbits.core.utils import create_invoice +from lnbits.core.services import create_invoice from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request from lnbits.helpers import Status -from lnbits.settings import WALLET, FEE_RESERVE -from lnbits.extensions.tpos import tpos_ext +from lnbits.settings import WALLET +from lnbits.extensions.tpos import tpos_ext from .crud import create_tpos, get_tpos, get_tposs, delete_tpos + @tpos_ext.route("/api/v1/tposs", methods=["GET"]) @api_check_wallet_macaroon(key_type="invoice") def api_tposs(): diff --git a/lnbits/helpers.py b/lnbits/helpers.py index 9a7775f7..33544322 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -1,5 +1,6 @@ import json import os +import shortuuid import sqlite3 from typing import List, NamedTuple, Optional @@ -52,6 +53,10 @@ class Status: INTERNAL_SERVER_ERROR = 500 +def urlsafe_short_hash() -> str: + return shortuuid.uuid() + + class MegaEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, sqlite3.Row): diff --git a/lnbits/static/css/base.css b/lnbits/static/css/base.css index 3fe859bf..5be09bfa 100644 --- a/lnbits/static/css/base.css +++ b/lnbits/static/css/base.css @@ -29,12 +29,15 @@ color: inherit; font-weight: bold; } -.q-table--dense th:first-child, .q-table--dense td:first-child, -.q-table--dense .q-table__bottom { - padding-left: 6px !important; } - .q-table--dense th:last-child, .q-table--dense td:last-child, +.lnbits__dialog-card { + width: 500px; } + + .q-table--dense th:first-child, .q-table--dense td:first-child, .q-table--dense .q-table__bottom { - padding-right: 6px !important; } + padding-left: 6px !important; } + .q-table--dense th:last-child, .q-table--dense td:last-child, + .q-table--dense .q-table__bottom { + padding-right: 6px !important; } video { border-radius: 3px; } diff --git a/lnbits/static/js/components.js b/lnbits/static/js/components.js index 79767eda..f8a5c8ce 100644 --- a/lnbits/static/js/components.js +++ b/lnbits/static/js/components.js @@ -91,13 +91,17 @@ Vue.component('lnbits-extension-list', { } }, template: ` - + Extensions - + @@ -105,6 +109,9 @@ Vue.component('lnbits-extension-list', { {{ extension.name }} + + + @@ -119,18 +126,26 @@ Vue.component('lnbits-extension-list', { computed: { userExtensions: function () { if (!this.user) return []; + + var path = window.location.pathname; var userExtensions = this.user.extensions; + return this.extensions.filter(function (obj) { return userExtensions.indexOf(obj.code) !== -1; + }).map(function (obj) { + obj.isActive = path.startsWith(obj.url); + return obj; }); } }, created: function () { - this.extensions = window.extensions.map(function (data) { - return LNbits.map.extension(data); - }).sort(function (a, b) { - return a.name.localeCompare(b.name); - }); + if (window.extensions) { + this.extensions = window.extensions.map(function (data) { + return LNbits.map.extension(data); + }).sort(function (a, b) { + return a.name.localeCompare(b.name); + }); + } if (window.user) { this.user = LNbits.map.user(window.user); diff --git a/lnbits/static/scss/base.scss b/lnbits/static/scss/base.scss index c5035c7c..a25bdcc0 100644 --- a/lnbits/static/scss/base.scss +++ b/lnbits/static/scss/base.scss @@ -48,6 +48,10 @@ body.body--dark .q-field--error { } } +.lnbits__dialog-card { + width: 500px; +} + .q-table--dense { th:first-child, td:first-child, diff --git a/lnbits/templates/base.html b/lnbits/templates/base.html index a2f1c125..9951df03 100644 --- a/lnbits/templates/base.html +++ b/lnbits/templates/base.html @@ -8,7 +8,11 @@ {% endassets %} {% block styles %}{% endblock %} - {% block title %}LNbits{% endblock %} + + {% block title %} + {% if SITE_TITLE != 'LNbits' %}{{ SITE_TITLE }}{% else %}LNbits{% endif %} + {% endblock %} + {% block head_scripts %}{% endblock %} @@ -19,23 +23,31 @@ - - LNbits - - USE WITH CAUTION - LNbits wallet is still in BETA - + {% block drawer_toggle %} + + {% endblock %} + {% if SITE_TITLE != 'LNbits' %} + {{ SITE_TITLE }} + {% else %} + LNbits + {% endif %} + {% block beta %} + + USE WITH CAUTION - LNbits wallet is still in BETA + + {% endblock %} Toggle Dark Mode - - {% block drawer %} + {% block drawer %} + - {% endblock %} - + + {% endblock %} diff --git a/lnbits/wallets/opennode.py b/lnbits/wallets/opennode.py index b7a4d1fb..679db779 100644 --- a/lnbits/wallets/opennode.py +++ b/lnbits/wallets/opennode.py @@ -56,5 +56,5 @@ class OpenNodeWallet(Wallet): if not r.ok: return PaymentStatus(None) - statuses = {"pending": None, "confirmed": True, "error": False, "failed": False} + statuses = {"initial": None, "pending": None, "confirmed": True, "error": False, "failed": False} return PaymentStatus(statuses[r.json()["data"]["status"]]) diff --git a/requirements.txt b/requirements.txt index 93d76657..11a3654a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ grpcio==1.28.1 gunicorn==20.0.4 idna==2.9 itsdangerous==1.1.0 -jinja2==2.11.1 +jinja2==2.11.2 limits==1.5.1 lnd-grpc==0.4.0 lnurl==0.3.3 @@ -28,6 +28,7 @@ pydantic==1.4 pylightning==0.0.7.3 pyscss==1.3.7 requests==2.23.0 +shortuuid==1.0.1 six==1.14.0 typing-extensions==3.7.4.2 ; python_version < '3.8' urllib3==1.25.8