From 166530eb0c985575a140124fb4c9e5a23ee9e5a7 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:03:32 +0000 Subject: [PATCH 001/565] Added old admin extension --- .env.example | 12 +- lnbits/extensions/admin/README.md | 11 + lnbits/extensions/admin/__init__.py | 10 + lnbits/extensions/admin/config.json | 6 + lnbits/extensions/admin/crud.py | 59 ++ lnbits/extensions/admin/migrations.py | 256 ++++++++ lnbits/extensions/admin/models.py | 38 ++ .../admin/templates/admin/index.html | 565 ++++++++++++++++++ lnbits/extensions/admin/views.py | 20 + lnbits/extensions/admin/views_api.py | 41 ++ 10 files changed, 1013 insertions(+), 5 deletions(-) create mode 100644 lnbits/extensions/admin/README.md create mode 100644 lnbits/extensions/admin/__init__.py create mode 100644 lnbits/extensions/admin/config.json create mode 100644 lnbits/extensions/admin/crud.py create mode 100644 lnbits/extensions/admin/migrations.py create mode 100644 lnbits/extensions/admin/models.py create mode 100644 lnbits/extensions/admin/templates/admin/index.html create mode 100644 lnbits/extensions/admin/views.py create mode 100644 lnbits/extensions/admin/views_api.py diff --git a/.env.example b/.env.example index 93b82325..68e25ad1 100644 --- a/.env.example +++ b/.env.example @@ -3,10 +3,12 @@ PORT=5000 DEBUG=false -LNBITS_ALLOWED_USERS="" -LNBITS_ADMIN_USERS="" -# Extensions only admin can access -LNBITS_ADMIN_EXTENSIONS="ngrok" +LNBITS_ADMIN_USERS="" # User IDs seperated by comma +LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access +LNBITS_ADMIN_UI=false # Extensions only admin can access + +LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma + LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" # csv ad image filepaths or urls, extensions can choose to honor @@ -91,4 +93,4 @@ LNBITS_DENOMINATION=sats # EclairWallet ECLAIR_URL=http://127.0.0.1:8283 -ECLAIR_PASS=eclairpw \ No newline at end of file +ECLAIR_PASS=eclairpw diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md new file mode 100644 index 00000000..27729459 --- /dev/null +++ b/lnbits/extensions/admin/README.md @@ -0,0 +1,11 @@ +

Example Extension

+

*tagline*

+This is an example extension to help you organise and build you own. + +Try to include an image + + + +

If your extension has API endpoints, include useful ones here

+ +curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py new file mode 100644 index 00000000..d5f26c90 --- /dev/null +++ b/lnbits/extensions/admin/__init__.py @@ -0,0 +1,10 @@ +from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_admin") + +admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates") + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/admin/config.json b/lnbits/extensions/admin/config.json new file mode 100644 index 00000000..69661733 --- /dev/null +++ b/lnbits/extensions/admin/config.json @@ -0,0 +1,6 @@ +{ + "name": "Admin", + "short_description": "Manage your LNbits install", + "icon": "build", + "contributors": ["benarc"] +} diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py new file mode 100644 index 00000000..cb8f9b5b --- /dev/null +++ b/lnbits/extensions/admin/crud.py @@ -0,0 +1,59 @@ +from typing import List, Optional + +from . import db +from .models import Admin, Funding +from lnbits.settings import * +from lnbits.helpers import urlsafe_short_hash +from lnbits.core.crud import create_payment +from lnbits.db import Connection + + +def update_wallet_balance(wallet_id: str, amount: int) -> str: + temp_id = f"temp_{urlsafe_short_hash()}" + internal_id = f"internal_{urlsafe_short_hash()}" + create_payment( + wallet_id=wallet_id, + checking_id=internal_id, + payment_request="admin_internal", + payment_hash="admin_internal", + amount=amount * 1000, + memo="Admin top up", + pending=False, + ) + return "success" + + +async def update_admin( +) -> Optional[Admin]: + if not CLightningWallet: + print("poo") + await db.execute( + """ + UPDATE admin + SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ? + WHERE 1 + """, + ( + + ), + ) + row = await db.fetchone("SELECT * FROM admin WHERE 1") + return Admin.from_row(row) if row else None + +async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) + ) + row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) + return Jukebox(**row) if row else None + +async def get_admin() -> List[Admin]: + row = await db.fetchone("SELECT * FROM admin WHERE 1") + return Admin.from_row(row) if row else None + + +async def get_funding() -> List[Funding]: + rows = await db.fetchall("SELECT * FROM funding") + + return [Funding.from_row(row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py new file mode 100644 index 00000000..82d934cb --- /dev/null +++ b/lnbits/extensions/admin/migrations.py @@ -0,0 +1,256 @@ +from sqlalchemy.exc import OperationalError # type: ignore +from os import getenv +from lnbits.helpers import urlsafe_short_hash + + +async def m001_create_admin_table(db): + user = None + site_title = None + site_tagline = None + site_description = None + allowed_users = None + admin_user = None + default_wallet_name = None + data_folder = None + disabled_ext = None + force_https = True + service_fee = 0 + funding_source = "" + + if getenv("LNBITS_SITE_TITLE"): + site_title = getenv("LNBITS_SITE_TITLE") + + if getenv("LNBITS_SITE_TAGLINE"): + site_tagline = getenv("LNBITS_SITE_TAGLINE") + + if getenv("LNBITS_SITE_DESCRIPTION"): + site_description = getenv("LNBITS_SITE_DESCRIPTION") + + if getenv("LNBITS_ALLOWED_USERS"): + allowed_users = getenv("LNBITS_ALLOWED_USERS") + + if getenv("LNBITS_ADMIN_USER"): + admin_user = getenv("LNBITS_ADMIN_USER") + + if getenv("LNBITS_DEFAULT_WALLET_NAME"): + default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") + + if getenv("LNBITS_DATA_FOLDER"): + data_folder = getenv("LNBITS_DATA_FOLDER") + + if getenv("LNBITS_DISABLED_EXTENSIONS"): + disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + + if getenv("LNBITS_FORCE_HTTPS"): + force_https = getenv("LNBITS_FORCE_HTTPS") + + if getenv("LNBITS_SERVICE_FEE"): + service_fee = getenv("LNBITS_SERVICE_FEE") + + if getenv("LNBITS_BACKEND_WALLET_CLASS"): + funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS admin ( + user TEXT, + site_title TEXT, + site_tagline TEXT, + site_description TEXT, + admin_user TEXT, + allowed_users TEXT, + default_wallet_name TEXT, + data_folder TEXT, + disabled_ext TEXT, + force_https BOOLEAN, + service_fee INT, + funding_source TEXT + ); + """ + ) + await db.execute( + """ + INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + user, + site_title, + site_tagline, + site_description, + admin_user, + allowed_users, + default_wallet_name, + data_folder, + disabled_ext, + force_https, + service_fee, + funding_source, + ), + ) + + +async def m001_create_funding_table(db): + + funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") + + # Make the funding table, if it does not already exist + await db.execute( + """ + CREATE TABLE IF NOT EXISTS funding ( + id TEXT PRIMARY KEY, + backend_wallet TEXT, + endpoint TEXT, + port INT, + read_key TEXT, + invoice_key TEXT, + admin_key TEXT, + cert TEXT, + balance INT, + selected INT + ); + """ + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, selected) + VALUES (?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "CLightningWallet", + getenv("CLIGHTNING_RPC"), + 1 if funding_wallet == "CLightningWallet" else 0, + ), + ) + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "SparkWallet", + getenv("SPARK_URL"), + getenv("SPARK_TOKEN"), + 1 if funding_wallet == "SparkWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LnbitsWallet", + getenv("LNBITS_ENDPOINT"), + getenv("LNBITS_KEY"), + 1 if funding_wallet == "LnbitsWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LndWallet", + getenv("LND_GRPC_ENDPOINT"), + getenv("LND_GRPC_PORT"), + getenv("LND_GRPC_MACAROON"), + getenv("LND_GRPC_CERT"), + 1 if funding_wallet == "LndWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LndRestWallet", + getenv("LND_REST_ENDPOINT"), + getenv("LND_REST_MACAROON"), + getenv("LND_REST_CERT"), + 1 if funding_wallet == "LndWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LNPayWallet", + getenv("LNPAY_API_ENDPOINT"), + getenv("LNPAY_WALLET_KEY"), + getenv("LNPAY_API_KEY"), # this is going in as the cert + 1 if funding_wallet == "LNPayWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LntxbotWallet", + getenv("LNTXBOT_API_ENDPOINT"), + getenv("LNTXBOT_KEY"), + 1 if funding_wallet == "LntxbotWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "OpenNodeWallet", + getenv("OPENNODE_API_ENDPOINT"), + getenv("OPENNODE_KEY"), + 1 if funding_wallet == "OpenNodeWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "SparkWallet", + getenv("SPARK_URL"), + getenv("SPARK_TOKEN"), + 1 if funding_wallet == "SparkWallet" else 0, + ), + ) + + ## PLACEHOLDER FOR ECLAIR WALLET + # await db.execute( + # """ + # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + # VALUES (?, ?, ?, ?, ?) + # """, + # ( + # urlsafe_short_hash(), + # "EclairWallet", + # getenv("ECLAIR_URL"), + # getenv("ECLAIR_PASS"), + # 1 if funding_wallet == "EclairWallet" else 0, + # ), + # ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py new file mode 100644 index 00000000..c38f17f4 --- /dev/null +++ b/lnbits/extensions/admin/models.py @@ -0,0 +1,38 @@ +from typing import NamedTuple +from sqlite3 import Row + +class Admin(NamedTuple): + user: str + site_title: str + site_tagline: str + site_description:str + allowed_users: str + admin_user: str + default_wallet_name: str + data_folder: str + disabled_ext: str + force_https: str + service_fee: str + funding_source: str + + @classmethod + def from_row(cls, row: Row) -> "Admin": + data = dict(row) + return cls(**data) + +class Funding(NamedTuple): + id: str + backend_wallet: str + endpoint: str + port: str + read_key: str + invoice_key: str + admin_key: str + cert: str + balance: int + selected: int + + @classmethod + def from_row(cls, row: Row) -> "Funding": + data = dict(row) + return cls(**data) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html new file mode 100644 index 00000000..87cf09ef --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -0,0 +1,565 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + +

Admin

+

+ +
+
+ + +
Settings
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+

+
+
+ +
+
+
+
+ +
+ + +
Wallet topup
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ +{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py new file mode 100644 index 00000000..5e17919c --- /dev/null +++ b/lnbits/extensions/admin/views.py @@ -0,0 +1,20 @@ +from quart import g, render_template, request, jsonify +import json + +from lnbits.decorators import check_user_exists, validate_uuids +from lnbits.extensions.admin import admin_ext +from lnbits.core.crud import get_user, create_account +from .crud import get_admin, get_funding +from lnbits.settings import WALLET + + +@admin_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + user_id = g.user + admin = await get_admin() + + funding = [{**funding._asdict()} for funding in await get_funding()] + + return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py new file mode 100644 index 00000000..2a61b6f5 --- /dev/null +++ b/lnbits/extensions/admin/views_api.py @@ -0,0 +1,41 @@ +from quart import jsonify, g, request +from http import HTTPStatus +from .crud import update_wallet_balance +from lnbits.extensions.admin import admin_ext +from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from lnbits.core.crud import get_wallet +from .crud import get_admin,update_admin +import json + +@admin_ext.route("/api/v1/admin//", methods=["GET"]) +@api_check_wallet_key("admin") +async def api_update_balance(wallet_id, topup_amount): + print(g.data.wallet) + try: + wallet = await get_wallet(wallet_id) + except: + return ( + jsonify({"error": "Not allowed: not an admin"}), + HTTPStatus.FORBIDDEN, + ) + print(wallet) + print(topup_amount) + return jsonify({"status": "Success"}), HTTPStatus.OK + + +@admin_ext.route("/api/v1/admin/", methods=["POST"]) +@api_check_wallet_key("admin") +@api_validate_post_request(schema={}) +async def api_update_admin(): + body = await request.get_json() + admin = await get_admin() + print(g.wallet[2]) + print(body["admin_user"]) + if not admin.admin_user == g.wallet[2] and admin.admin_user != None: + return ( + jsonify({"error": "Not allowed: not an admin"}), + HTTPStatus.FORBIDDEN, + ) + updated = await update_admin(body) + print(updated) + return jsonify({"status": "Success"}), HTTPStatus.OK \ No newline at end of file From 68eee00b45170db4e35fa6fdafba85373958e9fa Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:11:55 +0000 Subject: [PATCH 002/565] old admin setup UI --- lnbits/core/templates/core/admin.html | 717 ++++++++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 lnbits/core/templates/core/admin.html diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html new file mode 100644 index 00000000..e8176555 --- /dev/null +++ b/lnbits/core/templates/core/admin.html @@ -0,0 +1,717 @@ +{% extends "public.html" %} {% from "macros.jinja" import window_vars with +context %} {% block page %} +
+
+ + +

+
Welcome to LNbits
+

+
+ Fill in the information below to setup your LNbits instance. Details + can be changed later. +
+

+ + +
+ +
Branding
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ +
Service settings
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details + should be filled in for you
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+
+
+ View project in GitHub + Donate +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }} + +{% endblock %} From 65e1f19ed1124340d2a9ba97b4420c099896488c Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:18:09 +0000 Subject: [PATCH 003/565] convert to FastAPI --- lnbits/extensions/admin/__init__.py | 11 +++- lnbits/extensions/admin/crud.py | 50 +++++++-------- lnbits/extensions/admin/migrations.py | 23 ++++--- lnbits/extensions/admin/models.py | 51 ++++++++++------ .../admin/templates/admin/index.html | 23 +++++-- lnbits/extensions/admin/views.py | 39 ++++++++---- lnbits/extensions/admin/views_api.py | 61 ++++++++++--------- 7 files changed, 151 insertions(+), 107 deletions(-) diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py index d5f26c90..6a56b2bb 100644 --- a/lnbits/extensions/admin/__init__.py +++ b/lnbits/extensions/admin/__init__.py @@ -1,10 +1,15 @@ -from quart import Blueprint +from fastapi import APIRouter + from lnbits.db import Database +from lnbits.helpers import template_renderer db = Database("ext_admin") -admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates") +admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"]) + +def admin_renderer(): + return template_renderer(["lnbits/extensions/admin/templates"]) -from .views_api import * # noqa from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index cb8f9b5b..872d6c97 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,11 +1,11 @@ from typing import List, Optional +from lnbits.core.crud import create_payment +from lnbits.helpers import urlsafe_short_hash +from lnbits.settings import * + from . import db from .models import Admin, Funding -from lnbits.settings import * -from lnbits.helpers import urlsafe_short_hash -from lnbits.core.crud import create_payment -from lnbits.db import Connection def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -22,38 +22,30 @@ def update_wallet_balance(wallet_id: str, amount: int) -> str: ) return "success" - -async def update_admin( -) -> Optional[Admin]: - if not CLightningWallet: - print("poo") - await db.execute( - """ - UPDATE admin - SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ? - WHERE 1 - """, - ( - - ), - ) - row = await db.fetchone("SELECT * FROM admin WHERE 1") - return Admin.from_row(row) if row else None - -async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]: +async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + print("UPDATE", q) await db.execute( - f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) + f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) - return Jukebox(**row) if row else None + row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,)) + assert row, "Newly updated settings couldn't be retrieved" + return Admin(**row) if row else None + +# async def update_admin(user: str, **kwargs) -> Optional[Admin]: +# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) +# await db.execute( +# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user) +# ) +# new_settings = await get_admin() +# return new_settings async def get_admin() -> List[Admin]: - row = await db.fetchone("SELECT * FROM admin WHERE 1") - return Admin.from_row(row) if row else None + row = await db.fetchone("SELECT * FROM admin") + return Admin(**row) if row else None async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM funding") - return [Funding.from_row(row) for row in rows] + return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 82d934cb..13b76923 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -1,5 +1,7 @@ -from sqlalchemy.exc import OperationalError # type: ignore from os import getenv + +from sqlalchemy.exc import OperationalError # type: ignore + from lnbits.helpers import urlsafe_short_hash @@ -9,7 +11,7 @@ async def m001_create_admin_table(db): site_tagline = None site_description = None allowed_users = None - admin_user = None + admin_users = None default_wallet_name = None data_folder = None disabled_ext = None @@ -29,8 +31,9 @@ async def m001_create_admin_table(db): if getenv("LNBITS_ALLOWED_USERS"): allowed_users = getenv("LNBITS_ALLOWED_USERS") - if getenv("LNBITS_ADMIN_USER"): - admin_user = getenv("LNBITS_ADMIN_USER") + if getenv("LNBITS_ADMIN_USERS"): + admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) + user = admin_users.split(',')[0] if getenv("LNBITS_DEFAULT_WALLET_NAME"): default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") @@ -53,32 +56,32 @@ async def m001_create_admin_table(db): await db.execute( """ CREATE TABLE IF NOT EXISTS admin ( - user TEXT, + "user" TEXT, site_title TEXT, site_tagline TEXT, site_description TEXT, - admin_user TEXT, + admin_users TEXT, allowed_users TEXT, default_wallet_name TEXT, data_folder TEXT, disabled_ext TEXT, force_https BOOLEAN, - service_fee INT, + service_fee REAL, funding_source TEXT ); """ ) await db.execute( """ - INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) + INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( - user, + user.strip(), site_title, site_tagline, site_description, - admin_user, + admin_users[1:], allowed_users, default_wallet_name, data_folder, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index c38f17f4..4080ff01 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,18 +1,35 @@ -from typing import NamedTuple from sqlite3 import Row +from typing import List, Optional -class Admin(NamedTuple): +from fastapi import Query +from pydantic import BaseModel + + +class UpdateAdminSettings(BaseModel): + site_title: Optional[str] + site_tagline: Optional[str] + site_description: Optional[str] + allowed_users: Optional[str] + admin_users: Optional[str] + default_wallet_name: Optional[str] + data_folder: Optional[str] + disabled_ext: Optional[str] + force_https: Optional[bool] + service_fee: Optional[float] + funding_source: Optional[str] + +class Admin(BaseModel): user: str - site_title: str - site_tagline: str - site_description:str - allowed_users: str - admin_user: str + site_title: Optional[str] + site_tagline: Optional[str] + site_description: Optional[str] + allowed_users: Optional[str] + admin_users: str default_wallet_name: str data_folder: str disabled_ext: str - force_https: str - service_fee: str + force_https: Optional[bool] = Query(True) + service_fee: float funding_source: str @classmethod @@ -20,16 +37,16 @@ class Admin(NamedTuple): data = dict(row) return cls(**data) -class Funding(NamedTuple): +class Funding(BaseModel): id: str backend_wallet: str - endpoint: str - port: str - read_key: str - invoice_key: str - admin_key: str - cert: str - balance: int + endpoint: str = Query(None) + port: str = Query(None) + read_key: str = Query(None) + invoice_key: str = Query(None) + admin_key: str = Query(None) + cert: str = Query(None) + balance: int = Query(None) selected: int @classmethod diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 87cf09ef..a6b45625 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -87,7 +87,7 @@ @@ -442,13 +442,14 @@ site_title: '{{admin.site_title}}', tagline: '{{admin.site_tagline}}', description: '{{admin.site_description}}', - admin_user: '{{admin.admin_user}}', - service_fee: parseInt('{{admin.service_fee}}'), + admin_users: '{{admin.admin_users}}', + service_fee: parseFloat('{{admin.service_fee}}'), default_wallet_name: '{{admin.default_wallet_name}}', data_folder: '{{admin.data_folder}}', funding_source_primary: '{{admin.funding_source}}', disabled_ext: '{{admin.disabled_ext}}'.split(','), edited: [], + funding: {}, senddata: {} } }, @@ -528,15 +529,27 @@ }, UpdateLNbits: function () { var self = this - console.log(self.data.admin) + let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin + let data = { + site_title, + site_tagline: this.data.admin.tagline, + site_description: this.data.admin.description, + admin_users: admin_users.toString(), + default_wallet_name, + data_folder, + disabled_ext: disabled_ext.toString(), + service_fee, + funding_source: funding_source_primary} + console.log(data) LNbits.api .request( 'POST', '/admin/api/v1/admin/', self.g.user.wallets[0].adminkey, - self.data.admin + data ) .then(function (response) { + console.log(response.data) self.$q.notify({ type: 'positive', message: diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 5e17919c..00a0c99f 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -1,20 +1,33 @@ -from quart import g, render_template, request, jsonify -import json +from email.policy import default +from os import getenv -from lnbits.decorators import check_user_exists, validate_uuids +from fastapi import Request +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists from lnbits.extensions.admin import admin_ext -from lnbits.core.crud import get_user, create_account +from lnbits.requestvars import g + +from . import admin_ext, admin_renderer from .crud import get_admin, get_funding -from lnbits.settings import WALLET +templates = Jinja2Templates(directory="templates") -@admin_ext.route("/") -@validate_uuids(["usr"], required=True) -@check_user_exists() -async def index(): - user_id = g.user +@admin_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() + print(g()) + funding = [f.dict() for f in await get_funding()] - funding = [{**funding._asdict()} for funding in await get_funding()] - - return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding) + print("ADMIN", admin.dict()) + return admin_renderer().TemplateResponse( + "admin/index.html", { + "request": request, + "user": user.dict(), + "admin": admin.dict(), + "funding": funding + } + ) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 2a61b6f5..b2c65be2 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,41 +1,42 @@ -from quart import jsonify, g, request from http import HTTPStatus -from .crud import update_wallet_balance -from lnbits.extensions.admin import admin_ext -from lnbits.decorators import api_check_wallet_key, api_validate_post_request -from lnbits.core.crud import get_wallet -from .crud import get_admin,update_admin -import json -@admin_ext.route("/api/v1/admin//", methods=["GET"]) -@api_check_wallet_key("admin") -async def api_update_balance(wallet_id, topup_amount): - print(g.data.wallet) +from fastapi import Body, Depends, Request +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_wallet +from lnbits.decorators import WalletTypeInfo, require_admin_key +from lnbits.extensions.admin import admin_ext +from lnbits.extensions.admin.models import Admin, UpdateAdminSettings + +from .crud import get_admin, update_admin, update_wallet_balance + + +@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) +async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)): + print(g.wallet) try: wallet = await get_wallet(wallet_id) except: - return ( - jsonify({"error": "Not allowed: not an admin"}), - HTTPStatus.FORBIDDEN, - ) + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) print(wallet) print(topup_amount) - return jsonify({"status": "Success"}), HTTPStatus.OK + return {"status": "Success"} -@admin_ext.route("/api/v1/admin/", methods=["POST"]) -@api_check_wallet_key("admin") -@api_validate_post_request(schema={}) -async def api_update_admin(): - body = await request.get_json() +@admin_ext.post("/api/v1/admin/", status_code=HTTPStatus.OK) +async def api_update_admin( + request: Request, + data: UpdateAdminSettings = Body(...), + g: WalletTypeInfo = Depends(require_admin_key) + ): admin = await get_admin() - print(g.wallet[2]) - print(body["admin_user"]) - if not admin.admin_user == g.wallet[2] and admin.admin_user != None: - return ( - jsonify({"error": "Not allowed: not an admin"}), - HTTPStatus.FORBIDDEN, - ) - updated = await update_admin(body) + print(data) + if not admin.user == g.wallet.user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) + updated = await update_admin(user=g.wallet.user, **data.dict()) print(updated) - return jsonify({"status": "Success"}), HTTPStatus.OK \ No newline at end of file + return {"status": "Success"} From 23d770a07489c3d74cc0c4c4d3b4a3b4b00fc393 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:18:58 +0000 Subject: [PATCH 004/565] remove core admin html (renamed for now) --- lnbits/core/templates/core/core_admin.html | 717 +++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 lnbits/core/templates/core/core_admin.html diff --git a/lnbits/core/templates/core/core_admin.html b/lnbits/core/templates/core/core_admin.html new file mode 100644 index 00000000..835fc00a --- /dev/null +++ b/lnbits/core/templates/core/core_admin.html @@ -0,0 +1,717 @@ +{% extends "public.html" %} {% from "macros.jinja" import window_vars with +context %} {% block page %} +
+
+ + +

+
Welcome to LNbits
+

+
+ Fill in the information below to setup your LNbits instance. Details + can be changed later. +
+

+ + +
+ +
Branding
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ +
Service settings
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details + should be filled in for you
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+
+
+ View project in GitHub + Donate +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }} + +{% endblock %} From 165ab6d0b50b49cad69b46100bce0f87fd6f7257 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:21:38 +0000 Subject: [PATCH 005/565] typo --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 68e25ad1..bd189484 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ DEBUG=false LNBITS_ADMIN_USERS="" # User IDs seperated by comma LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Extensions only admin can access +LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma From 844e11edeb441fd2e7c7b40d11c25cc4019471bf Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:22:23 +0000 Subject: [PATCH 006/565] add admin_ui env --- lnbits/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/settings.py b/lnbits/settings.py index 3f4e31cc..43cb87cb 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,5 +1,6 @@ import importlib import subprocess +from email.policy import default from os import path from typing import List @@ -27,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None) LNBITS_ALLOWED_USERS: List[str] = [ x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str) ] +LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False) LNBITS_ADMIN_USERS: List[str] = [ x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) ] From 4336613028e05dae5b6adf3b543903f6fc365a44 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:23:16 +0000 Subject: [PATCH 007/565] add db config at startup --- lnbits/commands.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lnbits/commands.py b/lnbits/commands.py index 0f7454f2..8c39c338 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -52,6 +52,25 @@ def bundle_vendored(): with open(outputpath, "w") as f: f.write(output) +async def get_admin_settings(): + from lnbits.extensions.admin.models import Admin + + async with core_db.connect() as conn: + + if conn.type == SQLITE: + exists = await conn.fetchone( + "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" + ) + elif conn.type in {POSTGRES, COCKROACH}: + exists = await conn.fetchone( + "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" + ) + if not exists: + return False + + row = await conn.fetchone("SELECT * from admin") + + return Admin(**row) if row else None async def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" From 32a6a6ae2fb9fa3ba01c24f31067a5115feca4e3 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:23:53 +0000 Subject: [PATCH 008/565] get admin settings at startup --- lnbits/app.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index 51482538..6a66b99e 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -18,6 +18,7 @@ from loguru import logger import lnbits.settings from lnbits.core.tasks import register_task_listeners +from .commands import get_admin_settings from .core import core_app from .core.views.generic import core_html_routes from .helpers import ( @@ -42,6 +43,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: """Create application factory. :param config_object: The configuration object to use. """ +<<<<<<< HEAD configure_logger() app = FastAPI( @@ -53,6 +55,14 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") +======= + app = FastAPI() + + if lnbits.settings.LNBITS_ADMIN_UI: + check_settings(app) + + app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") +>>>>>>> e3a1b3ae (get admin settings at startup) app.mount( "/core/static", StaticFiles(packages=[("lnbits.core", "static")]), @@ -64,7 +74,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: app.add_middleware( CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"] ) - g().config = lnbits.settings g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}" @@ -102,6 +111,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app +def check_settings(app: FastAPI): + @app.on_event("startup") + async def check_settings_admin(): + while True: + admin_set = await get_admin_settings() + if admin_set : + break + print("ERROR:", admin_set) + await asyncio.sleep(5) + # admin_set = await get_admin_settings() + g().admin_conf = admin_set + def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") async def check_wallet_status(): From f245f3188b7f35b6f9388e9caa12d3d6a0b20c3e Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:24:11 +0000 Subject: [PATCH 009/565] remove core admin.html --- lnbits/core/templates/core/admin.html | 717 -------------------------- 1 file changed, 717 deletions(-) delete mode 100644 lnbits/core/templates/core/admin.html diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html deleted file mode 100644 index e8176555..00000000 --- a/lnbits/core/templates/core/admin.html +++ /dev/null @@ -1,717 +0,0 @@ -{% extends "public.html" %} {% from "macros.jinja" import window_vars with -context %} {% block page %} -
-
- - -

-
Welcome to LNbits
-

-
- Fill in the information below to setup your LNbits instance. Details - can be changed later. -
-

- - -
- -
Branding
-
-
- -
-
- -
-
-
-
- - - -
-
- - - -
-
- -
Service settings
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
- Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details - should be filled in for you
-
- - - - - - - - - - - - - -
-
- -
-
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
-
-
- -
- - -
-
-
-
- View project in GitHub - Donate -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(funding) }} - -{% endblock %} From de21f0216161ac9f45cf17f42f5be708404156d0 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Mar 2022 16:55:31 +0000 Subject: [PATCH 010/565] refactor ui --- .../admin/templates/admin/index.html | 727 +++++++++++++++++- 1 file changed, 712 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index a6b45625..65ac9f33 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1,6 +1,670 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %} +
+
+ +
+
+ + + + + + +
+
+ + + + +
Wallets Management
+
+
+
+
+

Funding Source Info

+
    + {%raw%} +
  • Funding Source: {{data.admin.funding_source}}
  • +
  • Balance: {{data.admin.balance / 1000}} sats
  • + {%endraw%} +
+
+
+
+
+
+

Active Funding

+ +
+
+
+ +

TopUp a wallet

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+

Funding Sources

+ + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+
+ +
+ Save +
+
+
+ + +
User Management
+
+

+ Super Admin: {% raw + %}{{this.data.admin.user}}{% endraw %} +

+
+
+

Admin Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Allowed Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+
+
+ + +
Server Management
+
+
+
+
+

Server Info

+
    + {%raw%} +
  • SQlite: {{data.admin.data_folder}}
  • +
  • Postgres: {{data.admin.database_url}}
  • + {%endraw%} +
+
+
+
+
+
+

Service Fee

+ +
+
+
+

Miscelaneous

+ + + Force HTTPS + Prefer secure URLs + + + + + + + + Hide API + Hides wallet api, extensions can choose to honor + + + + + +
+
+
+
+ +
+ Save +
+
+
+ + +
UI Management
+
+
+
+
+

Site Title

+ +
+
+
+

Site Tagline

+ +
+
+
+
+

Site Description

+ +
+
+
+
+

Default Wallet Name

+ +
+
+
+

Denomination

+ +
+
+
+
+
+

Themes

+ +
+
+
+

Advertisement Slots

+ + + +
+ {% raw %} + + {{ space.slice(0, 8) + " ... " + space.slice(-8) }} + + {% endraw %} +
+
+
+
+
+ +
+ Save +
+
+
+
+
+
+
+
+

Admin

-
+
@@ -426,6 +1090,7 @@ return { wallet: {data: {}}, cancel: {}, + tab: 'funding', data: { funding_source: [ 'CLightningWallet', @@ -436,24 +1101,14 @@ 'LnbitsWallet', 'OpenNodeWallet' ], - + admin: { - user: '{{ user.id }}', - site_title: '{{admin.site_title}}', - tagline: '{{admin.site_tagline}}', - description: '{{admin.site_description}}', - admin_users: '{{admin.admin_users}}', - service_fee: parseFloat('{{admin.service_fee}}'), - default_wallet_name: '{{admin.default_wallet_name}}', - data_folder: '{{admin.data_folder}}', - funding_source_primary: '{{admin.funding_source}}', - disabled_ext: '{{admin.disabled_ext}}'.split(','), edited: [], funding: {}, senddata: {} } }, - + themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'], options: [ 'bleskomat', 'captcha', @@ -489,9 +1144,51 @@ for (i = 0; i < funding.length; i++) { self.data.admin.funding[funding[i].backend_wallet] = funding[i] } - console.log(self.data.admin) + let settings = JSON.parse('{{ settings | tojson|safe }}') + settings.balance = '{{ balance }}' + this.data.admin = {...this.data.admin, ...settings} + console.log(this.g.user) }, methods: { + addAdminUser(){ + let addUser = this.data.admin_users_add + let admin_users = this.data.admin.admin_users + if(addUser.length && !admin_users.includes(addUser)){ + admin_users.push(addUser) + this.data.admin.admin_users = admin_users + this.data.admin_users_add = "" + } + }, + removeAdminUser(user){ + let admin_users = this.data.admin.admin_users + this.data.admin.admin_users = admin_users.filter(u => u !== user) + }, + addAllowedUser(){ + let addUser = this.data.allowed_users_add + let allowed_users = this.data.admin.allowed_users + if(addUser.length && !allowed_users.includes(addUser)){ + allowed_users.push(addUser) + this.data.admin.allowed_users = allowed_users + this.data.allowed_users_add = "" + } + }, + removeAllowedUser(user){ + let allowed_users = this.data.admin.allowed_users + this.data.admin.allowed_users = allowed_users.filter(u => u !== user) + }, + addAdSpace(){ + let adSpace = this.data.ad_space_add + let spaces = this.data.admin.ad_space + if(adSpace.length && !spaces.includes(adSpace)){ + spaces.push(adSpace) + this.data.admin.ad_space = spaces + this.data.ad_space_add = "" + } + }, + removeAdSpace(ad){ + let spaces = this.data.admin.ad_space + this.data.admin.ad_space = spaces.filter(s => s !== ad) + }, topupWallet: function () { var self = this LNbits.api From 582cc52ac61236ca7ae219b49e20d0954e983411 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Mar 2022 16:59:06 +0000 Subject: [PATCH 011/565] make it work from g() --- lnbits/app.py | 34 +++--- lnbits/config.py | 62 ++++++++++ lnbits/extensions/admin/crud.py | 2 +- lnbits/extensions/admin/migrations.py | 162 +++++++++++++++++--------- lnbits/extensions/admin/models.py | 27 +++-- lnbits/extensions/admin/views.py | 8 +- lnbits/settings.py | 2 +- 7 files changed, 218 insertions(+), 79 deletions(-) create mode 100644 lnbits/config.py diff --git a/lnbits/app.py b/lnbits/app.py index 6a66b99e..ccac1e00 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -19,6 +19,7 @@ import lnbits.settings from lnbits.core.tasks import register_task_listeners from .commands import get_admin_settings +from .config import WALLET, conf from .core import core_app from .core.views.generic import core_html_routes from .helpers import ( @@ -29,7 +30,8 @@ from .helpers import ( url_for_vendored, ) from .requestvars import g -from .settings import WALLET + +# from .settings import WALLET from .tasks import ( catch_everything_and_restart, check_pending_payments, @@ -43,7 +45,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: """Create application factory. :param config_object: The configuration object to use. """ -<<<<<<< HEAD configure_logger() app = FastAPI( @@ -55,20 +56,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") -======= - app = FastAPI() - - if lnbits.settings.LNBITS_ADMIN_UI: - check_settings(app) - - app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") ->>>>>>> e3a1b3ae (get admin settings at startup) app.mount( "/core/static", StaticFiles(packages=[("lnbits.core", "static")]), name="core_static", ) + if lnbits.settings.LNBITS_ADMIN_UI: + g().admin_conf = conf + check_settings(app) + + g().WALLET = WALLET + origins = ["*"] app.add_middleware( @@ -110,18 +109,27 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app - def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): + + def removeEmptyString(arr): + return list(filter(None, arr)) + while True: admin_set = await get_admin_settings() if admin_set : break print("ERROR:", admin_set) await asyncio.sleep(5) - # admin_set = await get_admin_settings() - g().admin_conf = admin_set + + admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) + admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(',')) + admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(',')) + admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(',')) + admin_set.theme = removeEmptyString(admin_set.theme.split(',')) + admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) + g().admin_conf = conf.copy(update=admin_set.dict()) def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") diff --git a/lnbits/config.py b/lnbits/config.py new file mode 100644 index 00000000..02e8cf53 --- /dev/null +++ b/lnbits/config.py @@ -0,0 +1,62 @@ +import importlib +import json +from os import getenv, path +from typing import List, Optional + +from pydantic import BaseSettings, Field, validator + +wallets_module = importlib.import_module("lnbits.wallets") +wallet_class = getattr( + wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet") +) + +WALLET = wallet_class() + +def list_parse_fallback(v): + try: + return json.loads(v) + except Exception as e: + return v.replace(' ','').split(',') + +class Settings(BaseSettings): + # users + admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") + allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") + admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS") + disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS") + funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS") + # ops + data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER") + database_url: str = Field(default=None, env="LNBITS_DATABASE_URL") + force_https: bool = Field(default=True, env="LNBITS_FORCE_HTTPS") + service_fee: float = Field(default=0, env="LNBITS_SERVICE_FEE") + hide_api: bool = Field(default=False, env="LNBITS_HIDE_API") + denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") + # Change theme + site_title: str = Field(default=None, env="LNBITS_SITE_TITLE") + site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE") + site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") + default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME") + theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") + ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") + # .env + env: Optional[str] + debug: Optional[str] + host: Optional[str] + port: Optional[str] + lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) + + # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True) + # def validate(cls, val): + # print(val) + # return val.split(',') + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + case_sensitive = False + json_loads = list_parse_fallback + + +conf = Settings() +WALLET = wallet_class() diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 872d6c97..6fccb8ee 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -40,7 +40,7 @@ async def update_admin(user: str, **kwargs) -> Admin: # new_settings = await get_admin() # return new_settings -async def get_admin() -> List[Admin]: +async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 13b76923..574f772d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -2,93 +2,151 @@ from os import getenv from sqlalchemy.exc import OperationalError # type: ignore +from lnbits.config import conf from lnbits.helpers import urlsafe_short_hash async def m001_create_admin_table(db): - user = None - site_title = None - site_tagline = None - site_description = None - allowed_users = None - admin_users = None - default_wallet_name = None - data_folder = None - disabled_ext = None - force_https = True - service_fee = 0 - funding_source = "" + # users/server + user = conf.admin_users[0] + admin_users = ",".join(conf.admin_users) + allowed_users = ",".join(conf.allowed_users) + admin_ext = ",".join(conf.admin_ext) + disabled_ext = ",".join(conf.disabled_ext) + funding_source = conf.funding_source + #operational + data_folder = conf.data_folder + database_url = conf.database_url + force_https = conf.force_https + service_fee = conf.service_fee + hide_api = conf.hide_api + denomination = conf.denomination + # Theme'ing + site_title = conf.site_title + site_tagline = conf.site_tagline + site_description = conf.site_description + default_wallet_name = conf.default_wallet_name + theme = ",".join(conf.theme) + ad_space = ",".join(conf.ad_space) - if getenv("LNBITS_SITE_TITLE"): - site_title = getenv("LNBITS_SITE_TITLE") + # if getenv("LNBITS_ADMIN_EXTENSIONS"): + # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS") - if getenv("LNBITS_SITE_TAGLINE"): - site_tagline = getenv("LNBITS_SITE_TAGLINE") + # if getenv("LNBITS_DATABASE_URL"): + # database_url = getenv("LNBITS_DATABASE_URL") - if getenv("LNBITS_SITE_DESCRIPTION"): - site_description = getenv("LNBITS_SITE_DESCRIPTION") + # if getenv("LNBITS_HIDE_API"): + # hide_api = getenv("LNBITS_HIDE_API") - if getenv("LNBITS_ALLOWED_USERS"): - allowed_users = getenv("LNBITS_ALLOWED_USERS") + # if getenv("LNBITS_THEME_OPTIONS"): + # theme = getenv("LNBITS_THEME_OPTIONS") - if getenv("LNBITS_ADMIN_USERS"): - admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) - user = admin_users.split(',')[0] + # if getenv("LNBITS_AD_SPACE"): + # ad_space = getenv("LNBITS_AD_SPACE") - if getenv("LNBITS_DEFAULT_WALLET_NAME"): - default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") + # if getenv("LNBITS_SITE_TITLE"): + # site_title = getenv("LNBITS_SITE_TITLE") - if getenv("LNBITS_DATA_FOLDER"): - data_folder = getenv("LNBITS_DATA_FOLDER") + # if getenv("LNBITS_SITE_TAGLINE"): + # site_tagline = getenv("LNBITS_SITE_TAGLINE") - if getenv("LNBITS_DISABLED_EXTENSIONS"): - disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + # if getenv("LNBITS_SITE_DESCRIPTION"): + # site_description = getenv("LNBITS_SITE_DESCRIPTION") - if getenv("LNBITS_FORCE_HTTPS"): - force_https = getenv("LNBITS_FORCE_HTTPS") + # if getenv("LNBITS_ALLOWED_USERS"): + # allowed_users = getenv("LNBITS_ALLOWED_USERS") - if getenv("LNBITS_SERVICE_FEE"): - service_fee = getenv("LNBITS_SERVICE_FEE") + # if getenv("LNBITS_ADMIN_USERS"): + # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) + # user = admin_users.split(',')[0] + + # if getenv("LNBITS_DEFAULT_WALLET_NAME"): + # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") - if getenv("LNBITS_BACKEND_WALLET_CLASS"): - funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") + # if getenv("LNBITS_DATA_FOLDER"): + # data_folder = getenv("LNBITS_DATA_FOLDER") + + # if getenv("LNBITS_DISABLED_EXTENSIONS"): + # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + + # if getenv("LNBITS_FORCE_HTTPS"): + # force_https = getenv("LNBITS_FORCE_HTTPS") + + # if getenv("LNBITS_SERVICE_FEE"): + # service_fee = getenv("LNBITS_SERVICE_FEE") + + # if getenv("LNBITS_DENOMINATION"): + # denomination = getenv("LNBITS_DENOMINATION", "sats") + + # if getenv("LNBITS_BACKEND_WALLET_CLASS"): + # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") await db.execute( """ CREATE TABLE IF NOT EXISTS admin ( - "user" TEXT, + "user" TEXT PRIMARY KEY, + admin_users TEXT, + allowed_users TEXT, + admin_ext TEXT, + disabled_ext TEXT, + funding_source TEXT, + data_folder TEXT, + database_url TEXT, + force_https BOOLEAN, + service_fee REAL, + hide_api BOOLEAN, + denomination TEXT, site_title TEXT, site_tagline TEXT, site_description TEXT, - admin_users TEXT, - allowed_users TEXT, default_wallet_name TEXT, - data_folder TEXT, - disabled_ext TEXT, - force_https BOOLEAN, - service_fee REAL, - funding_source TEXT + theme TEXT, + ad_space TEXT ); """ ) await db.execute( """ - INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - user.strip(), + INSERT INTO admin ( + "user", + admin_users, + allowed_users, + admin_ext, + disabled_ext, + funding_source, + data_folder, + database_url, + force_https, + service_fee, + hide_api, + denomination, site_title, site_tagline, site_description, - admin_users[1:], - allowed_users, default_wallet_name, - data_folder, + theme, + ad_space) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + user, + admin_users, + allowed_users, + admin_ext, disabled_ext, + funding_source, + data_folder, + database_url, force_https, service_fee, - funding_source, + hide_api, + denomination, + site_title, + site_tagline, + site_description, + default_wallet_name, + theme, + ad_space, ), ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 4080ff01..f7c64de5 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -2,7 +2,7 @@ from sqlite3 import Row from typing import List, Optional from fastapi import Query -from pydantic import BaseModel +from pydantic import BaseModel, Field class UpdateAdminSettings(BaseModel): @@ -19,18 +19,27 @@ class UpdateAdminSettings(BaseModel): funding_source: Optional[str] class Admin(BaseModel): + # users user: str + admin_users: Optional[str] + allowed_users: Optional[str] + admin_ext: Optional[str] + disabled_ext: Optional[str] + funding_source: Optional[str] + # ops + data_folder: Optional[str] + database_url: Optional[str] + force_https: bool = Field(default=True) + service_fee: float = Field(default=0) + hide_api: bool = Field(default=False) + # Change theme site_title: Optional[str] site_tagline: Optional[str] site_description: Optional[str] - allowed_users: Optional[str] - admin_users: str - default_wallet_name: str - data_folder: str - disabled_ext: str - force_https: Optional[bool] = Query(True) - service_fee: float - funding_source: str + default_wallet_name: Optional[str] + denomination: str = Field(default="sats") + theme: Optional[str] + ad_space: Optional[str] @classmethod def from_row(cls, row: Row) -> "Admin": diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 00a0c99f..105f05a1 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -19,15 +19,17 @@ templates = Jinja2Templates(directory="templates") @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() - print(g()) funding = [f.dict() for f in await get_funding()] - + error, balance = await g().WALLET.status() print("ADMIN", admin.dict()) + print(g().admin_conf) return admin_renderer().TemplateResponse( "admin/index.html", { "request": request, "user": user.dict(), "admin": admin.dict(), - "funding": funding + "funding": funding, + "settings": g().admin_conf.dict(), + "balance": balance } ) diff --git a/lnbits/settings.py b/lnbits/settings.py index 43cb87cb..ed5c77f7 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -4,7 +4,7 @@ from email.policy import default from os import path from typing import List -from environs import Env # type: ignore +from environs import Env env = Env() env.read_env() From 66a7f53b976ae98a1e18cff8305da1299e524b69 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:28:07 +0000 Subject: [PATCH 012/565] topup wallet endpoint --- lnbits/extensions/admin/crud.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 6fccb8ee..683558f9 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,26 +1,31 @@ +import json from typing import List, Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash from lnbits.settings import * +from lnbits.tasks import internal_invoice_queue from . import db from .models import Admin, Funding -def update_wallet_balance(wallet_id: str, amount: int) -> str: +async def update_wallet_balance(wallet_id: str, amount: int) -> str: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" - create_payment( + + payment = await create_payment( wallet_id=wallet_id, checking_id=internal_id, payment_request="admin_internal", payment_hash="admin_internal", - amount=amount * 1000, + amount=amount*1000, memo="Admin top up", pending=False, ) - return "success" + # manually send this for now + await internal_invoice_queue.put(internal_id) + return payment async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) From 663c7ebd2f52c4cc0ea57839d921e3730d4decef Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:28:44 +0000 Subject: [PATCH 013/565] update admin settings in db --- lnbits/extensions/admin/models.py | 29 +++++++++++++++++----------- lnbits/extensions/admin/views_api.py | 8 ++++---- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index f7c64de5..36d9b815 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,17 +6,24 @@ from pydantic import BaseModel, Field class UpdateAdminSettings(BaseModel): - site_title: Optional[str] - site_tagline: Optional[str] - site_description: Optional[str] - allowed_users: Optional[str] - admin_users: Optional[str] - default_wallet_name: Optional[str] - data_folder: Optional[str] - disabled_ext: Optional[str] - force_https: Optional[bool] - service_fee: Optional[float] - funding_source: Optional[str] + # users + admin_users: str = Query(None) + allowed_users: str = Query(None) + admin_ext: str = Query(None) + disabled_ext: str = Query(None) + funding_source: str = Query(None) + # ops + force_https: bool = Query(None) + service_fee: float = Query(None, ge=0) + hide_api: bool = Query(None) + # Change theme + site_title: str = Query(None) + site_tagline: str = Query(None) + site_description: str = Query(None) + default_wallet_name: str = Query(None) + denomination: str = Query(None) + theme: str = Query(None) + ad_space: str = Query(None) class Admin(BaseModel): # users diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index b2c65be2..cb526aa5 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -12,16 +12,16 @@ from .crud import get_admin, update_admin, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) -async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)): - print(g.wallet) +async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)): try: wallet = await get_wallet(wallet_id) except: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - print(wallet) - print(topup_amount) + + await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) + return {"status": "Success"} From bc090190fca9a170566e1d605bdbdbd3536c70f5 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:29:18 +0000 Subject: [PATCH 014/565] update settings and topup logic --- .../admin/templates/admin/index.html | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 65ac9f33..e9ddc7c4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -30,7 +30,7 @@
- + @@ -61,7 +61,7 @@
- +

TopUp a wallet

@@ -87,13 +87,13 @@
- +
@@ -577,7 +577,6 @@

Site Description

s !== ad) }, - topupWallet: function () { - var self = this + topupWallet() { LNbits.api .request( 'GET', '/admin/api/v1/admin/' + - self.wallet.id + + this.wallet.data.id + '/' + - self.wallet.data.amount, - self.g.user.wallets[0].adminkey + this.wallet.data.amount, + this.g.user.wallets[0].adminkey ) - .then(function (response) { - self.$q.notify({ + .then((response) => { + this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - self.wallet.amount + - ' to ' + - self.wallet.id, + 'Success! Added ' + + this.wallet.data.amount + + ' to ' + + this.wallet.data.id, icon: null }) + this.wallet.data = {} }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -1224,36 +1224,59 @@ self.data.admin.edited.push(source) console.log(self.data.admin.edited) }, - UpdateLNbits: function () { - var self = this - let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin + UpdateLNbits() { + let { + admin_users, + allowed_users, + admin_ext, + disabled_ext, + funding_source, + force_https, + service_fee, + hide_api, + site_title, + site_tagline, + site_description, + default_wallet_name, + denomination, + theme, + ad_space + } = this.data.admin + //console.log("this", this.data.admin) let data = { - site_title, - site_tagline: this.data.admin.tagline, - site_description: this.data.admin.description, - admin_users: admin_users.toString(), - default_wallet_name, - data_folder, + admin_users: admin_users.toString(), + allowed_users: allowed_users.toString(), + admin_ext: admin_ext.toString(), disabled_ext: disabled_ext.toString(), - service_fee, - funding_source: funding_source_primary} + funding_source, + force_https, + service_fee, + hide_api, + site_title, + site_tagline, + site_description, + default_wallet_name, + denomination, + theme: theme.toString(), + ad_space: ad_space.toString() + } console.log(data) LNbits.api .request( 'POST', '/admin/api/v1/admin/', - self.g.user.wallets[0].adminkey, + this.g.user.wallets[0].adminkey, data ) - .then(function (response) { + .then(response => { console.log(response.data) - self.$q.notify({ + this.$q.notify({ type: 'positive', message: 'Success! Added ' + - self.wallet.amount + + this.wallet.amount + ' to ' + - self.wallet.id, + this.wallet.id, icon: null }) }) From 313574df1991c47b6a0dbc390f8d839278e200d4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:34:47 +0000 Subject: [PATCH 015/565] make removeEmptyString fn as helper fn --- lnbits/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index ccac1e00..5df439dc 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -26,6 +26,7 @@ from .helpers import ( get_css_vendored, get_js_vendored, get_valid_extensions, + removeEmptyString, template_renderer, url_for_vendored, ) @@ -113,9 +114,6 @@ def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): - def removeEmptyString(arr): - return list(filter(None, arr)) - while True: admin_set = await get_admin_settings() if admin_set : From edfa98f00e7111096321d259fca4cd2eb36ac3d5 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:35:15 +0000 Subject: [PATCH 016/565] add some defaults --- lnbits/config.py | 6 +++--- lnbits/extensions/admin/models.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 02e8cf53..b2fbfff1 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -33,10 +33,10 @@ class Settings(BaseSettings): hide_api: bool = Field(default=False, env="LNBITS_HIDE_API") denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") # Change theme - site_title: str = Field(default=None, env="LNBITS_SITE_TITLE") - site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE") + site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE") + site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") - default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME") + default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 36d9b815..0f25679d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -17,11 +17,11 @@ class UpdateAdminSettings(BaseModel): service_fee: float = Query(None, ge=0) hide_api: bool = Query(None) # Change theme - site_title: str = Query(None) - site_tagline: str = Query(None) + site_title: str = Query("LNbits") + site_tagline: str = Query("free and open-source lightning wallet") site_description: str = Query(None) - default_wallet_name: str = Query(None) - denomination: str = Query(None) + default_wallet_name: str = Query("LNbits wallet") + denomination: str = Query("sats") theme: str = Query(None) ad_space: str = Query(None) From ba6bda39ab4a6c50631658d9bee85329c4784950 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:36:04 +0000 Subject: [PATCH 017/565] removeEmtpy sting as helper fn --- lnbits/helpers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e213240c..e456f715 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -154,8 +154,20 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s url = f"{base}{endpoint}{url_params}" return url +def removeEmptyString(arr): + return list(filter(None, arr)) def template_renderer(additional_folders: List = []) -> Jinja2Templates: + if(settings.LNBITS_ADMIN_UI): + _ = g().admin_conf + settings.LNBITS_AD_SPACE = _.ad_space + settings.LNBITS_HIDE_API = _.hide_api + settings.LNBITS_SITE_TITLE = _.site_title + settings.LNBITS_DENOMINATION = _.denomination + settings.LNBITS_SITE_TAGLINE = _.site_tagline + settings.LNBITS_SITE_DESCRIPTION = _.site_description + settings.LNBITS_THEME_OPTIONS = _.theme + t = Jinja2Templates( loader=jinja2.FileSystemLoader( ["lnbits/templates", "lnbits/core/templates", *additional_folders] From 0a211a2fb2d1fdf7c135cf637b768c8fc2855a64 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:41:11 +0000 Subject: [PATCH 018/565] cleanup --- lnbits/extensions/admin/crud.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 683558f9..e14ad194 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,9 +1,7 @@ -import json -from typing import List, Optional +from typing import List from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import * from lnbits.tasks import internal_invoice_queue from . import db @@ -37,14 +35,6 @@ async def update_admin(user: str, **kwargs) -> Admin: assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None -# async def update_admin(user: str, **kwargs) -> Optional[Admin]: -# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) -# await db.execute( -# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user) -# ) -# new_settings = await get_admin() -# return new_settings - async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None From 5ec7f21650897699f39723bbe93edd3d53da1fd2 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:42:28 +0000 Subject: [PATCH 019/565] make string to list --- lnbits/extensions/admin/views_api.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index cb526aa5..1d4e6a9c 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,5 +1,6 @@ from http import HTTPStatus +# from config import conf from fastapi import Body, Depends, Request from starlette.exceptions import HTTPException @@ -7,6 +8,8 @@ from lnbits.core.crud import get_wallet from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import Admin, UpdateAdminSettings +from lnbits.helpers import removeEmptyString +from lnbits.requestvars import g from .crud import get_admin, update_admin, update_wallet_balance @@ -19,7 +22,7 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - + await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) return {"status": "Success"} @@ -29,14 +32,24 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D async def api_update_admin( request: Request, data: UpdateAdminSettings = Body(...), - g: WalletTypeInfo = Depends(require_admin_key) + w: WalletTypeInfo = Depends(require_admin_key) ): admin = await get_admin() print(data) - if not admin.user == g.wallet.user: + if not admin.user == w.wallet.user: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - updated = await update_admin(user=g.wallet.user, **data.dict()) - print(updated) + updated = await update_admin(user=w.wallet.user, **data.dict()) + + updated.admin_users = removeEmptyString(updated.admin_users.split(',')) + updated.allowed_users = removeEmptyString(updated.allowed_users.split(',')) + updated.admin_ext = removeEmptyString(updated.admin_ext.split(',')) + updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(',')) + updated.theme = removeEmptyString(updated.theme.split(',')) + updated.ad_space = removeEmptyString(updated.ad_space.split(',')) + + g().admin_conf = g().admin_conf.copy(update=updated.dict()) + + print(g().admin_conf) return {"status": "Success"} From 4d16c296aa1d8b8375e3f131e459a2f2f80f0d3b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:42:47 +0000 Subject: [PATCH 020/565] success message --- lnbits/extensions/admin/templates/admin/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index e9ddc7c4..9aa4f12a 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1273,10 +1273,7 @@ this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - this.wallet.amount + - ' to ' + - this.wallet.id, + 'Success! Settings changed!', icon: null }) }) From 2c48e3aa5f1e561e4106c60bfe0e4266a86711b3 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 14 Apr 2022 10:42:26 +0100 Subject: [PATCH 021/565] allow html to be passed to description --- lnbits/core/templates/core/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index f769b44f..03cf706f 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -82,7 +82,7 @@ >
-

{{SITE_DESCRIPTION}}

+

{{SITE_DESCRIPTION | safe}}

From f16ead4f7303787d945ab9dc39550ae3bc0ec611 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 14 Apr 2022 16:37:13 +0100 Subject: [PATCH 022/565] update funding wallets --- lnbits/extensions/admin/crud.py | 12 + .../admin/templates/admin/index.html | 523 ++++++++++-------- lnbits/extensions/admin/views.py | 3 +- lnbits/extensions/admin/views_api.py | 19 +- 4 files changed, 315 insertions(+), 242 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index e14ad194..dd39e8e4 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -39,6 +39,18 @@ async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None +async def update_funding(data: Funding) -> Funding: + await db.execute( + """ + UPDATE funding + SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? + WHERE id = ? + """, + (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), + ) + row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,)) + assert row, "Newly updated settings couldn't be retrieved" + return Funding(**row) if row else None async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM funding") diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 9aa4f12a..d56b3d79 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -31,11 +31,11 @@ - - - -
Wallets Management
-
+ + + +
Wallets Management
+
@@ -62,43 +62,96 @@
-

TopUp a wallet

-
-
- -
-
-
- -
+

TopUp a wallet

+
+
+ +
-
- +
+
+
+
+ +

Funding Sources

- + {% raw %} + + + + + + + + + + + + + + {% endraw %} + +
+ + + + +
User Management
+
+

+ Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %} +

+

Admin Users

+ hint="Users with admin privileges" + >
{% raw %} -
-
-
-

Allowed Users

- - - +
- {% raw %} - Allowed Users

+ - {{ user }} -
- {% endraw %} + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+ + + + +
Server Management

-
-
-
-

Admin Extensions

- -
-
-
-

Disabled Extensions

- -
-
-
-
- Save -
-
-
- - -
Server Management
-

Server Info

    {%raw%} -
  • SQlite: {{data.admin.data_folder}}
  • -
  • Postgres: {{data.admin.database_url}}
  • +
  • + SQlite: {{data.admin.data_folder}} +
  • +
  • + Postgres: {{data.admin.database_url}} +
  • {%endraw%}

@@ -520,7 +576,10 @@ Hide API - Hides wallet api, extensions can choose to honor + Hides wallet api, extensions can choose to + honor
-
- -
- Save -
-
-
- - -
UI Management
-
+
+ +
+ Save +
+ + + + +
UI Management
+
@@ -575,15 +629,15 @@
-

Site Description

- -
-
+

Site Description

+ +
+

Default Wallet Name

@@ -628,12 +682,15 @@ @keydown.enter="addAdSpace" type="text" label="Ad image URL" - hint="Ad image filepaths or urls, extensions can choose to honor"> + hint="Ad image filepaths or urls, extensions can choose to honor" + >
{% raw %} -
-
- -
- Save -
-
-
- - +
+ +
+ Save +
+
+
+
+
- - -

Admin

-

+ + - -
- - -
Wallet topup
-
-
- -
-
- -
-
-
- -
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -1100,14 +1114,22 @@ 'LnbitsWallet', 'OpenNodeWallet' ], - + admin: { edited: [], - funding: {}, + funding: [], senddata: {} } }, - themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'], + themes: [ + 'classic', + 'bitcoin', + 'flamingo', + 'mint', + 'autumn', + 'monochrome', + 'salvador' + ], options: [ 'bleskomat', 'captcha', @@ -1139,10 +1161,13 @@ self.cancel.on = true } funding = JSON.parse(String('{{ funding | tojson|safe }}')) - var i + funding.map(f => { + this.data.admin.funding.push(f) + }) + /*var i for (i = 0; i < funding.length; i++) { self.data.admin.funding[funding[i].backend_wallet] = funding[i] - } + }*/ let settings = JSON.parse('{{ settings | tojson|safe }}') settings.balance = '{{ balance }}' this.data.admin = {...this.data.admin, ...settings} @@ -1150,42 +1175,42 @@ console.log(settings) }, methods: { - addAdminUser(){ + addAdminUser() { let addUser = this.data.admin_users_add let admin_users = this.data.admin.admin_users - if(addUser.length && !admin_users.includes(addUser)){ + if (addUser.length && !admin_users.includes(addUser)) { admin_users.push(addUser) this.data.admin.admin_users = admin_users - this.data.admin_users_add = "" + this.data.admin_users_add = '' } }, - removeAdminUser(user){ + removeAdminUser(user) { let admin_users = this.data.admin.admin_users this.data.admin.admin_users = admin_users.filter(u => u !== user) }, - addAllowedUser(){ + addAllowedUser() { let addUser = this.data.allowed_users_add let allowed_users = this.data.admin.allowed_users - if(addUser.length && !allowed_users.includes(addUser)){ + if (addUser.length && !allowed_users.includes(addUser)) { allowed_users.push(addUser) this.data.admin.allowed_users = allowed_users - this.data.allowed_users_add = "" + this.data.allowed_users_add = '' } }, - removeAllowedUser(user){ + removeAllowedUser(user) { let allowed_users = this.data.admin.allowed_users this.data.admin.allowed_users = allowed_users.filter(u => u !== user) }, - addAdSpace(){ + addAdSpace() { let adSpace = this.data.ad_space_add let spaces = this.data.admin.ad_space - if(adSpace.length && !spaces.includes(adSpace)){ + if (adSpace.length && !spaces.includes(adSpace)) { spaces.push(adSpace) this.data.admin.ad_space = spaces - this.data.ad_space_add = "" + this.data.ad_space_add = '' } }, - removeAdSpace(ad){ + removeAdSpace(ad) { let spaces = this.data.admin.ad_space this.data.admin.ad_space = spaces.filter(s => s !== ad) }, @@ -1199,14 +1224,14 @@ this.wallet.data.amount, this.g.user.wallets[0].adminkey ) - .then((response) => { + .then(response => { this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - this.wallet.data.amount + - ' to ' + - this.wallet.data.id, + 'Success! Added ' + + this.wallet.data.amount + + ' to ' + + this.wallet.data.id, icon: null }) this.wallet.data = {} @@ -1224,6 +1249,29 @@ self.data.admin.edited.push(source) console.log(self.data.admin.edited) }, + updateFunding(fund) { + let data = this.data.admin.funding.find(v => v.backend_wallet == fund) + + LNbits.api + .request( + 'POST', + '/admin/api/v1/admin/funding', + this.g.user.wallets[0].adminkey, + data + ) + .then(response => { + //let wallet = response.data.backend_wallet + //this.data.admin.funding[wallet] = response.data + //this.data.admin.funding[wallet].endpoint = response.data.endpoint + //console.log(this.data.admin.funding) + //console.log(this.data.admin) + this.$q.notify({ + type: 'positive', + message: `Success! ${response.data.backend_wallet} changed!`, + icon: null + }) + }) + }, UpdateLNbits() { let { admin_users, @@ -1272,8 +1320,7 @@ console.log(response.data) this.$q.notify({ type: 'positive', - message: - 'Success! Settings changed!', + message: 'Success! Settings changed!', icon: null }) }) diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 105f05a1..24b8ca85 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -21,8 +21,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() funding = [f.dict() for f in await get_funding()] error, balance = await g().WALLET.status() - print("ADMIN", admin.dict()) - print(g().admin_conf) + return admin_renderer().TemplateResponse( "admin/index.html", { "request": request, diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 1d4e6a9c..b797dc2d 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.extensions.admin import admin_ext -from lnbits.extensions.admin.models import Admin, UpdateAdminSettings +from lnbits.extensions.admin.models import Admin, Funding, UpdateAdminSettings from lnbits.helpers import removeEmptyString from lnbits.requestvars import g -from .crud import get_admin, update_admin, update_wallet_balance +from .crud import get_admin, update_admin, update_funding, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) @@ -53,3 +53,18 @@ async def api_update_admin( print(g().admin_conf) return {"status": "Success"} + +@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) +async def api_update_funding( + request: Request, + data: Funding = Body(...), + w: WalletTypeInfo = Depends(require_admin_key) + ): + admin = await get_admin() + + if not admin.user == w.wallet.user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) + funding = await update_funding(data=data) + return funding From 5a3ad81c315f42a7b482714e943a1b0a72028e93 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 18 Apr 2022 14:25:06 +0100 Subject: [PATCH 023/565] allow user settings without restart --- lnbits/core/views/generic.py | 7 ++++++- lnbits/decorators.py | 8 ++++++++ lnbits/helpers.py | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 31a7b030..83648c44 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,7 +15,9 @@ from lnbits.core import db from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer, url_for +from lnbits.requestvars import g from lnbits.settings import ( + LNBITS_ADMIN_UI, LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, LNBITS_CUSTOM_LOGO, @@ -37,7 +39,6 @@ from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) - @core_html_routes.get("/favicon.ico", response_class=FileResponse) async def favicon(): return FileResponse("lnbits/core/static/favicon.ico") @@ -119,6 +120,10 @@ async def wallet( wallet_name = nme service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + if not user_id: user = await get_user((await create_account()).id) logger.info(f"Create user {user.id}") # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index d4aa63ae..f951163f 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -16,6 +16,7 @@ from lnbits.core.models import User, Wallet from lnbits.requestvars import g from lnbits.settings import ( LNBITS_ADMIN_EXTENSIONS, + LNBITS_ADMIN_UI, LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, ) @@ -138,6 +139,9 @@ async def get_key_type( detail="Invoice (or Admin) key required.", ) + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + for typenr, WalletChecker in zip( [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] ): @@ -231,6 +235,10 @@ async def check_user_exists(usr: UUID4) -> User: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) + + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: raise HTTPException( diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e456f715..1167143f 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -24,6 +24,9 @@ class Extension(NamedTuple): class ExtensionManager: def __init__(self): + if settings.LNBITS_ADMIN_UI: + settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext + settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS self._admin_only: List[str] = [ x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS From 1ff8a9fce5c15d421a8c37a3f8bc908a4b249510 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 18 Apr 2022 14:25:26 +0100 Subject: [PATCH 024/565] advert for server restart option --- lnbits/extensions/admin/templates/admin/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d56b3d79..089c5f1c 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -51,7 +51,9 @@
-

Active Funding

+

+ Active Funding (Requires server restart) +

Date: Thu, 21 Apr 2022 11:08:26 +0100 Subject: [PATCH 025/565] cleanup prints and console logs --- lnbits/extensions/admin/crud.py | 2 +- lnbits/extensions/admin/templates/admin/index.html | 4 ++-- lnbits/extensions/admin/views_api.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index dd39e8e4..f866bc1a 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -27,7 +27,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - print("UPDATE", q) + # print("UPDATE", q) await db.execute( f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 089c5f1c..584d3a33 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1310,7 +1310,7 @@ theme: theme.toString(), ad_space: ad_space.toString() } - console.log(data) + //console.log(data) LNbits.api .request( 'POST', @@ -1319,7 +1319,7 @@ data ) .then(response => { - console.log(response.data) + //console.log(response.data) this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index b797dc2d..c0650c8a 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -35,7 +35,7 @@ async def api_update_admin( w: WalletTypeInfo = Depends(require_admin_key) ): admin = await get_admin() - print(data) + # print(data) if not admin.user == w.wallet.user: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" @@ -51,7 +51,7 @@ async def api_update_admin( g().admin_conf = g().admin_conf.copy(update=updated.dict()) - print(g().admin_conf) + # print(g().admin_conf) return {"status": "Success"} @admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) From b0f9c82e1b0b16e3e1d499f5173a0368e5d9c93e Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 10:49:21 +0100 Subject: [PATCH 026/565] create first user on fresh install --- .env.example | 4 ++-- lnbits/config.py | 3 ++- lnbits/extensions/admin/README.md | 15 ++++++++------- lnbits/extensions/admin/migrations.py | 15 ++++++++++++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index bd189484..7a49d5c5 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,8 @@ PORT=5000 DEBUG=false LNBITS_ADMIN_USERS="" # User IDs seperated by comma -LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS +LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access +LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma diff --git a/lnbits/config.py b/lnbits/config.py index b2fbfff1..3ce51c3c 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -19,6 +19,7 @@ def list_parse_fallback(v): return v.replace(' ','').split(',') class Settings(BaseSettings): + admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI") # users admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") @@ -37,7 +38,7 @@ class Settings(BaseSettings): site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") - theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") + theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env env: Optional[str] diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md index 27729459..6cf073a1 100644 --- a/lnbits/extensions/admin/README.md +++ b/lnbits/extensions/admin/README.md @@ -1,11 +1,12 @@ -

Example Extension

-

*tagline*

-This is an example extension to help you organise and build you own. +# Admin Extension -Try to include an image - +## Dashboard to manage LNbits from the UI +With AdminUI you can manage your LNbits from the UI -

If your extension has API endpoints, include useful ones here

+![AdminUI](https://i.imgur.com/BIyLkyG.png) -curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" +## Before you start + +**This extension doesn't discard the need for the `.env` file!** +In the .env file, set the `LNBITS_ADMIN_USERS` variable to include at least your user id. diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 574f772d..0e22e667 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,9 +6,22 @@ from lnbits.config import conf from lnbits.helpers import urlsafe_short_hash +async def get_admin_user(): + if(conf.admin_users[0]): + return conf.admin_users[0] + from lnbits.core.crud import create_account, get_user + print("Seems like there's no admin users yet. Let's create an account for you!") + account = await create_account() + user = account.id + assert user, "Newly created user couldn't be retrieved" + print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + return user + + + async def m001_create_admin_table(db): # users/server - user = conf.admin_users[0] + user = await get_admin_user() admin_users = ",".join(conf.admin_users) allowed_users = ",".join(conf.allowed_users) admin_ext = ",".join(conf.admin_ext) From 2f2d70f9a8cdd3edc59cb3c71b841b6823309dcf Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 12:29:58 +0100 Subject: [PATCH 027/565] fix schemas for admin --- lnbits/commands.py | 11 ++++++++--- lnbits/extensions/admin/crud.py | 12 ++++++------ lnbits/extensions/admin/migrations.py | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lnbits/commands.py b/lnbits/commands.py index 8c39c338..7d9b49e2 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -55,8 +55,12 @@ def bundle_vendored(): async def get_admin_settings(): from lnbits.extensions.admin.models import Admin - async with core_db.connect() as conn: + try: + ext_db = importlib.import_module(f"lnbits.extensions.admin").db + except: + return False + async with ext_db.connect() as conn: if conn.type == SQLITE: exists = await conn.fetchone( "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" @@ -65,11 +69,12 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) + print("EXISTS", exists) if not exists: return False - row = await conn.fetchone("SELECT * from admin") - + row = await conn.fetchone("SELECT * from admin.admin") + return Admin(**row) if row else None async def migrate_databases(): diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index f866bc1a..67fbc614 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -29,30 +29,30 @@ async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) await db.execute( - f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) + f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,)) + row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,)) assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None async def get_admin() -> Admin: - row = await db.fetchone("SELECT * FROM admin") + row = await db.fetchone("SELECT * FROM admin.admin") return Admin(**row) if row else None async def update_funding(data: Funding) -> Funding: await db.execute( """ - UPDATE funding + UPDATE admin.funding SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? WHERE id = ? """, (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), ) - row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,)) + row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,)) assert row, "Newly updated settings couldn't be retrieved" return Funding(**row) if row else None async def get_funding() -> List[Funding]: - rows = await db.fetchall("SELECT * FROM funding") + rows = await db.fetchall("SELECT * FROM admin.funding") return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 0e22e667..c94d140b 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -96,7 +96,7 @@ async def m001_create_admin_table(db): await db.execute( """ - CREATE TABLE IF NOT EXISTS admin ( + CREATE TABLE IF NOT EXISTS admin.admin ( "user" TEXT PRIMARY KEY, admin_users TEXT, allowed_users TEXT, @@ -120,7 +120,7 @@ async def m001_create_admin_table(db): ) await db.execute( """ - INSERT INTO admin ( + INSERT INTO admin.admin ( "user", admin_users, allowed_users, @@ -171,7 +171,7 @@ async def m001_create_funding_table(db): # Make the funding table, if it does not already exist await db.execute( """ - CREATE TABLE IF NOT EXISTS funding ( + CREATE TABLE IF NOT EXISTS admin.funding ( id TEXT PRIMARY KEY, backend_wallet TEXT, endpoint TEXT, @@ -188,7 +188,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, selected) VALUES (?, ?, ?, ?) """, ( @@ -200,7 +200,7 @@ async def m001_create_funding_table(db): ) await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -214,7 +214,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -228,7 +228,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( @@ -244,7 +244,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?) """, ( @@ -259,7 +259,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?) """, ( @@ -274,7 +274,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -288,7 +288,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -302,7 +302,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -317,7 +317,7 @@ async def m001_create_funding_table(db): ## PLACEHOLDER FOR ECLAIR WALLET # await db.execute( # """ - # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) # VALUES (?, ?, ?, ?, ?) # """, # ( From 08e54de99b72eec2c8f7850ef0d346c15bf64705 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 12:53:47 +0100 Subject: [PATCH 028/565] fix sqlite and show user account --- lnbits/app.py | 1 + lnbits/commands.py | 2 +- lnbits/extensions/admin/migrations.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index 5df439dc..f066163f 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -117,6 +117,7 @@ def check_settings(app: FastAPI): while True: admin_set = await get_admin_settings() if admin_set : + print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") break print("ERROR:", admin_set) await asyncio.sleep(5) diff --git a/lnbits/commands.py b/lnbits/commands.py index 7d9b49e2..763a5b90 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -69,7 +69,7 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) - print("EXISTS", exists) + if not exists: return False diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index c94d140b..6c5b507d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -15,6 +15,7 @@ async def get_admin_user(): user = account.id assert user, "Newly created user couldn't be retrieved" print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + conf.admin_users.insert(0, user) return user From 1adfb674ccb6bf44ad6e3680c795ed085bd20d8e Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 15:35:04 +0100 Subject: [PATCH 029/565] cleanup and info to user on startup --- lnbits/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index f066163f..950b6140 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -113,13 +113,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI: def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): - while True: admin_set = await get_admin_settings() if admin_set : - print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") break - print("ERROR:", admin_set) + print("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) @@ -129,6 +127,7 @@ def check_settings(app: FastAPI): admin_set.theme = removeEmptyString(admin_set.theme.split(',')) admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) g().admin_conf = conf.copy(update=admin_set.dict()) + print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") From 363bc85e3b73967c26b9809f1d71664ec8719d8d Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 8 Jun 2022 11:00:43 +0100 Subject: [PATCH 030/565] add custom logo --- lnbits/config.py | 1 + lnbits/extensions/admin/migrations.py | 58 ++----------------- lnbits/extensions/admin/models.py | 2 + .../admin/templates/admin/index.html | 20 +++++-- lnbits/helpers.py | 3 +- 5 files changed, 26 insertions(+), 58 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 3ce51c3c..d07ca044 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -39,6 +39,7 @@ class Settings(BaseSettings): site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") + custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env env: Optional[str] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 6c5b507d..aad66f02 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -41,60 +41,9 @@ async def m001_create_admin_table(db): site_description = conf.site_description default_wallet_name = conf.default_wallet_name theme = ",".join(conf.theme) + custom_logo = conf.custom_logo ad_space = ",".join(conf.ad_space) - # if getenv("LNBITS_ADMIN_EXTENSIONS"): - # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS") - - # if getenv("LNBITS_DATABASE_URL"): - # database_url = getenv("LNBITS_DATABASE_URL") - - # if getenv("LNBITS_HIDE_API"): - # hide_api = getenv("LNBITS_HIDE_API") - - # if getenv("LNBITS_THEME_OPTIONS"): - # theme = getenv("LNBITS_THEME_OPTIONS") - - # if getenv("LNBITS_AD_SPACE"): - # ad_space = getenv("LNBITS_AD_SPACE") - - # if getenv("LNBITS_SITE_TITLE"): - # site_title = getenv("LNBITS_SITE_TITLE") - - # if getenv("LNBITS_SITE_TAGLINE"): - # site_tagline = getenv("LNBITS_SITE_TAGLINE") - - # if getenv("LNBITS_SITE_DESCRIPTION"): - # site_description = getenv("LNBITS_SITE_DESCRIPTION") - - # if getenv("LNBITS_ALLOWED_USERS"): - # allowed_users = getenv("LNBITS_ALLOWED_USERS") - - # if getenv("LNBITS_ADMIN_USERS"): - # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) - # user = admin_users.split(',')[0] - - # if getenv("LNBITS_DEFAULT_WALLET_NAME"): - # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") - - # if getenv("LNBITS_DATA_FOLDER"): - # data_folder = getenv("LNBITS_DATA_FOLDER") - - # if getenv("LNBITS_DISABLED_EXTENSIONS"): - # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") - - # if getenv("LNBITS_FORCE_HTTPS"): - # force_https = getenv("LNBITS_FORCE_HTTPS") - - # if getenv("LNBITS_SERVICE_FEE"): - # service_fee = getenv("LNBITS_SERVICE_FEE") - - # if getenv("LNBITS_DENOMINATION"): - # denomination = getenv("LNBITS_DENOMINATION", "sats") - - # if getenv("LNBITS_BACKEND_WALLET_CLASS"): - # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") - await db.execute( """ CREATE TABLE IF NOT EXISTS admin.admin ( @@ -115,6 +64,7 @@ async def m001_create_admin_table(db): site_description TEXT, default_wallet_name TEXT, theme TEXT, + custom_logo TEXT, ad_space TEXT ); """ @@ -139,8 +89,9 @@ async def m001_create_admin_table(db): site_description, default_wallet_name, theme, + custom_logo, ad_space) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( user, @@ -160,6 +111,7 @@ async def m001_create_admin_table(db): site_description, default_wallet_name, theme, + custom_logo, ad_space, ), ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 0f25679d..3b17e720 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -23,6 +23,7 @@ class UpdateAdminSettings(BaseModel): default_wallet_name: str = Query("LNbits wallet") denomination: str = Query("sats") theme: str = Query(None) + custom_logo: str = Query(None) ad_space: str = Query(None) class Admin(BaseModel): @@ -46,6 +47,7 @@ class Admin(BaseModel): default_wallet_name: Optional[str] denomination: str = Field(default="sats") theme: Optional[str] + custom_logo: Optional[str] ad_space: Optional[str] @classmethod diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 584d3a33..d9790051 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -705,6 +705,19 @@
+
+
+

Custom Logo

+ +
+
+
@@ -718,10 +731,7 @@
- +
@@ -1292,6 +1323,8 @@ disabled_ext, funding_source, force_https, + reserve_fee_min, + reserve_fee_pct, service_fee, hide_api, site_title, @@ -1311,6 +1344,8 @@ disabled_ext: disabled_ext.toString(), funding_source, force_https, + reserve_fee_min, + reserve_fee_pct, service_fee, hide_api, site_title, diff --git a/lnbits/settings.py b/lnbits/settings.py index ed5c77f7..8e5c321a 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,6 +1,5 @@ import importlib import subprocess -from email.policy import default from os import path from typing import List From fcf05a7dd19b831ee986578efad9b2d9094e69bd Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 8 Jun 2022 15:38:28 +0100 Subject: [PATCH 032/565] calle's semantics --- lnbits/extensions/admin/templates/admin/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 832629bc..d34b9068 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -67,7 +67,7 @@
-

Minimum wallet reserve

+

Fee reserve

From faab389e3fa21671313b66236e66fccbf1f70a1d Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 5 Jul 2022 16:25:02 +0100 Subject: [PATCH 033/565] blacked --- lnbits/extensions/admin/__init__.py | 1 + lnbits/extensions/admin/crud.py | 23 ++++++++++++--- lnbits/extensions/admin/migrations.py | 10 ++++--- lnbits/extensions/admin/models.py | 2 ++ lnbits/extensions/admin/views.py | 10 ++++--- lnbits/extensions/admin/views_api.py | 41 ++++++++++++++------------- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py index 6a56b2bb..24b91fe2 100644 --- a/lnbits/extensions/admin/__init__.py +++ b/lnbits/extensions/admin/__init__.py @@ -7,6 +7,7 @@ db = Database("ext_admin") admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"]) + def admin_renderer(): return template_renderer(["lnbits/extensions/admin/templates"]) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 67fbc614..0d7019cc 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -11,13 +11,13 @@ from .models import Admin, Funding async def update_wallet_balance(wallet_id: str, amount: int) -> str: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" - + payment = await create_payment( wallet_id=wallet_id, checking_id=internal_id, payment_request="admin_internal", payment_hash="admin_internal", - amount=amount*1000, + amount=amount * 1000, memo="Admin top up", pending=False, ) @@ -25,6 +25,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: await internal_invoice_queue.put(internal_id) return payment + async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) @@ -35,23 +36,37 @@ async def update_admin(user: str, **kwargs) -> Admin: assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None + async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin.admin") return Admin(**row) if row else None + async def update_funding(data: Funding) -> Funding: await db.execute( """ UPDATE admin.funding SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? WHERE id = ? - """, - (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), + """, + ( + data.backend_wallet, + data.endpoint, + data.port, + data.read_key, + data.invoice_key, + data.admin_key, + data.cert, + data.balance, + data.selected, + data.id, + ), ) row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,)) assert row, "Newly updated settings couldn't be retrieved" return Funding(**row) if row else None + async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM admin.funding") diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index f3663435..388f5ec6 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -7,19 +7,21 @@ from lnbits.helpers import urlsafe_short_hash async def get_admin_user(): - if(conf.admin_users[0]): + if conf.admin_users[0]: return conf.admin_users[0] from lnbits.core.crud import create_account, get_user + print("Seems like there's no admin users yet. Let's create an account for you!") account = await create_account() user = account.id assert user, "Newly created user couldn't be retrieved" - print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + print( + f"Your newly created account/user id is: {user}. This will be the Super Admin user." + ) conf.admin_users.insert(0, user) return user - async def m001_create_admin_table(db): # users/server user = await get_admin_user() @@ -28,7 +30,7 @@ async def m001_create_admin_table(db): admin_ext = ",".join(conf.admin_ext) disabled_ext = ",".join(conf.disabled_ext) funding_source = conf.funding_source - #operational + # operational data_folder = conf.data_folder database_url = conf.database_url force_https = conf.force_https diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 3d8efdcd..6e95d68f 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -28,6 +28,7 @@ class UpdateAdminSettings(BaseModel): custom_logo: str = Query(None) ad_space: str = Query(None) + class Admin(BaseModel): # users user: str @@ -59,6 +60,7 @@ class Admin(BaseModel): data = dict(row) return cls(**data) + class Funding(BaseModel): id: str backend_wallet: str diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 24b8ca85..ceda5192 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -16,19 +16,21 @@ from .crud import get_admin, get_funding templates = Jinja2Templates(directory="templates") + @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() funding = [f.dict() for f in await get_funding()] error, balance = await g().WALLET.status() - + return admin_renderer().TemplateResponse( - "admin/index.html", { + "admin/index.html", + { "request": request, "user": user.dict(), "admin": admin.dict(), "funding": funding, "settings": g().admin_conf.dict(), - "balance": balance - } + "balance": balance, + }, ) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c0650c8a..784ad97f 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -15,16 +15,18 @@ from .crud import get_admin, update_admin, update_funding, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) -async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)): +async def api_update_balance( + wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key) +): try: wallet = await get_wallet(wallet_id) except: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) - + return {"status": "Success"} @@ -32,39 +34,40 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D async def api_update_admin( request: Request, data: UpdateAdminSettings = Body(...), - w: WalletTypeInfo = Depends(require_admin_key) - ): + w: WalletTypeInfo = Depends(require_admin_key), +): admin = await get_admin() # print(data) if not admin.user == w.wallet.user: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) updated = await update_admin(user=w.wallet.user, **data.dict()) - updated.admin_users = removeEmptyString(updated.admin_users.split(',')) - updated.allowed_users = removeEmptyString(updated.allowed_users.split(',')) - updated.admin_ext = removeEmptyString(updated.admin_ext.split(',')) - updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(',')) - updated.theme = removeEmptyString(updated.theme.split(',')) - updated.ad_space = removeEmptyString(updated.ad_space.split(',')) + updated.admin_users = removeEmptyString(updated.admin_users.split(",")) + updated.allowed_users = removeEmptyString(updated.allowed_users.split(",")) + updated.admin_ext = removeEmptyString(updated.admin_ext.split(",")) + updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(",")) + updated.theme = removeEmptyString(updated.theme.split(",")) + updated.ad_space = removeEmptyString(updated.ad_space.split(",")) g().admin_conf = g().admin_conf.copy(update=updated.dict()) - + # print(g().admin_conf) return {"status": "Success"} + @admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) async def api_update_funding( request: Request, data: Funding = Body(...), - w: WalletTypeInfo = Depends(require_admin_key) - ): + w: WalletTypeInfo = Depends(require_admin_key), +): admin = await get_admin() if not admin.user == w.wallet.user: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) funding = await update_funding(data=data) return funding From c319b84d7299eb5f7d942214fab2f21deffb38f6 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:28:13 +0100 Subject: [PATCH 034/565] Had to add a couple of tries --- lnbits/core/views/generic.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 83648c44..63f7af68 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -133,12 +133,19 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User does not exist."} ) - if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: - return template_renderer().TemplateResponse( - "error.html", {"request": request, "err": "User not authorized."} - ) - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True + try: + if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: + return template_renderer().TemplateResponse( + "error.html", {"request": request, "err": "User not authorized."} + ) + except: + pass + + try: + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True + except: + pass if not wallet_id: if user.wallets and not wallet_name: # type: ignore wallet = user.wallets[0] # type: ignore From d8a13ed29d323f5233c6d9ba5cb59f9ef352d90c Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:31:31 +0100 Subject: [PATCH 035/565] Added couple more tries --- lnbits/decorators.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index f951163f..a810892d 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -239,13 +239,16 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users - - if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - - if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: - g().user.admin = True - + try: + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + except: + pass + try: + if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: + g().user.admin = True + except: + pass return g().user From 55a44030283b34b6d61c0d441b779b135b449c3a Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:35:06 +0100 Subject: [PATCH 036/565] Reverted try --- lnbits/decorators.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index a810892d..904ca1c2 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -239,16 +239,12 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users - try: - if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - except: - pass - try: - if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: - g().user.admin = True + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: + g().user.admin = True except: pass return g().user From a932f8a3d0c43692f6ec2b1ae9284c279ab7396c Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:37:07 +0100 Subject: [PATCH 037/565] reverted other try --- lnbits/core/views/generic.py | 19 ++++++------------- lnbits/decorators.py | 2 -- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 63f7af68..83648c44 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -133,19 +133,12 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User does not exist."} ) - try: - if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: - return template_renderer().TemplateResponse( - "error.html", {"request": request, "err": "User not authorized."} - ) - except: - pass - - try: - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True - except: - pass + if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: + return template_renderer().TemplateResponse( + "error.html", {"request": request, "err": "User not authorized."} + ) + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True if not wallet_id: if user.wallets and not wallet_name: # type: ignore wallet = user.wallets[0] # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 904ca1c2..dd26d8fe 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -245,6 +245,4 @@ async def check_user_exists(usr: UUID4) -> User: ) if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: g().user.admin = True - except: - pass return g().user From c32e0cbecb147918b2c95ad29bcaf8876855ac0b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 21 Sep 2022 18:40:46 +0100 Subject: [PATCH 038/565] fix main merge missing settings --- lnbits/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lnbits/app.py b/lnbits/app.py index 950b6140..49ef3d1b 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -56,6 +56,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI: "url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE", }, ) + if lnbits.settings.LNBITS_ADMIN_UI: + g().admin_conf = conf + check_settings(app) + + g().WALLET = WALLET app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") app.mount( "/core/static", From e4c310d197fbb6ddb9a7d5528aab66fe60608313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 10:46:11 +0200 Subject: [PATCH 039/565] format --- lnbits/app.py | 22 +++++++++++++--------- lnbits/commands.py | 6 +++++- lnbits/config.py | 25 ++++++++++++++++++------- lnbits/core/views/generic.py | 1 + lnbits/decorators.py | 2 +- lnbits/helpers.py | 8 +++++--- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 49ef3d1b..00ed6d8d 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -115,24 +115,28 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app + def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): while True: admin_set = await get_admin_settings() - if admin_set : + if admin_set: break print("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) - - admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) - admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(',')) - admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(',')) - admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(',')) - admin_set.theme = removeEmptyString(admin_set.theme.split(',')) - admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) + + admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(",")) + admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(",")) + admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(",")) + admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(",")) + admin_set.theme = removeEmptyString(admin_set.theme.split(",")) + admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(",")) g().admin_conf = conf.copy(update=admin_set.dict()) - print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") + print( + f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}" + ) + def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") diff --git a/lnbits/commands.py b/lnbits/commands.py index 763a5b90..86868f1f 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -5,6 +5,7 @@ import re import warnings import click +from genericpath import exists from loguru import logger from .core import db as core_db @@ -52,6 +53,7 @@ def bundle_vendored(): with open(outputpath, "w") as f: f.write(output) + async def get_admin_settings(): from lnbits.extensions.admin.models import Admin @@ -61,6 +63,7 @@ async def get_admin_settings(): return False async with ext_db.connect() as conn: + if conn.type == SQLITE: exists = await conn.fetchone( "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" @@ -69,7 +72,7 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) - + if not exists: return False @@ -77,6 +80,7 @@ async def get_admin_settings(): return Admin(**row) if row else None + async def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" diff --git a/lnbits/config.py b/lnbits/config.py index 37b700fd..cf26ad21 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -6,17 +6,19 @@ from typing import List, Optional from pydantic import BaseSettings, Field wallets_module = importlib.import_module("lnbits.wallets") -wallet_class = getattr( +wallet_class = getattr( wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet") ) WALLET = wallet_class() + def list_parse_fallback(v): try: return json.loads(v) except Exception as e: - return v.replace(' ','').split(',') + return v.replace(" ", "").split(",") + class Settings(BaseSettings): admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI") @@ -24,7 +26,9 @@ class Settings(BaseSettings): admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS") - disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS") + disabled_ext: List[str] = Field( + default_factory=list, env="LNBITS_DISABLED_EXTENSIONS" + ) funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS") # ops data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER") @@ -37,10 +41,17 @@ class Settings(BaseSettings): denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") # Change theme site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE") - site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") + site_tagline: str = Field( + default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE" + ) site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") - default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") - theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") + default_wallet_name: str = Field( + default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME" + ) + theme: List[str] = Field( + default=["classic, flamingo, mint, salvador, monochrome, autumn"], + env="LNBITS_THEME_OPTIONS", + ) custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env @@ -48,7 +59,7 @@ class Settings(BaseSettings): debug: Optional[str] host: Optional[str] port: Optional[str] - lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) + lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True) # def validate(cls, val): diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 83648c44..3a1fbdfc 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -39,6 +39,7 @@ from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) + @core_html_routes.get("/favicon.ico", response_class=FileResponse) async def favicon(): return FileResponse("lnbits/core/static/favicon.ico") diff --git a/lnbits/decorators.py b/lnbits/decorators.py index dd26d8fe..58b025aa 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -235,7 +235,7 @@ async def check_user_exists(usr: UUID4) -> User: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) - + if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users diff --git a/lnbits/helpers.py b/lnbits/helpers.py index 7bd1b54a..f4255c86 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -157,11 +157,13 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s url = f"{base}{endpoint}{url_params}" return url + def removeEmptyString(arr): return list(filter(None, arr)) + def template_renderer(additional_folders: List = []) -> Jinja2Templates: - if(settings.LNBITS_ADMIN_UI): + if settings.LNBITS_ADMIN_UI: _ = g().admin_conf settings.LNBITS_AD_SPACE = _.ad_space settings.LNBITS_HIDE_API = _.hide_api @@ -170,8 +172,8 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates: settings.LNBITS_SITE_TAGLINE = _.site_tagline settings.LNBITS_SITE_DESCRIPTION = _.site_description settings.LNBITS_THEME_OPTIONS = _.theme - settings.LNBITS_CUSTOM_LOGO = _.custom_logo - + settings.LNBITS_CUSTOM_LOGO = _.custom_logo + t = Jinja2Templates( loader=jinja2.FileSystemLoader( ["lnbits/templates", "lnbits/core/templates", *additional_folders] From 6c2a9b2258087302487716e3d35b1b2387bb58ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 11:47:24 +0200 Subject: [PATCH 040/565] format black --- lnbits/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/app.py b/lnbits/app.py index 00ed6d8d..6ae75d7a 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -56,6 +56,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: "url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE", }, ) + if lnbits.settings.LNBITS_ADMIN_UI: g().admin_conf = conf check_settings(app) From 7c05d4c354be10d502b7c453f96902f76f9c15d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 15:29:12 +0200 Subject: [PATCH 041/565] fix AD_SPACE --- lnbits/core/templates/core/wallet.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index bccdc2b4..189b060b 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -385,12 +385,9 @@ - {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = - ADS.split(';') %} + {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - {% endfor %} {% endif %}
From 415165c6fe7ec2affda20078c81e12b4fc2944de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 15:55:37 +0200 Subject: [PATCH 042/565] fix some javascript errors when adding users --- lnbits/extensions/admin/templates/admin/index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d34b9068..1e881cb6 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1221,7 +1221,7 @@ addAdminUser() { let addUser = this.data.admin_users_add let admin_users = this.data.admin.admin_users - if (addUser.length && !admin_users.includes(addUser)) { + if (addUser && addUser.length && !admin_users.includes(addUser)) { admin_users.push(addUser) this.data.admin.admin_users = admin_users this.data.admin_users_add = '' @@ -1234,7 +1234,7 @@ addAllowedUser() { let addUser = this.data.allowed_users_add let allowed_users = this.data.admin.allowed_users - if (addUser.length && !allowed_users.includes(addUser)) { + if (addUser && addUser.length && !allowed_users.includes(addUser)) { allowed_users.push(addUser) this.data.admin.allowed_users = allowed_users this.data.allowed_users_add = '' @@ -1336,7 +1336,6 @@ custom_logo, ad_space } = this.data.admin - //console.log("this", this.data.admin) let data = { admin_users: admin_users.toString(), allowed_users: allowed_users.toString(), From 850ed85311fea5c5e10bca8cdeaaa0a3dffbe71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 16:04:55 +0200 Subject: [PATCH 043/565] fix ADMIN_UI=false errors --- lnbits/core/views/generic.py | 3 +++ lnbits/decorators.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 3a1fbdfc..db4fac43 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -124,6 +124,9 @@ async def wallet( if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + else: + LNBITS_ADMIN_USERS = [] + LNBITS_ALLOWED_USERS = [] if not user_id: user = await get_user((await create_account()).id) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 58b025aa..5a3c0a5c 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -141,6 +141,8 @@ async def get_key_type( if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users + else: + LNBITS_ADMIN_USERS = [] for typenr, WalletChecker in zip( [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] @@ -239,6 +241,10 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + else: + LNBITS_ADMIN_USERS = [] + LNBITS_ALLOWED_USERS = [] + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: raise HTTPException( status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." From 393d1a8204f04498aca712604f5646dcb30d7746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 16:14:17 +0200 Subject: [PATCH 044/565] prettier --- lnbits/core/templates/core/wallet.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 189b060b..22c65bf3 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -386,8 +386,7 @@ {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - - {% endfor %} {% endif %} From 622d0f50da4b319765175c97613ab31e5e1dd722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 18:35:47 +0200 Subject: [PATCH 045/565] fix migration tests --- lnbits/extensions/admin/migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 388f5ec6..196c9fc0 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -7,7 +7,7 @@ from lnbits.helpers import urlsafe_short_hash async def get_admin_user(): - if conf.admin_users[0]: + if len(conf.admin_users) > 0: return conf.admin_users[0] from lnbits.core.crud import create_account, get_user From 26769d94984c5d7c4f56b1bd06ca4ce3e376453c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 26 Sep 2022 16:54:19 +0200 Subject: [PATCH 046/565] change comments to use multiple lines --- .env.example | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 7a49d5c5..c1ac7497 100644 --- a/.env.example +++ b/.env.example @@ -3,11 +3,15 @@ PORT=5000 DEBUG=false -LNBITS_ADMIN_USERS="" # User IDs seperated by comma -LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available +# User IDs seperated by comma +LNBITS_ADMIN_USERS="" +# Extensions only admin can access +LNBITS_ADMIN_EXTENSIONS="ngrok, admin" +# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available +LNBITS_ADMIN_UI=false -LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma +# Restricts access, User IDs seperated by comma +LNBITS_ALLOWED_USERS="" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" From cadb6b21617c77fac36ea55937a03c223ec5aa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 26 Sep 2022 16:59:30 +0200 Subject: [PATCH 047/565] fix merge error --- lnbits/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 6ae75d7a..60c09038 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -57,10 +57,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) - if lnbits.settings.LNBITS_ADMIN_UI: - g().admin_conf = conf - check_settings(app) - g().WALLET = WALLET app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") app.mount( From a78ebbacd700c7975662424e07bb042a2da380f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:14:36 +0200 Subject: [PATCH 048/565] use logger in app.py --- lnbits/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 60c09038..d7bd3ea6 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -31,8 +31,6 @@ from .helpers import ( url_for_vendored, ) from .requestvars import g - -# from .settings import WALLET from .tasks import ( catch_everything_and_restart, check_pending_payments, @@ -120,7 +118,7 @@ def check_settings(app: FastAPI): admin_set = await get_admin_settings() if admin_set: break - print("Waiting for admin settings... retrying in 5 seconds!") + logger.info("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(",")) @@ -130,7 +128,7 @@ def check_settings(app: FastAPI): admin_set.theme = removeEmptyString(admin_set.theme.split(",")) admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(",")) g().admin_conf = conf.copy(update=admin_set.dict()) - print( + logger.info( f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}" ) From 8be9b950fdff51e8c9d94d4123cae0dfb650dd3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:14:57 +0200 Subject: [PATCH 049/565] fix config --- lnbits/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index cf26ad21..874effae 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -17,7 +17,11 @@ def list_parse_fallback(v): try: return json.loads(v) except Exception as e: - return v.replace(" ", "").split(",") + replaced = v.replace(" ", "") + if replaced: + return replaced.split(",") + else: + return [] class Settings(BaseSettings): @@ -48,10 +52,7 @@ class Settings(BaseSettings): default_wallet_name: str = Field( default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME" ) - theme: List[str] = Field( - default=["classic, flamingo, mint, salvador, monochrome, autumn"], - env="LNBITS_THEME_OPTIONS", - ) + theme: List[str] = Field(default_factory=list, env="LNBITS_THEME_OPTIONS") custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env @@ -74,4 +75,5 @@ class Settings(BaseSettings): conf = Settings() +print(conf) WALLET = wallet_class() From f2e494384e96587b0b0fb2a9de8825aa9115ee21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:17:20 +0200 Subject: [PATCH 050/565] concatenate first migrations script fixup --- lnbits/config.py | 1 - lnbits/extensions/admin/migrations.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 874effae..fe8dabf9 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -75,5 +75,4 @@ class Settings(BaseSettings): conf = Settings() -print(conf) WALLET = wallet_class() diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 196c9fc0..2d48a8e4 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -23,6 +23,8 @@ async def get_admin_user(): async def m001_create_admin_table(db): + + # users/server user = await get_admin_user() admin_users = ",".join(conf.admin_users) @@ -78,7 +80,7 @@ async def m001_create_admin_table(db): await db.execute( """ INSERT INTO admin.admin ( - "user", + "user", admin_users, allowed_users, admin_ext, @@ -126,9 +128,6 @@ async def m001_create_admin_table(db): ), ) - -async def m001_create_funding_table(db): - funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") # Make the funding table, if it does not already exist From 9757fad8684d166ce9ac04e6a69b0ff820c4de61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 16:37:08 +0200 Subject: [PATCH 051/565] add restart button to frontend --- .../admin/templates/admin/index.html | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 1e881cb6..319ca3f0 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -65,6 +65,14 @@ :options="data.funding_source" >
+
+ +

Fee reserve

@@ -89,7 +97,7 @@ >
-
+ @@ -1257,6 +1265,24 @@ let spaces = this.data.admin.ad_space this.data.admin.ad_space = spaces.filter(s => s !== ad) }, + restartServer() { + LNbits.api + .request( + 'GET', + '/admin/api/v1/admin/restart/', + this.g.user.wallets[0].adminkey + ) + .then(response => { + this.$q.notify({ + type: 'positive', + message: 'Success! Restarted Server', + icon: null + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, topupWallet() { LNbits.api .request( From 66739f4246bdc6d3b347f54f675c00937601935e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:34:52 +0200 Subject: [PATCH 052/565] make extension use new settings --- lnbits/extensions/boltz/boltz.py | 14 ++++++-------- lnbits/extensions/boltz/mempool.py | 15 ++++++--------- lnbits/extensions/boltz/views_api.py | 4 ++-- lnbits/extensions/lndhub/views_api.py | 4 ++-- lnbits/extensions/tpos/views.py | 14 +++++++------- 5 files changed, 23 insertions(+), 28 deletions(-) diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py index ac99d4f4..424d0bf7 100644 --- a/lnbits/extensions/boltz/boltz.py +++ b/lnbits/extensions/boltz/boltz.py @@ -12,7 +12,7 @@ from loguru import logger from lnbits.core.services import create_invoice, pay_invoice from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL +from lnbits.settings import settings from .crud import update_swap_status from .mempool import ( @@ -33,9 +33,7 @@ from .models import ( ) from .utils import check_balance, get_timestamp, req_wrap -net = NETWORKS[BOLTZ_NETWORK] -logger.trace(f"BOLTZ_URL: {BOLTZ_URL}") -logger.trace(f"Bitcoin Network: {net['name']}") +net = NETWORKS[settings.boltz_network] async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: @@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: res = req_wrap( "post", - f"{BOLTZ_URL}/createswap", + f"{settings.boltz_url}/createswap", json={ "type": "submarine", "pairId": "BTC/BTC", @@ -129,7 +127,7 @@ async def create_reverse_swap( res = req_wrap( "post", - f"{BOLTZ_URL}/createswap", + f"{settings.boltz_url}/createswap", json={ "type": "reversesubmarine", "pairId": "BTC/BTC", @@ -409,7 +407,7 @@ def check_boltz_limits(amount): def get_boltz_pairs(): res = req_wrap( "get", - f"{BOLTZ_URL}/getpairs", + f"{settings.boltz_url}/getpairs", headers={"Content-Type": "application/json"}, ) return res.json() @@ -418,7 +416,7 @@ def get_boltz_pairs(): def get_boltz_status(boltzid): res = req_wrap( "post", - f"{BOLTZ_URL}/swapstatus", + f"{settings.boltz_url}/swapstatus", json={"id": boltzid}, ) return res.json() diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py index a44c0f02..a64cadad 100644 --- a/lnbits/extensions/boltz/mempool.py +++ b/lnbits/extensions/boltz/mempool.py @@ -7,14 +7,11 @@ import websockets from embit.transaction import Transaction from loguru import logger -from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS +from lnbits.settings import settings from .utils import req_wrap -logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") -logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") - -websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws" +websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws" async def wait_for_websocket_message(send, message_string): @@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string): def get_mempool_tx(address): res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs", + f"{settings.boltz_mempool_space_url}/api/address/{address}/txs", headers={"Content-Type": "text/plain"}, ) txs = res.json() @@ -70,7 +67,7 @@ def get_fee_estimation() -> int: def get_mempool_fees() -> int: res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended", + f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended", headers={"Content-Type": "text/plain"}, ) fees = res.json() @@ -80,7 +77,7 @@ def get_mempool_fees() -> int: def get_mempool_blockheight() -> int: res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height", + f"{settings.boltz_mempool_space_url}/api/blocks/tip/height", headers={"Content-Type": "text/plain"}, ) return int(res.text) @@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction): logger.debug(f"Boltz - mempool sending onchain tx...") req_wrap( "post", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx", + f"{settings.boltz_mempool_space_url}/api/tx", headers={"Content-Type": "text/plain"}, content=raw, ) diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py index a4b7d318..18ca14cb 100644 --- a/lnbits/extensions/boltz/views_api.py +++ b/lnbits/extensions/boltz/views_api.py @@ -14,7 +14,7 @@ from starlette.requests import Request from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL +from lnbits.settings import settings from . import boltz_ext from .boltz import ( @@ -55,7 +55,7 @@ from .utils import check_balance response_model=str, ) async def api_mempool_url(): - return BOLTZ_MEMPOOL_SPACE_URL + return settings.boltz_mempool_space_url # NORMAL SWAP diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py index 8cbe5a6b..b2328c39 100644 --- a/lnbits/extensions/lndhub/views_api.py +++ b/lnbits/extensions/lndhub/views_api.py @@ -12,7 +12,7 @@ from lnbits import bolt11 from lnbits.core.crud import delete_expired_invoices, get_payments from lnbits.core.services import create_invoice, pay_invoice from lnbits.decorators import WalletTypeInfo -from lnbits.settings import LNBITS_SITE_TITLE, WALLET +from lnbits.settings import WALLET, settings from . import lndhub_ext from .decorators import check_wallet, require_admin_key @@ -56,7 +56,7 @@ async def lndhub_addinvoice( _, pr = await create_invoice( wallet_id=wallet.wallet.id, amount=int(data.amt), - memo=data.memo or LNBITS_SITE_TITLE, + memo=data.memo or settings.lnbits_site_title, extra={"tag": "lndhub"}, ) except: diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py index e1f1d21e..dac129a9 100644 --- a/lnbits/extensions/tpos/views.py +++ b/lnbits/extensions/tpos/views.py @@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE +from lnbits.settings import settings from . import tpos_ext, tpos_renderer from .crud import get_tpos @@ -50,12 +50,12 @@ async def manifest(tpos_id: str): ) return { - "short_name": LNBITS_SITE_TITLE, - "name": tpos.name + " - " + LNBITS_SITE_TITLE, + "short_name": settings.lnbits_site_title, + "name": tpos.name + " - " + settings.lnbits_site_title, "icons": [ { - "src": LNBITS_CUSTOM_LOGO - if LNBITS_CUSTOM_LOGO + "src": settings.lnbits_custom_logo + if settings.lnbits_custom_logo else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", "type": "image/png", "sizes": "900x900", @@ -69,9 +69,9 @@ async def manifest(tpos_id: str): "theme_color": "#1F2234", "shortcuts": [ { - "name": tpos.name + " - " + LNBITS_SITE_TITLE, + "name": tpos.name + " - " + settings.lnbits_site_title, "short_name": tpos.name, - "description": tpos.name + " - " + LNBITS_SITE_TITLE, + "description": tpos.name + " - " + settings.lnbits_site_title, "url": "/tpos/" + tpos_id, } ], From 2b2884142fd9c2ac6fbfa3cbd8916aaf2225068a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:35:26 +0200 Subject: [PATCH 053/565] remove enviroms --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 19dac860..7a69ec43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ charset-normalizer = "2.0.6" click = "8.0.1" ecdsa = "0.17.0" embit = "0.4.9" -environs = "9.3.3" fastapi = "0.78.0" h11 = "0.12.0" httpcore = "0.15.0" From 840ede1bd66fd437e342bbefc0880de6fc2f216c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:36:14 +0200 Subject: [PATCH 054/565] fix admin --- lnbits/extensions/admin/crud.py | 24 +- lnbits/extensions/admin/migrations.py | 337 +---- lnbits/extensions/admin/models.py | 58 +- .../admin/templates/admin/_tab_funding.html | 158 +++ .../admin/templates/admin/_tab_server.html | 78 ++ .../admin/templates/admin/_tab_theme.html | 122 ++ .../admin/templates/admin/_tab_users.html | 96 ++ .../admin/templates/admin/index.html | 1133 +---------------- lnbits/extensions/admin/views.py | 16 +- lnbits/extensions/admin/views_api.py | 28 +- 10 files changed, 576 insertions(+), 1474 deletions(-) create mode 100644 lnbits/extensions/admin/templates/admin/_tab_funding.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_server.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_theme.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_users.html diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 0d7019cc..e4cb5d77 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -2,10 +2,11 @@ from typing import List from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash +from lnbits.settings import Settings from lnbits.tasks import internal_invoice_queue from . import db -from .models import Admin, Funding +from .models import Funding async def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -23,26 +24,26 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: ) # manually send this for now await internal_invoice_queue.put(internal_id) - return payment -async def update_admin(user: str, **kwargs) -> Admin: +async def update_settings(user: str, **kwargs) -> Settings: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) await db.execute( - f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) + f'UPDATE admin.settings SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,)) + row = await db.fetchone('SELECT * FROM admin.settings WHERE "user" = ?', (user,)) assert row, "Newly updated settings couldn't be retrieved" - return Admin(**row) if row else None - - -async def get_admin() -> Admin: - row = await db.fetchone("SELECT * FROM admin.admin") - return Admin(**row) if row else None + return Settings(**row) if row else None async def update_funding(data: Funding) -> Funding: + await db.execute( + """ + UPDATE admin.settings SET funding_source = ? WHERE user = ? + """, + (data.backend_wallet, data.user), + ) await db.execute( """ UPDATE admin.funding @@ -69,5 +70,4 @@ async def update_funding(data: Funding) -> Funding: async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM admin.funding") - return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 2d48a8e4..8f6c76a0 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -1,292 +1,57 @@ -from os import getenv - -from sqlalchemy.exc import OperationalError # type: ignore - -from lnbits.config import conf -from lnbits.helpers import urlsafe_short_hash - - -async def get_admin_user(): - if len(conf.admin_users) > 0: - return conf.admin_users[0] - from lnbits.core.crud import create_account, get_user - - print("Seems like there's no admin users yet. Let's create an account for you!") - account = await create_account() - user = account.id - assert user, "Newly created user couldn't be retrieved" - print( - f"Your newly created account/user id is: {user}. This will be the Super Admin user." - ) - conf.admin_users.insert(0, user) - return user - - -async def m001_create_admin_table(db): - - - # users/server - user = await get_admin_user() - admin_users = ",".join(conf.admin_users) - allowed_users = ",".join(conf.allowed_users) - admin_ext = ",".join(conf.admin_ext) - disabled_ext = ",".join(conf.disabled_ext) - funding_source = conf.funding_source - # operational - data_folder = conf.data_folder - database_url = conf.database_url - force_https = conf.force_https - reserve_fee_min = conf.reserve_fee_min - reserve_fee_pct = conf.reserve_fee_pct - service_fee = conf.service_fee - hide_api = conf.hide_api - denomination = conf.denomination - # Theme'ing - site_title = conf.site_title - site_tagline = conf.site_tagline - site_description = conf.site_description - default_wallet_name = conf.default_wallet_name - theme = ",".join(conf.theme) - custom_logo = conf.custom_logo - ad_space = ",".join(conf.ad_space) - +async def m001_create_admin_settings_table(db): await db.execute( """ - CREATE TABLE IF NOT EXISTS admin.admin ( - "user" TEXT PRIMARY KEY, - admin_users TEXT, - allowed_users TEXT, - admin_ext TEXT, - disabled_ext TEXT, - funding_source TEXT, - data_folder TEXT, - database_url TEXT, - force_https BOOLEAN, - reserve_fee_min INT, - reserve_fee_pct REAL, - service_fee REAL, - hide_api BOOLEAN, - denomination TEXT, - site_title TEXT, - site_tagline TEXT, - site_description TEXT, - default_wallet_name TEXT, - theme TEXT, - custom_logo TEXT, - ad_space TEXT - ); - """ - ) - await db.execute( - """ - INSERT INTO admin.admin ( - "user", - admin_users, - allowed_users, - admin_ext, - disabled_ext, - funding_source, - data_folder, - database_url, - force_https, - reserve_fee_min, - reserve_fee_pct, - service_fee, - hide_api, - denomination, - site_title, - site_tagline, - site_description, - default_wallet_name, - theme, - custom_logo, - ad_space) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - user, - admin_users, - allowed_users, - admin_ext, - disabled_ext, - funding_source, - data_folder, - database_url, - force_https, - reserve_fee_min, - reserve_fee_pct, - service_fee, - hide_api, - denomination, - site_title, - site_tagline, - site_description, - default_wallet_name, - theme, - custom_logo, - ad_space, - ), - ) - - funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") - - # Make the funding table, if it does not already exist - await db.execute( - """ - CREATE TABLE IF NOT EXISTS admin.funding ( - id TEXT PRIMARY KEY, - backend_wallet TEXT, - endpoint TEXT, + CREATE TABLE IF NOT EXISTS admin.settings ( + lnbits_admin_ui TEXT, + debug TEXT, + host TEXT, port INT, - read_key TEXT, - invoice_key TEXT, - admin_key TEXT, - cert TEXT, - balance INT, - selected INT + lnbits_path TEXT, + lnbits_commit TEXT, + lnbits_admin_users TEXT, + lnbits_allowed_users TEXT, + lnbits_allowed_funding_sources TEXT, + lnbits_admin_extensions TEXT, + lnbits_disabled_extensions TEXT, + lnbits_site_title TEXT, + lnbits_site_tagline TEXT, + lnbits_site_description TEXT, + lnbits_default_wallet_name TEXT, + lnbits_theme_options TEXT, + lnbits_custom_logo TEXT, + lnbits_ad_space TEXT, + lnbits_data_folder TEXT, + lnbits_database_url TEXT, + lnbits_force_https TEXT, + lnbits_reserve_fee_min TEXT, + lnbits_reserve_fee_percent TEXT, + lnbits_service_fee TEXT, + lnbits_hide_api TEXT, + lnbits_denomination TEXT, + lnbits_backend_wallet_class TEXT, + fake_wallet_secret TEXT, + lnbits_endpoint TEXT, + lnbits_key TEXT, + cliche_endpoint TEXT, + corelightning_rpc TEXT, + eclair_url TEXT, + eclair_pass TEXT, + lnd_rest_endpoint TEXT, + lnd_rest_cert TEXT, + lnd_rest_macaroon TEXT, + lnpay_api_endpoint TEXT, + lnpay_api_key TEXT, + lnpay_wallet_key TEXT, + lntxbot_api_endpoint TEXT, + lntxbot_key TEXT, + opennode_api_endpoint TEXT, + opennode_key TEXT, + spark_url TEXT, + spark_token TEXT, + boltz_network TEXT, + boltz_url TEXT, + boltz_mempool_space_url TEXT, + boltz_mempool_space_url_ws TEXT ); """ ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, selected) - VALUES (?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "CLightningWallet", - getenv("CLIGHTNING_RPC"), - 1 if funding_wallet == "CLightningWallet" else 0, - ), - ) - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "SparkWallet", - getenv("SPARK_URL"), - getenv("SPARK_TOKEN"), - 1 if funding_wallet == "SparkWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LnbitsWallet", - getenv("LNBITS_ENDPOINT"), - getenv("LNBITS_KEY"), - 1 if funding_wallet == "LnbitsWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LndWallet", - getenv("LND_GRPC_ENDPOINT"), - getenv("LND_GRPC_PORT"), - getenv("LND_GRPC_MACAROON"), - getenv("LND_GRPC_CERT"), - 1 if funding_wallet == "LndWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LndRestWallet", - getenv("LND_REST_ENDPOINT"), - getenv("LND_REST_MACAROON"), - getenv("LND_REST_CERT"), - 1 if funding_wallet == "LndWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LNPayWallet", - getenv("LNPAY_API_ENDPOINT"), - getenv("LNPAY_WALLET_KEY"), - getenv("LNPAY_API_KEY"), # this is going in as the cert - 1 if funding_wallet == "LNPayWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LntxbotWallet", - getenv("LNTXBOT_API_ENDPOINT"), - getenv("LNTXBOT_KEY"), - 1 if funding_wallet == "LntxbotWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "OpenNodeWallet", - getenv("OPENNODE_API_ENDPOINT"), - getenv("OPENNODE_KEY"), - 1 if funding_wallet == "OpenNodeWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "SparkWallet", - getenv("SPARK_URL"), - getenv("SPARK_TOKEN"), - 1 if funding_wallet == "SparkWallet" else 0, - ), - ) - - ## PLACEHOLDER FOR ECLAIR WALLET - # await db.execute( - # """ - # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - # VALUES (?, ?, ?, ?, ?) - # """, - # ( - # urlsafe_short_hash(), - # "EclairWallet", - # getenv("ECLAIR_URL"), - # getenv("ECLAIR_PASS"), - # 1 if funding_wallet == "EclairWallet" else 0, - # ), - # ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 6e95d68f..ef57cadd 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -29,36 +29,36 @@ class UpdateAdminSettings(BaseModel): ad_space: str = Query(None) -class Admin(BaseModel): - # users - user: str - admin_users: Optional[str] - allowed_users: Optional[str] - admin_ext: Optional[str] - disabled_ext: Optional[str] - funding_source: Optional[str] - # ops - data_folder: Optional[str] - database_url: Optional[str] - force_https: bool = Field(default=True) - reserve_fee_min: Optional[int] - reserve_fee_pct: Optional[float] - service_fee: float = Optional[float] - hide_api: bool = Field(default=False) - # Change theme - site_title: Optional[str] - site_tagline: Optional[str] - site_description: Optional[str] - default_wallet_name: Optional[str] - denomination: str = Field(default="sats") - theme: Optional[str] - custom_logo: Optional[str] - ad_space: Optional[str] +# class Admin(BaseModel): +# # users +# user: str +# admin_users: Optional[str] +# allowed_users: Optional[str] +# admin_ext: Optional[str] +# disabled_ext: Optional[str] +# funding_source: Optional[str] +# # ops +# data_folder: Optional[str] +# database_url: Optional[str] +# force_https: bool = Field(default=True) +# reserve_fee_min: Optional[int] +# reserve_fee_pct: Optional[float] +# service_fee: float = Optional[float] +# hide_api: bool = Field(default=False) +# # Change theme +# site_title: Optional[str] +# site_tagline: Optional[str] +# site_description: Optional[str] +# default_wallet_name: Optional[str] +# denomination: str = Field(default="sats") +# theme: Optional[str] +# custom_logo: Optional[str] +# ad_space: Optional[str] - @classmethod - def from_row(cls, row: Row) -> "Admin": - data = dict(row) - return cls(**data) +# @classmethod +# def from_row(cls, row: Row) -> "Admin": +# data = dict(row) +# return cls(**data) class Funding(BaseModel): diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html new file mode 100644 index 00000000..2ed0aae2 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -0,0 +1,158 @@ + + +
Wallets Management
+
+
+
+
+

Funding Source Info

+
    + {%raw%} +
  • + Funding Source: {{data.settings.lnbits_backend_wallet_class}} +
  • +
  • Balance: {{data.balance / 1000}} sats
  • + {%endraw%} +
+
+
+
+
+
+
+
+

Active Funding (Requires server restart)

+ +
+
+ +
+
+
+

Fee reserve

+
+
+ +
+
+ +
+
+
+
+
+
+

TopUp a wallet

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+

Funding Sources

+ {% raw %} + + + + + + + + + + + + + + {% endraw %} +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_server.html b/lnbits/extensions/admin/templates/admin/_tab_server.html new file mode 100644 index 00000000..2924e6a4 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_server.html @@ -0,0 +1,78 @@ + + +
Server Management
+
+
+
+
+

Server Info

+
    + {%raw%} +
  • + SQlite: {{data.settings.lnbits_data_folder}} +
  • +
  • + Postgres: {{data.settings.lnbits_database_url}} +
  • + {%endraw%} +
+
+
+
+
+
+

Service Fee

+ +
+
+
+

Miscelaneous

+ + + Force HTTPS + Prefer secure URLs + + + + + + + + Hide API + Hides wallet api, extensions can choose to honor + + + + + +
+
+
+
+ +
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html new file mode 100644 index 00000000..41dc0447 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html @@ -0,0 +1,122 @@ + + +
UI Management
+
+
+
+
+

Site Title

+ +
+
+
+

Site Tagline

+ +
+
+
+
+

Site Description

+ +
+
+
+
+

Default Wallet Name

+ +
+
+
+

Denomination

+ +
+
+
+
+
+

Themes

+ +
+
+
+

Advertisement Slots

+ + + +
+ {% raw %} + + {{ space.slice(0, 8) + " ... " + space.slice(-8) }} + + {% endraw %} +
+
+
+
+
+
+

Custom Logo

+ +
+
+
+
+ +
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html new file mode 100644 index 00000000..3eb53ac4 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -0,0 +1,96 @@ + + +
User Management
+
+

+ Super Admin: {% raw %}{{this.data.settings.lnbits_admin_users[0]}}{% + endraw %} +

+
+
+

Admin Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Allowed Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 319ca3f0..87e89321 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -29,1118 +29,16 @@ - - - -
Wallets Management
-
-
-
-
-

Funding Source Info

-
    - {%raw%} -
  • Funding Source: {{data.admin.funding_source}}
  • -
  • Balance: {{data.admin.balance / 1000}} sats
  • - {%endraw%} -
-
-
-
-
-
-
-
-

- Active Funding - (Requires server restart) -

- -
-
- -
-
-
-

Fee reserve

-
-
- -
-
- -
-
- -
-
-
-
- -

TopUp a wallet

-
-
- -
-
-
- -
-
-
- -
- -
-
-
-

Funding Sources

- {% raw %} - - - - - - - - - - - - - - {% endraw %} - -
-
-
- - -
User Management
-
-

- Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %} -

-
-
-

Admin Users

- - - -
- {% raw %} - - {{ user }} - - {% endraw %} -
-
-
-
-

Allowed Users

- - - -
- {% raw %} - - {{ user }} - - {% endraw %} -
-
-
-
-
-

Admin Extensions

- -
-
-
-

Disabled Extensions

- -
-
-
-
- Save -
-
-
- - -
Server Management
-
-
-
-
-

Server Info

-
    - {%raw%} -
  • - SQlite: {{data.admin.data_folder}} -
  • -
  • - Postgres: {{data.admin.database_url}} -
  • - {%endraw%} -
-
-
-
-
-
-

Service Fee

- -
-
-
-

Miscelaneous

- - - Force HTTPS - Prefer secure URLs - - - - - - - - Hide API - Hides wallet api, extensions can choose to - honor - - - - - -
-
-
-
- -
- Save -
-
-
- - -
UI Management
-
-
-
-
-

Site Title

- -
-
-
-

Site Tagline

- -
-
-
-
-

Site Description

- -
-
-
-
-

Default Wallet Name

- -
-
-
-

Denomination

- -
-
-
-
-
-

Themes

- -
-
-
-

Advertisement Slots

- - - -
- {% raw %} - - {{ space.slice(0, 8) + " ... " + space.slice(-8) }} - - {% endraw %} -
-
-
-
-
-
-

Custom Logo

- -
-
-
-
- -
- Save -
-
-
+ {% include "admin/_tab_funding.html" %} {% include + "admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {% + include "admin/_tab_theme.html" %}
- - -
- -
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} From 3778990000848fc74c23e4fdab3696f73edda4b1 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 7 Oct 2022 19:24:07 +0100 Subject: [PATCH 095/565] make saving possible (possible will change in future) --- .../admin/templates/admin/index.html | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 72352651..18df16a9 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -121,8 +121,11 @@ created: function () { this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data this.balance = +'{{ balance|safe }}' - this.formData = this.settings //model + this.formData = _.clone(this.settings) //model + //this.formData.lnbits_ad_space = "hdh" console.log(this.formData) + console.log(_.isEqual(this.settings, this.formData)) + }, methods: { addAdminUser() { @@ -206,18 +209,27 @@ }, updateSettings() { let data = { - ...this.settings, - ...this.formData + lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class, + lnbits_admin_users: this.formData.lnbits_admin_users.toString(), + lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(), + lnbits_admin_ext: this.formData.lnbits_admin_ext, + lnbits_disabled_ext: this.formData.lnbits_disabled_ext, + lnbits_funding_source: this.formData.lnbits_funding_source, + lnbits_force_https: this.formData.lnbits_force_https, + lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min, + lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent, + lnbits_service_fee: this.formData.lnbits_service_fee, + lnbits_hide_api: this.formData.lnbits_hide_api, + lnbits_site_title: this.formData.lnbits_site_title, + lnbits_site_tagline: this.formData.lnbits_site_tagline, + lnbits_site_description: this.formData.lnbits_site_description, + lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name, + lnbits_denomination: this.formData.lnbits_denomination, + lnbits_theme: this.formData.lnbits_theme, + lnbits_custom_logo: this.formData.lnbits_custom_logo, + lnbits_ad_space: this.formData.lnbits_ad_space.toString() } - /* - const formElement = document.getElementById('settings_form') - const formData = new FormData(formElement) - const data = {} - formData.forEach((value, key) => (data[key] = value)) - // only for debugging - for (const [key, value] of formData) { - console.log(`${key}: ${value}\n`) - }*/ + console.log(data) LNbits.api .request( 'PUT', From 3fdafca0a15bb9931fbfe3a61b7a228c91b2d855 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 7 Oct 2022 19:44:03 +0100 Subject: [PATCH 096/565] some more refining --- lnbits/extensions/admin/models.py | 10 +++++----- .../extensions/admin/templates/admin/_tab_users.html | 2 ++ lnbits/extensions/admin/templates/admin/index.html | 12 +++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 13a6cd23..45cd990d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -4,10 +4,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: str = Query(None) - lnbits_allowed_users: str = Query(None) - lnbits_admin_ext: str = Query(None) - lnbits_disabled_ext: str = Query(None) + lnbits_admin_users: str = Query(None) #this should be List[str] ?? + lnbits_allowed_users: str = Query(None) #this should be List[str] ?? + lnbits_admin_ext: str = Query(None) #this should be List[str] ?? + lnbits_disabled_ext: str = Query(None) #this should be List[str] ?? lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -21,4 +21,4 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: str = Query(None) + lnbits_ad_space: str = Query(None) #this should be List[str] ?? diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html index c396ba7d..08b08a62 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_users.html +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -71,6 +71,7 @@ multiple hint="Extensions only user with admin privileges can use" label="Admin extensions" + :options="g.extensions.map(e => e.name)" >
@@ -79,6 +80,7 @@
- + Date: Mon, 10 Oct 2022 12:17:35 +0100 Subject: [PATCH 097/565] get saved data and alert when data changed --- .../admin/templates/admin/index.html | 18 +++++++----------- lnbits/extensions/admin/views_api.py | 5 +++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 4754656d..4e401cb4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,7 @@
- + { + this.settings = response.data.settings + this.formData = _.clone(this.settings) this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c8120564..c2079e37 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -43,8 +43,9 @@ async def api_update_settings( user: User = Depends(check_admin), data: UpdateSettings = Body(...), ): - await update_settings(data) - return {"status": "Success"} + settings = await update_settings(data) + logger.debug(settings) + return {"status": "Success", "settings": settings.dict()} @admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK) From a3b05e26b718db4969884e642416b2eac119f506 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 10 Oct 2022 12:23:19 +0100 Subject: [PATCH 098/565] cleanup and typing fix for data --- lnbits/extensions/admin/models.py | 12 +++++----- .../admin/templates/admin/index.html | 22 ++----------------- lnbits/extensions/admin/views_api.py | 1 - 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 45cd990d..94fa56bb 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,13 +1,15 @@ +from typing import List + from fastapi import Query from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: str = Query(None) #this should be List[str] ?? - lnbits_allowed_users: str = Query(None) #this should be List[str] ?? - lnbits_admin_ext: str = Query(None) #this should be List[str] ?? - lnbits_disabled_ext: str = Query(None) #this should be List[str] ?? + lnbits_admin_users: List[str] = Query(None) + lnbits_allowed_users: List[str] = Query(None) + lnbits_admin_ext: List[str] = Query(None) + lnbits_disabled_ext: List[str] = Query(None) lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -21,4 +23,4 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: str = Query(None) #this should be List[str] ?? + lnbits_ad_space: List[str] = Query(None) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 4e401cb4..d8111595 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -209,26 +209,8 @@ }) }, updateSettings() { - let data = { - lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class, - lnbits_admin_users: this.formData.lnbits_admin_users.toString(), - lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(), - lnbits_admin_ext: this.formData.lnbits_admin_ext, - lnbits_disabled_ext: this.formData.lnbits_disabled_ext, - lnbits_funding_source: this.formData.lnbits_funding_source, - lnbits_force_https: this.formData.lnbits_force_https, - lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min, - lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent, - lnbits_service_fee: this.formData.lnbits_service_fee, - lnbits_hide_api: this.formData.lnbits_hide_api, - lnbits_site_title: this.formData.lnbits_site_title, - lnbits_site_tagline: this.formData.lnbits_site_tagline, - lnbits_site_description: this.formData.lnbits_site_description, - lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name, - lnbits_denomination: this.formData.lnbits_denomination, - lnbits_theme: this.formData.lnbits_theme, - lnbits_custom_logo: this.formData.lnbits_custom_logo, - lnbits_ad_space: this.formData.lnbits_ad_space.toString() + let data = { + ...this.formData } LNbits.api .request( diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c2079e37..19b52e35 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -44,7 +44,6 @@ async def api_update_settings( data: UpdateSettings = Body(...), ): settings = await update_settings(data) - logger.debug(settings) return {"status": "Success", "settings": settings.dict()} From 91a5f7d2143ac4e2eb4f58607a062c1386bea6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 10 Oct 2022 23:27:46 +0200 Subject: [PATCH 099/565] add callback for saas app --- lnbits/app.py | 2 +- lnbits/extensions/admin/migrations.py | 3 +++ lnbits/extensions/admin/models.py | 10 ++++----- lnbits/extensions/admin/views_api.py | 5 ----- lnbits/settings.py | 32 +++++++++++++++++++++++---- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index a8371950..6fa5cf5d 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -64,7 +64,7 @@ def create_app() -> FastAPI: # TODO: why those 2? g().config = settings - # g().base_url = f"http://{settings.host}:{settings.port}" + g().base_url = f"http://{settings.host}:{settings.port}" app.add_middleware(GZipMiddleware, minimum_size=1000) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index c4bc98d8..ea698c27 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,6 +6,9 @@ async def m001_create_admin_settings_table(db): debug TEXT, host TEXT, port INTEGER, + lnbits_saas_instance_id TEXT, + lnbits_saas_callback TEXT, + lnbits_saas_secret TEXT, lnbits_path TEXT, lnbits_commit TEXT, lnbits_admin_users TEXT, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 94fa56bb..ada84e9d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,10 +6,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: List[str] = Query(None) - lnbits_allowed_users: List[str] = Query(None) - lnbits_admin_ext: List[str] = Query(None) - lnbits_disabled_ext: List[str] = Query(None) + lnbits_admin_users: List[str] = Query(None) + lnbits_allowed_users: List[str] = Query(None) + lnbits_admin_ext: List[str] = Query(None) + lnbits_disabled_ext: List[str] = Query(None) lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -23,4 +23,4 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: List[str] = Query(None) + lnbits_ad_space: List[str] = Query(None) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 19b52e35..ae2959bc 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -53,8 +53,3 @@ async def api_delete_settings( ): await delete_settings() return {"status": "Success"} - - -@admin_ext.get("/api/v1/backup/", status_code=HTTPStatus.OK) -async def api_backup(user: User = Depends(check_admin)): - return {"status": "not implemented"} diff --git a/lnbits/settings.py b/lnbits/settings.py index ffcdcc0a..f183211c 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,6 +1,7 @@ import importlib import json import subprocess +import httpx from os import path from sqlite3 import Row from typing import List, Optional @@ -9,6 +10,7 @@ from loguru import logger from pydantic import BaseSettings, Field, validator + def list_parse_fallback(v): try: return json.loads(v) @@ -34,6 +36,11 @@ class Settings(BaseSettings): lnbits_path: str = Field(default=".") lnbits_commit: str = Field(default="unknown") + # saas + lnbits_saas_callback: Optional[str] = Field(default=None) + lnbits_saas_secret: Optional[str] = Field(default=None) + lnbits_saas_instance_id: Optional[str] = Field(default=None) + # users lnbits_admin_users: List[str] = Field(default=[]) lnbits_allowed_users: List[str] = Field(default=[]) @@ -230,11 +237,28 @@ async def check_admin_settings(): http = "https" if settings.lnbits_force_https else "http" user = settings.lnbits_admin_users[0] - logger.warning( - f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}" - ) + + admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + logger.warning(f"✔️ Access admin user account at: {admin_url}") + + if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id: + with httpx.Client() as client: + headers = { + "Content-Type": "application/json; charset=utf-8", + "X-API-KEY": settings.lnbits_saas_secret + } + payload = { + "instance_id": settings.lnbits_saas_instance_id, + "adminuser": user + } + try: + r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload) + logger.warning("sent admin user to saas application") + except: + logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}") + except: - logger.warning("admin.settings tables does not exist.") + logger.error("admin.settings tables does not exist.") raise From c9ead25d50cc1c8cd019e51d7147c4febb71b635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 12 Oct 2022 13:08:59 +0200 Subject: [PATCH 100/565] bugfixes and fix topup wallet --- lnbits/app.py | 1 + .../admin/templates/admin/_tab_funding.html | 4 +-- .../admin/templates/admin/index.html | 28 +++++++++------ lnbits/extensions/admin/views_api.py | 36 ++++++++++--------- lnbits/server.py | 1 + lnbits/settings.py | 29 ++++++++++----- lnbits/wallets/fake.py | 2 +- 7 files changed, 60 insertions(+), 41 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 6fa5cf5d..49ad8d77 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -84,6 +84,7 @@ async def check_funding_source() -> None: def signal_handler(signal, frame): logger.debug(f"SIGINT received, terminating LNbits.") sys.exit(1) + signal.signal(signal.SIGINT, signal_handler) WALLET = get_wallet_class() diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index 8b5456f1..34162a9f 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -8,9 +8,7 @@

Funding Source Info

    {%raw%} -
  • - Funding Source: {{settings.lnbits_backend_wallet_class}} -
  • +
  • Funding Source: {{settings.lnbits_backend_wallet_class}}
  • Balance: {{balance / 1000}} sats
  • {%endraw%}
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d8111595..d3e93a71 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,13 @@
- + u !== user - ) + this.settings.lnbits_admin_users = admin_users.filter(u => u !== user) }, addAllowedUser() { let addUser = this.formData.allowed_users_add @@ -155,7 +159,9 @@ }, removeAllowedUser(user) { let allowed_users = this.settings.lnbits_allowed_users - this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user) + this.settings.lnbits_allowed_users = allowed_users.filter( + u => u !== user + ) }, addAdSpace() { let adSpace = this.formData.ad_space_add @@ -187,10 +193,10 @@ topupWallet() { LNbits.api .request( - 'POST', + 'PUT', '/admin/api/v1/topup/?usr=' + this.g.user.id, - this.wallet.id, - this.wallet.amount + this.g.user.wallets[0].adminkey, + this.wallet ) .then(response => { this.$q.notify({ @@ -209,7 +215,7 @@ }) }, updateSettings() { - let data = { + let data = { ...this.formData } LNbits.api @@ -262,7 +268,7 @@ LNbits.utils.notifyApiError(error) }) } - }, + } }) {% endblock %} diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index ae2959bc..63ed5b3c 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,7 +1,6 @@ from http import HTTPStatus -from fastapi import Body, Depends, Request -from loguru import logger +from fastapi import Body, Depends, Query from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet @@ -9,47 +8,50 @@ from lnbits.core.models import User from lnbits.decorators import check_admin from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings -from lnbits.requestvars import g from lnbits.server import server_restart -from lnbits.settings import settings from .crud import delete_settings, update_settings, update_wallet_balance -@admin_ext.get("/api/v1/restart/", status_code=HTTPStatus.OK) -async def api_restart_server(user: User = Depends(check_admin)): +@admin_ext.get( + "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_restart_server() -> dict[str, str]: server_restart.set() return {"status": "Success"} -@admin_ext.put("/api/v1/topup/", status_code=HTTPStatus.OK) +@admin_ext.put( + "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_update_balance( - wallet_id, topup_amount: int, user: User = Depends(check_admin) -): + id: str = Body(...), amount: int = Body(...) +) -> dict[str, str]: try: - wallet = await get_wallet(wallet_id) + await get_wallet(id) except: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist." ) - await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) + await update_wallet_balance(wallet_id=id, amount=int(amount)) return {"status": "Success"} -@admin_ext.put("/api/v1/settings/", status_code=HTTPStatus.OK) +@admin_ext.put( + "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_update_settings( - user: User = Depends(check_admin), data: UpdateSettings = Body(...), ): settings = await update_settings(data) return {"status": "Success", "settings": settings.dict()} -@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK) -async def api_delete_settings( - user: User = Depends(check_admin), -): +@admin_ext.delete( + "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_delete_settings() -> dict[str, str]: await delete_settings() return {"status": "Success"} diff --git a/lnbits/server.py b/lnbits/server.py index 79af8112..6d4cd2e7 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -52,6 +52,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: port=port, host=host, reload=reload, + forwarded_allow_ips="*", ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, **d diff --git a/lnbits/settings.py b/lnbits/settings.py index f183211c..61dbd6f2 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,20 +1,19 @@ import importlib import json import subprocess -import httpx from os import path from sqlite3 import Row from typing import List, Optional +import httpx from loguru import logger from pydantic import BaseSettings, Field, validator - def list_parse_fallback(v): try: return json.loads(v) - except Exception as e: + except Exception: replaced = v.replace(" ", "") if replaced: return replaced.split(",") @@ -238,24 +237,36 @@ async def check_admin_settings(): http = "https" if settings.lnbits_force_https else "http" user = settings.lnbits_admin_users[0] - admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + admin_url = ( + f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + ) logger.warning(f"✔️ Access admin user account at: {admin_url}") - if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id: + if ( + settings.lnbits_saas_callback + and settings.lnbits_saas_secret + and settings.lnbits_saas_instance_id + ): with httpx.Client() as client: headers = { "Content-Type": "application/json; charset=utf-8", - "X-API-KEY": settings.lnbits_saas_secret + "X-API-KEY": settings.lnbits_saas_secret, } payload = { "instance_id": settings.lnbits_saas_instance_id, - "adminuser": user + "adminuser": user, } try: - r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload) + client.post( + settings.lnbits_saas_callback, + headers=headers, + json=payload, + ) logger.warning("sent admin user to saas application") except: - logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}") + logger.error( + f"error sending admin user to saas: {settings.lnbits_saas_callback}" + ) except: logger.error("admin.settings tables does not exist.") diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 73458e8c..94ff5f48 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -19,7 +19,6 @@ from .base import ( class FakeWallet(Wallet): - queue: asyncio.Queue = asyncio.Queue(0) secret: str = settings.fake_wallet_secret privkey: str = hashlib.pbkdf2_hmac( "sha256", @@ -98,6 +97,7 @@ class FakeWallet(Wallet): return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + self.queue: asyncio.Queue = asyncio.Queue(0) while True: value: Invoice = await self.queue.get() yield value.payment_hash From 9a48b174c62bd1908be3a8698c36b34aa78a3188 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 12 Oct 2022 19:04:46 +0100 Subject: [PATCH 101/565] add funding sources options --- lnbits/app.py | 1 + lnbits/extensions/admin/models.py | 41 +++- .../admin/templates/admin/_tab_funding.html | 25 +- .../admin/templates/admin/index.html | 217 +++++++++++++++++- 4 files changed, 259 insertions(+), 25 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index a8371950..50f218b7 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -84,6 +84,7 @@ async def check_funding_source() -> None: def signal_handler(signal, frame): logger.debug(f"SIGINT received, terminating LNbits.") sys.exit(1) + signal.signal(signal.SIGINT, signal_handler) WALLET = get_wallet_class() diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 94fa56bb..d9d2b22f 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,10 +6,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: List[str] = Query(None) - lnbits_allowed_users: List[str] = Query(None) - lnbits_admin_ext: List[str] = Query(None) - lnbits_disabled_ext: List[str] = Query(None) + lnbits_admin_users: List[str] = Query(None) + lnbits_allowed_users: List[str] = Query(None) + lnbits_admin_ext: List[str] = Query(None) + lnbits_disabled_ext: List[str] = Query(None) lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -23,4 +23,35 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: List[str] = Query(None) + lnbits_ad_space: List[str] = Query(None) + + # funding sources + fake_wallet_secret: str = Query(None) + lnbits_endpoint: str = Query(None) + lnbits_key: str = Query(None) + cliche_endpoint: str = Query(None) + corelightning_rpc: str = Query(None) + eclair_url: str = Query(None) + eclair_pass: str = Query(None) + lnd_rest_endpoint: str = Query(None) + lnd_rest_cert: str = Query(None) + lnd_rest_macaroon: str = Query(None) + lnd_rest_macaroon_encrypted: str = Query(None) + lnd_cert: str = Query(None) + lnd_admin_macaroon: str = Query(None) + lnd_invoice_macaroon: str = Query(None) + lnd_grpc_endpoint: str = Query(None) + lnd_grpc_cert: str = Query(None) + lnd_grpc_port: int = Query(None, ge=0) + lnd_grpc_admin_macaroon: str = Query(None) + lnd_grpc_invoice_macaroon: str = Query(None) + lnd_grpc_macaroon_encrypted: str = Query(None) + lnpay_api_endpoint: str = Query(None) + lnpay_api_key: str = Query(None) + lnpay_wallet_key: str = Query(None) + lntxbot_api_endpoint: str = Query(None) + lntxbot_key: str = Query(None) + opennode_api_endpoint: str = Query(None) + opennode_key: str = Query(None) + spark_url: str = Query(None) + spark_token: str = Query(None) diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index 8b5456f1..a523d4e5 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -8,9 +8,7 @@

Funding Source Info

    {%raw%} -
  • - Funding Source: {{settings.lnbits_backend_wallet_class}} -
  • +
  • Funding Source: {{settings.lnbits_backend_wallet_class}}
  • Balance: {{balance / 1000}} sats
  • {%endraw%}
@@ -60,21 +58,30 @@
-

Funding Sources

+

Funding Sources (Requires server restart)

- + - - + diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d8111595..ccaddda4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,13 @@
- + u !== user - ) + this.settings.lnbits_admin_users = admin_users.filter(u => u !== user) }, addAllowedUser() { let addUser = this.formData.allowed_users_add @@ -155,7 +335,9 @@ }, removeAllowedUser(user) { let allowed_users = this.settings.lnbits_allowed_users - this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user) + this.settings.lnbits_allowed_users = allowed_users.filter( + u => u !== user + ) }, addAdSpace() { let adSpace = this.formData.ad_space_add @@ -208,8 +390,19 @@ LNbits.utils.notifyApiError(error) }) }, + updateFundingData(){ + this.settings.lnbits_allowed_funding_sources.map(f => { + let opts = this.funding_sources.get(f) + if (!opts) return + + Object.keys(opts).forEach(e => { + opts[e].value = this.settings[e] + }) + }) + console.log("funding", this.funding_sources) + }, updateSettings() { - let data = { + let data = { ...this.formData } LNbits.api @@ -222,11 +415,13 @@ .then(response => { this.settings = response.data.settings this.formData = _.clone(this.settings) + this.updateFundingData() this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', icon: null }) + console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -262,7 +457,7 @@ LNbits.utils.notifyApiError(error) }) } - }, + } }) {% endblock %} From dde28e66a24e09517b668e291ed58a6eb7df37e2 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 13 Oct 2022 15:52:02 +0100 Subject: [PATCH 102/565] added tooltips, moved reset, and warnings --- .../admin/templates/admin/index.html | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 575b377f..7d268301 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1,8 +1,14 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %}
-
- +
+ + Save your changes - - - + + + Restart the server for changes to take effect + + + + + Add funds to a wallet. + + > --> + + Delete all settings and reset to defaults. +
@@ -121,6 +136,7 @@ show: false }, tab: 'funding', + needsRestart: false, funding_sources: new Map([ ['VoidWallet', null], [ @@ -302,13 +318,12 @@ this.balance = +'{{ balance|safe }}' this.formData = _.clone(this.settings) //model this.updateFundingData() - console.log(this.settings) }, computed: { checkChanges() { return !_.isEqual(this.settings, this.formData) - }, + } }, methods: { addAdminUser() { @@ -361,6 +376,7 @@ message: 'Success! Restarted Server', icon: null }) + this.needsRestart = false }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -390,16 +406,15 @@ LNbits.utils.notifyApiError(error) }) }, - updateFundingData(){ + updateFundingData() { this.settings.lnbits_allowed_funding_sources.map(f => { let opts = this.funding_sources.get(f) if (!opts) return - + Object.keys(opts).forEach(e => { opts[e].value = this.settings[e] }) }) - console.log("funding", this.funding_sources) }, updateSettings() { let data = { @@ -415,31 +430,41 @@ .then(response => { this.settings = response.data.settings this.formData = _.clone(this.settings) + this.needsRestart = true this.updateFundingData() this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', icon: null }) - console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) }, deleteSettings() { - LNbits.api - .request('DELETE', '/admin/api/v1/settings/?usr=' + this.g.user.id) - .then(response => { - this.$q.notify({ - type: 'positive', - message: - 'Success! Restored settings to defaults, restart required!', - icon: null - }) - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) + LNbits.utils + .confirmDialog( + 'Are you sure you want to restore settings to default?' + ) + .onOk(() => { + LNbits.api + .request( + 'DELETE', + '/admin/api/v1/settings/?usr=' + this.g.user.id + ) + .then(response => { + this.$q.notify({ + type: 'positive', + message: + 'Success! Restored settings to defaults, restart required!', + icon: null + }) + this.needsRestart = true + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) }) }, downloadBackup() { From fb2dee73955aef2700bdf52decb6a570440beb23 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 13 Oct 2022 15:52:26 +0100 Subject: [PATCH 103/565] moved to columns --- .../admin/templates/admin/_tab_funding.html | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index a523d4e5..a69ecb47 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -27,38 +27,38 @@ :options="settings.lnbits_allowed_funding_sources" >
-
-

Fee reserve

-
-
- -
-
- -
+
+
+
+
+

Fee reserve

+
+
+ + +
+
+
-
-
-
-

Funding Sources (Requires server restart)

+

+ Funding Sources (Requires server restart) +

Date: Thu, 13 Oct 2022 15:52:39 +0100 Subject: [PATCH 104/565] themes not displaying fixed --- lnbits/extensions/admin/templates/admin/_tab_theme.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html index 46bf83e9..c327733f 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_theme.html +++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html @@ -63,7 +63,7 @@

Themes

Date: Fri, 21 Oct 2022 10:00:47 +0200 Subject: [PATCH 105/565] add loop to uvicorn --- lnbits/server.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lnbits/server.py b/lnbits/server.py index 6d4cd2e7..eb7c12b1 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,12 +1,7 @@ -import asyncio - import uvloop - uvloop.install() -import contextlib import multiprocessing as mp -import sys import time import click @@ -49,6 +44,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: while True: config = uvicorn.Config( "lnbits.__main__:app", + loop="uvloop", port=port, host=host, reload=reload, @@ -65,9 +61,10 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: server_restart.clear() server.should_exit = True server.force_exit = True + time.sleep(3) process.terminate() process.join() - time.sleep(3) + time.sleep(1) server_restart = mp.Event() From 1b675f295bd2ec1a69ae972cce9b70f0371eefdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 11:13:40 +0200 Subject: [PATCH 106/565] add get settings endpoint with only values you can also save --- lnbits/app.py | 6 +---- lnbits/extensions/admin/crud.py | 12 +++++++++- lnbits/extensions/admin/models.py | 6 ++++- .../admin/templates/admin/index.html | 22 +++++++++++++++---- lnbits/extensions/admin/views_api.py | 7 +++++- lnbits/server.py | 1 + 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 49ad8d77..959a8168 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -62,10 +62,6 @@ def create_app() -> FastAPI: CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ) - # TODO: why those 2? - g().config = settings - g().base_url = f"http://{settings.host}:{settings.port}" - app.add_middleware(GZipMiddleware, minimum_size=1000) register_startup(app) @@ -174,7 +170,7 @@ def register_assets(app: FastAPI): @app.on_event("startup") async def vendored_assets_variable(): - if g().config.debug: + if settings.debug: g().VENDORED_JS = map(url_for_vendored, get_js_vendored()) g().VENDORED_CSS = map(url_for_vendored, get_css_vendored()) else: diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index cc937b5e..2ce91612 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -6,7 +6,7 @@ from lnbits.settings import Settings, read_only_variables from lnbits.tasks import internal_invoice_queue from . import db -from .models import UpdateSettings +from .models import AdminSettings, UpdateSettings async def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -26,6 +26,16 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: await internal_invoice_queue.put(internal_id) +async def get_settings() -> AdminSettings: + row = await db.fetchone("SELECT * FROM admin.settings") + all_settings = Settings(**row) + settings = AdminSettings() + for key, value in row.items(): + if hasattr(settings, key): + setattr(settings, key, getattr(all_settings, key)) + return settings + + async def update_settings(data: UpdateSettings) -> Settings: fields = [] for key, value in data.dict(exclude_none=True).items(): diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index d9d2b22f..31811659 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from fastapi import Query from pydantic import BaseModel @@ -55,3 +55,7 @@ class UpdateSettings(BaseModel): opennode_key: str = Query(None) spark_url: str = Query(None) spark_token: str = Query(None) + + +class AdminSettings(UpdateSettings): + lnbits_allowed_funding_sources: Optional[List[str]] diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 7d268301..10391261 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -314,11 +314,8 @@ } }, created: function () { - this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data + this.getSettings() this.balance = +'{{ balance|safe }}' - this.formData = _.clone(this.settings) //model - this.updateFundingData() - console.log(this.settings) }, computed: { checkChanges() { @@ -416,6 +413,23 @@ }) }) }, + getSettings() { + LNbits.api + .request( + 'GET', + '/admin/api/v1/settings/?usr=' + this.g.user.id, + this.g.user.wallets[0].adminkey + ) + .then(response => { + this.settings = response.data + this.formData = _.clone(this.settings) + this.updateFundingData() + console.log(this.settings) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, updateSettings() { let data = { ...this.formData diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 63ed5b3c..57d62ed4 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -10,7 +10,7 @@ from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings from lnbits.server import server_restart -from .crud import delete_settings, update_settings, update_wallet_balance +from .crud import delete_settings, get_settings, update_settings, update_wallet_balance @admin_ext.get( @@ -21,6 +21,11 @@ async def api_restart_server() -> dict[str, str]: return {"status": "Success"} +@admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)]) +async def api_get_settings() -> UpdateSettings: + return await get_settings() + + @admin_ext.put( "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) diff --git a/lnbits/server.py b/lnbits/server.py index eb7c12b1..ecf7ff62 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,4 +1,5 @@ import uvloop + uvloop.install() import multiprocessing as mp From 708f855c1d485639dfa559c2f2a26a5a9f41c1d7 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:03:32 +0000 Subject: [PATCH 107/565] Added old admin extension --- .env.example | 14 +- lnbits/extensions/admin/README.md | 11 + lnbits/extensions/admin/__init__.py | 10 + lnbits/extensions/admin/config.json | 6 + lnbits/extensions/admin/crud.py | 59 ++ lnbits/extensions/admin/migrations.py | 256 ++++++++ lnbits/extensions/admin/models.py | 38 ++ .../admin/templates/admin/index.html | 565 ++++++++++++++++++ lnbits/extensions/admin/views.py | 20 + lnbits/extensions/admin/views_api.py | 41 ++ 10 files changed, 1014 insertions(+), 6 deletions(-) create mode 100644 lnbits/extensions/admin/README.md create mode 100644 lnbits/extensions/admin/__init__.py create mode 100644 lnbits/extensions/admin/config.json create mode 100644 lnbits/extensions/admin/crud.py create mode 100644 lnbits/extensions/admin/migrations.py create mode 100644 lnbits/extensions/admin/models.py create mode 100644 lnbits/extensions/admin/templates/admin/index.html create mode 100644 lnbits/extensions/admin/views.py create mode 100644 lnbits/extensions/admin/views_api.py diff --git a/.env.example b/.env.example index 987c6ca6..bfaeb515 100644 --- a/.env.example +++ b/.env.example @@ -3,17 +3,19 @@ PORT=5000 DEBUG=false -LNBITS_ALLOWED_USERS="" -LNBITS_ADMIN_USERS="" -# Extensions only admin can access -LNBITS_ADMIN_EXTENSIONS="ngrok" +LNBITS_ADMIN_USERS="" # User IDs seperated by comma +LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access +LNBITS_ADMIN_UI=false # Extensions only admin can access + +LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma + LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" # csv ad image filepaths or urls, extensions can choose to honor LNBITS_AD_SPACE="" # Hides wallet api, extensions can choose to honor -LNBITS_HIDE_API=false +LNBITS_HIDE_API=false # Disable extensions for all users, use "all" to disable all extensions LNBITS_DISABLED_EXTENSIONS="amilk" @@ -67,7 +69,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY LND_REST_ENDPOINT=https://127.0.0.1:8080/ LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING" -# To use an AES-encrypted macaroon, set +# To use an AES-encrypted macaroon, set # LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn" # LNPayWallet diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md new file mode 100644 index 00000000..27729459 --- /dev/null +++ b/lnbits/extensions/admin/README.md @@ -0,0 +1,11 @@ +

Example Extension

+

*tagline*

+This is an example extension to help you organise and build you own. + +Try to include an image + + + +

If your extension has API endpoints, include useful ones here

+ +curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py new file mode 100644 index 00000000..d5f26c90 --- /dev/null +++ b/lnbits/extensions/admin/__init__.py @@ -0,0 +1,10 @@ +from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_admin") + +admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates") + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/admin/config.json b/lnbits/extensions/admin/config.json new file mode 100644 index 00000000..69661733 --- /dev/null +++ b/lnbits/extensions/admin/config.json @@ -0,0 +1,6 @@ +{ + "name": "Admin", + "short_description": "Manage your LNbits install", + "icon": "build", + "contributors": ["benarc"] +} diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py new file mode 100644 index 00000000..cb8f9b5b --- /dev/null +++ b/lnbits/extensions/admin/crud.py @@ -0,0 +1,59 @@ +from typing import List, Optional + +from . import db +from .models import Admin, Funding +from lnbits.settings import * +from lnbits.helpers import urlsafe_short_hash +from lnbits.core.crud import create_payment +from lnbits.db import Connection + + +def update_wallet_balance(wallet_id: str, amount: int) -> str: + temp_id = f"temp_{urlsafe_short_hash()}" + internal_id = f"internal_{urlsafe_short_hash()}" + create_payment( + wallet_id=wallet_id, + checking_id=internal_id, + payment_request="admin_internal", + payment_hash="admin_internal", + amount=amount * 1000, + memo="Admin top up", + pending=False, + ) + return "success" + + +async def update_admin( +) -> Optional[Admin]: + if not CLightningWallet: + print("poo") + await db.execute( + """ + UPDATE admin + SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ? + WHERE 1 + """, + ( + + ), + ) + row = await db.fetchone("SELECT * FROM admin WHERE 1") + return Admin.from_row(row) if row else None + +async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) + ) + row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) + return Jukebox(**row) if row else None + +async def get_admin() -> List[Admin]: + row = await db.fetchone("SELECT * FROM admin WHERE 1") + return Admin.from_row(row) if row else None + + +async def get_funding() -> List[Funding]: + rows = await db.fetchall("SELECT * FROM funding") + + return [Funding.from_row(row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py new file mode 100644 index 00000000..82d934cb --- /dev/null +++ b/lnbits/extensions/admin/migrations.py @@ -0,0 +1,256 @@ +from sqlalchemy.exc import OperationalError # type: ignore +from os import getenv +from lnbits.helpers import urlsafe_short_hash + + +async def m001_create_admin_table(db): + user = None + site_title = None + site_tagline = None + site_description = None + allowed_users = None + admin_user = None + default_wallet_name = None + data_folder = None + disabled_ext = None + force_https = True + service_fee = 0 + funding_source = "" + + if getenv("LNBITS_SITE_TITLE"): + site_title = getenv("LNBITS_SITE_TITLE") + + if getenv("LNBITS_SITE_TAGLINE"): + site_tagline = getenv("LNBITS_SITE_TAGLINE") + + if getenv("LNBITS_SITE_DESCRIPTION"): + site_description = getenv("LNBITS_SITE_DESCRIPTION") + + if getenv("LNBITS_ALLOWED_USERS"): + allowed_users = getenv("LNBITS_ALLOWED_USERS") + + if getenv("LNBITS_ADMIN_USER"): + admin_user = getenv("LNBITS_ADMIN_USER") + + if getenv("LNBITS_DEFAULT_WALLET_NAME"): + default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") + + if getenv("LNBITS_DATA_FOLDER"): + data_folder = getenv("LNBITS_DATA_FOLDER") + + if getenv("LNBITS_DISABLED_EXTENSIONS"): + disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + + if getenv("LNBITS_FORCE_HTTPS"): + force_https = getenv("LNBITS_FORCE_HTTPS") + + if getenv("LNBITS_SERVICE_FEE"): + service_fee = getenv("LNBITS_SERVICE_FEE") + + if getenv("LNBITS_BACKEND_WALLET_CLASS"): + funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS admin ( + user TEXT, + site_title TEXT, + site_tagline TEXT, + site_description TEXT, + admin_user TEXT, + allowed_users TEXT, + default_wallet_name TEXT, + data_folder TEXT, + disabled_ext TEXT, + force_https BOOLEAN, + service_fee INT, + funding_source TEXT + ); + """ + ) + await db.execute( + """ + INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + user, + site_title, + site_tagline, + site_description, + admin_user, + allowed_users, + default_wallet_name, + data_folder, + disabled_ext, + force_https, + service_fee, + funding_source, + ), + ) + + +async def m001_create_funding_table(db): + + funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") + + # Make the funding table, if it does not already exist + await db.execute( + """ + CREATE TABLE IF NOT EXISTS funding ( + id TEXT PRIMARY KEY, + backend_wallet TEXT, + endpoint TEXT, + port INT, + read_key TEXT, + invoice_key TEXT, + admin_key TEXT, + cert TEXT, + balance INT, + selected INT + ); + """ + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, selected) + VALUES (?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "CLightningWallet", + getenv("CLIGHTNING_RPC"), + 1 if funding_wallet == "CLightningWallet" else 0, + ), + ) + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "SparkWallet", + getenv("SPARK_URL"), + getenv("SPARK_TOKEN"), + 1 if funding_wallet == "SparkWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LnbitsWallet", + getenv("LNBITS_ENDPOINT"), + getenv("LNBITS_KEY"), + 1 if funding_wallet == "LnbitsWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LndWallet", + getenv("LND_GRPC_ENDPOINT"), + getenv("LND_GRPC_PORT"), + getenv("LND_GRPC_MACAROON"), + getenv("LND_GRPC_CERT"), + 1 if funding_wallet == "LndWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LndRestWallet", + getenv("LND_REST_ENDPOINT"), + getenv("LND_REST_MACAROON"), + getenv("LND_REST_CERT"), + 1 if funding_wallet == "LndWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + VALUES (?, ?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LNPayWallet", + getenv("LNPAY_API_ENDPOINT"), + getenv("LNPAY_WALLET_KEY"), + getenv("LNPAY_API_KEY"), # this is going in as the cert + 1 if funding_wallet == "LNPayWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "LntxbotWallet", + getenv("LNTXBOT_API_ENDPOINT"), + getenv("LNTXBOT_KEY"), + 1 if funding_wallet == "LntxbotWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "OpenNodeWallet", + getenv("OPENNODE_API_ENDPOINT"), + getenv("OPENNODE_KEY"), + 1 if funding_wallet == "OpenNodeWallet" else 0, + ), + ) + + await db.execute( + """ + INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + VALUES (?, ?, ?, ?, ?) + """, + ( + urlsafe_short_hash(), + "SparkWallet", + getenv("SPARK_URL"), + getenv("SPARK_TOKEN"), + 1 if funding_wallet == "SparkWallet" else 0, + ), + ) + + ## PLACEHOLDER FOR ECLAIR WALLET + # await db.execute( + # """ + # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + # VALUES (?, ?, ?, ?, ?) + # """, + # ( + # urlsafe_short_hash(), + # "EclairWallet", + # getenv("ECLAIR_URL"), + # getenv("ECLAIR_PASS"), + # 1 if funding_wallet == "EclairWallet" else 0, + # ), + # ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py new file mode 100644 index 00000000..c38f17f4 --- /dev/null +++ b/lnbits/extensions/admin/models.py @@ -0,0 +1,38 @@ +from typing import NamedTuple +from sqlite3 import Row + +class Admin(NamedTuple): + user: str + site_title: str + site_tagline: str + site_description:str + allowed_users: str + admin_user: str + default_wallet_name: str + data_folder: str + disabled_ext: str + force_https: str + service_fee: str + funding_source: str + + @classmethod + def from_row(cls, row: Row) -> "Admin": + data = dict(row) + return cls(**data) + +class Funding(NamedTuple): + id: str + backend_wallet: str + endpoint: str + port: str + read_key: str + invoice_key: str + admin_key: str + cert: str + balance: int + selected: int + + @classmethod + def from_row(cls, row: Row) -> "Funding": + data = dict(row) + return cls(**data) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html new file mode 100644 index 00000000..87cf09ef --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -0,0 +1,565 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + +

Admin

+

+ +
+
+ + +
Settings
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+

+
+
+ +
+
+
+
+ +
+ + +
Wallet topup
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+ +{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py new file mode 100644 index 00000000..5e17919c --- /dev/null +++ b/lnbits/extensions/admin/views.py @@ -0,0 +1,20 @@ +from quart import g, render_template, request, jsonify +import json + +from lnbits.decorators import check_user_exists, validate_uuids +from lnbits.extensions.admin import admin_ext +from lnbits.core.crud import get_user, create_account +from .crud import get_admin, get_funding +from lnbits.settings import WALLET + + +@admin_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + user_id = g.user + admin = await get_admin() + + funding = [{**funding._asdict()} for funding in await get_funding()] + + return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py new file mode 100644 index 00000000..2a61b6f5 --- /dev/null +++ b/lnbits/extensions/admin/views_api.py @@ -0,0 +1,41 @@ +from quart import jsonify, g, request +from http import HTTPStatus +from .crud import update_wallet_balance +from lnbits.extensions.admin import admin_ext +from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from lnbits.core.crud import get_wallet +from .crud import get_admin,update_admin +import json + +@admin_ext.route("/api/v1/admin//", methods=["GET"]) +@api_check_wallet_key("admin") +async def api_update_balance(wallet_id, topup_amount): + print(g.data.wallet) + try: + wallet = await get_wallet(wallet_id) + except: + return ( + jsonify({"error": "Not allowed: not an admin"}), + HTTPStatus.FORBIDDEN, + ) + print(wallet) + print(topup_amount) + return jsonify({"status": "Success"}), HTTPStatus.OK + + +@admin_ext.route("/api/v1/admin/", methods=["POST"]) +@api_check_wallet_key("admin") +@api_validate_post_request(schema={}) +async def api_update_admin(): + body = await request.get_json() + admin = await get_admin() + print(g.wallet[2]) + print(body["admin_user"]) + if not admin.admin_user == g.wallet[2] and admin.admin_user != None: + return ( + jsonify({"error": "Not allowed: not an admin"}), + HTTPStatus.FORBIDDEN, + ) + updated = await update_admin(body) + print(updated) + return jsonify({"status": "Success"}), HTTPStatus.OK \ No newline at end of file From a3b1d9528c92008b42116904f5ebbdf8d9360173 Mon Sep 17 00:00:00 2001 From: benarc Date: Mon, 7 Mar 2022 05:11:55 +0000 Subject: [PATCH 108/565] old admin setup UI --- lnbits/core/templates/core/admin.html | 717 ++++++++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 lnbits/core/templates/core/admin.html diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html new file mode 100644 index 00000000..e8176555 --- /dev/null +++ b/lnbits/core/templates/core/admin.html @@ -0,0 +1,717 @@ +{% extends "public.html" %} {% from "macros.jinja" import window_vars with +context %} {% block page %} +
+
+ + +

+
Welcome to LNbits
+

+
+ Fill in the information below to setup your LNbits instance. Details + can be changed later. +
+

+ + +
+ +
Branding
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ +
Service settings
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details + should be filled in for you
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+
+
+ View project in GitHub + Donate +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }} + +{% endblock %} From b325566302f079575e478d9ca9eeff47a8f10a1a Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:18:09 +0000 Subject: [PATCH 109/565] convert to FastAPI --- lnbits/extensions/admin/__init__.py | 11 +++- lnbits/extensions/admin/crud.py | 50 +++++++-------- lnbits/extensions/admin/migrations.py | 23 ++++--- lnbits/extensions/admin/models.py | 51 ++++++++++------ .../admin/templates/admin/index.html | 23 +++++-- lnbits/extensions/admin/views.py | 39 ++++++++---- lnbits/extensions/admin/views_api.py | 61 ++++++++++--------- 7 files changed, 151 insertions(+), 107 deletions(-) diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py index d5f26c90..6a56b2bb 100644 --- a/lnbits/extensions/admin/__init__.py +++ b/lnbits/extensions/admin/__init__.py @@ -1,10 +1,15 @@ -from quart import Blueprint +from fastapi import APIRouter + from lnbits.db import Database +from lnbits.helpers import template_renderer db = Database("ext_admin") -admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates") +admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"]) + +def admin_renderer(): + return template_renderer(["lnbits/extensions/admin/templates"]) -from .views_api import * # noqa from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index cb8f9b5b..872d6c97 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,11 +1,11 @@ from typing import List, Optional +from lnbits.core.crud import create_payment +from lnbits.helpers import urlsafe_short_hash +from lnbits.settings import * + from . import db from .models import Admin, Funding -from lnbits.settings import * -from lnbits.helpers import urlsafe_short_hash -from lnbits.core.crud import create_payment -from lnbits.db import Connection def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -22,38 +22,30 @@ def update_wallet_balance(wallet_id: str, amount: int) -> str: ) return "success" - -async def update_admin( -) -> Optional[Admin]: - if not CLightningWallet: - print("poo") - await db.execute( - """ - UPDATE admin - SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ? - WHERE 1 - """, - ( - - ), - ) - row = await db.fetchone("SELECT * FROM admin WHERE 1") - return Admin.from_row(row) if row else None - -async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]: +async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + print("UPDATE", q) await db.execute( - f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) + f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) - return Jukebox(**row) if row else None + row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,)) + assert row, "Newly updated settings couldn't be retrieved" + return Admin(**row) if row else None + +# async def update_admin(user: str, **kwargs) -> Optional[Admin]: +# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) +# await db.execute( +# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user) +# ) +# new_settings = await get_admin() +# return new_settings async def get_admin() -> List[Admin]: - row = await db.fetchone("SELECT * FROM admin WHERE 1") - return Admin.from_row(row) if row else None + row = await db.fetchone("SELECT * FROM admin") + return Admin(**row) if row else None async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM funding") - return [Funding.from_row(row) for row in rows] + return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 82d934cb..13b76923 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -1,5 +1,7 @@ -from sqlalchemy.exc import OperationalError # type: ignore from os import getenv + +from sqlalchemy.exc import OperationalError # type: ignore + from lnbits.helpers import urlsafe_short_hash @@ -9,7 +11,7 @@ async def m001_create_admin_table(db): site_tagline = None site_description = None allowed_users = None - admin_user = None + admin_users = None default_wallet_name = None data_folder = None disabled_ext = None @@ -29,8 +31,9 @@ async def m001_create_admin_table(db): if getenv("LNBITS_ALLOWED_USERS"): allowed_users = getenv("LNBITS_ALLOWED_USERS") - if getenv("LNBITS_ADMIN_USER"): - admin_user = getenv("LNBITS_ADMIN_USER") + if getenv("LNBITS_ADMIN_USERS"): + admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) + user = admin_users.split(',')[0] if getenv("LNBITS_DEFAULT_WALLET_NAME"): default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") @@ -53,32 +56,32 @@ async def m001_create_admin_table(db): await db.execute( """ CREATE TABLE IF NOT EXISTS admin ( - user TEXT, + "user" TEXT, site_title TEXT, site_tagline TEXT, site_description TEXT, - admin_user TEXT, + admin_users TEXT, allowed_users TEXT, default_wallet_name TEXT, data_folder TEXT, disabled_ext TEXT, force_https BOOLEAN, - service_fee INT, + service_fee REAL, funding_source TEXT ); """ ) await db.execute( """ - INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) + INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( - user, + user.strip(), site_title, site_tagline, site_description, - admin_user, + admin_users[1:], allowed_users, default_wallet_name, data_folder, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index c38f17f4..4080ff01 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,18 +1,35 @@ -from typing import NamedTuple from sqlite3 import Row +from typing import List, Optional -class Admin(NamedTuple): +from fastapi import Query +from pydantic import BaseModel + + +class UpdateAdminSettings(BaseModel): + site_title: Optional[str] + site_tagline: Optional[str] + site_description: Optional[str] + allowed_users: Optional[str] + admin_users: Optional[str] + default_wallet_name: Optional[str] + data_folder: Optional[str] + disabled_ext: Optional[str] + force_https: Optional[bool] + service_fee: Optional[float] + funding_source: Optional[str] + +class Admin(BaseModel): user: str - site_title: str - site_tagline: str - site_description:str - allowed_users: str - admin_user: str + site_title: Optional[str] + site_tagline: Optional[str] + site_description: Optional[str] + allowed_users: Optional[str] + admin_users: str default_wallet_name: str data_folder: str disabled_ext: str - force_https: str - service_fee: str + force_https: Optional[bool] = Query(True) + service_fee: float funding_source: str @classmethod @@ -20,16 +37,16 @@ class Admin(NamedTuple): data = dict(row) return cls(**data) -class Funding(NamedTuple): +class Funding(BaseModel): id: str backend_wallet: str - endpoint: str - port: str - read_key: str - invoice_key: str - admin_key: str - cert: str - balance: int + endpoint: str = Query(None) + port: str = Query(None) + read_key: str = Query(None) + invoice_key: str = Query(None) + admin_key: str = Query(None) + cert: str = Query(None) + balance: int = Query(None) selected: int @classmethod diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 87cf09ef..a6b45625 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -87,7 +87,7 @@ @@ -442,13 +442,14 @@ site_title: '{{admin.site_title}}', tagline: '{{admin.site_tagline}}', description: '{{admin.site_description}}', - admin_user: '{{admin.admin_user}}', - service_fee: parseInt('{{admin.service_fee}}'), + admin_users: '{{admin.admin_users}}', + service_fee: parseFloat('{{admin.service_fee}}'), default_wallet_name: '{{admin.default_wallet_name}}', data_folder: '{{admin.data_folder}}', funding_source_primary: '{{admin.funding_source}}', disabled_ext: '{{admin.disabled_ext}}'.split(','), edited: [], + funding: {}, senddata: {} } }, @@ -528,15 +529,27 @@ }, UpdateLNbits: function () { var self = this - console.log(self.data.admin) + let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin + let data = { + site_title, + site_tagline: this.data.admin.tagline, + site_description: this.data.admin.description, + admin_users: admin_users.toString(), + default_wallet_name, + data_folder, + disabled_ext: disabled_ext.toString(), + service_fee, + funding_source: funding_source_primary} + console.log(data) LNbits.api .request( 'POST', '/admin/api/v1/admin/', self.g.user.wallets[0].adminkey, - self.data.admin + data ) .then(function (response) { + console.log(response.data) self.$q.notify({ type: 'positive', message: diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 5e17919c..00a0c99f 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -1,20 +1,33 @@ -from quart import g, render_template, request, jsonify -import json +from email.policy import default +from os import getenv -from lnbits.decorators import check_user_exists, validate_uuids +from fastapi import Request +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists from lnbits.extensions.admin import admin_ext -from lnbits.core.crud import get_user, create_account +from lnbits.requestvars import g + +from . import admin_ext, admin_renderer from .crud import get_admin, get_funding -from lnbits.settings import WALLET +templates = Jinja2Templates(directory="templates") -@admin_ext.route("/") -@validate_uuids(["usr"], required=True) -@check_user_exists() -async def index(): - user_id = g.user +@admin_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() + print(g()) + funding = [f.dict() for f in await get_funding()] - funding = [{**funding._asdict()} for funding in await get_funding()] - - return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding) + print("ADMIN", admin.dict()) + return admin_renderer().TemplateResponse( + "admin/index.html", { + "request": request, + "user": user.dict(), + "admin": admin.dict(), + "funding": funding + } + ) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 2a61b6f5..b2c65be2 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,41 +1,42 @@ -from quart import jsonify, g, request from http import HTTPStatus -from .crud import update_wallet_balance -from lnbits.extensions.admin import admin_ext -from lnbits.decorators import api_check_wallet_key, api_validate_post_request -from lnbits.core.crud import get_wallet -from .crud import get_admin,update_admin -import json -@admin_ext.route("/api/v1/admin//", methods=["GET"]) -@api_check_wallet_key("admin") -async def api_update_balance(wallet_id, topup_amount): - print(g.data.wallet) +from fastapi import Body, Depends, Request +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_wallet +from lnbits.decorators import WalletTypeInfo, require_admin_key +from lnbits.extensions.admin import admin_ext +from lnbits.extensions.admin.models import Admin, UpdateAdminSettings + +from .crud import get_admin, update_admin, update_wallet_balance + + +@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) +async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)): + print(g.wallet) try: wallet = await get_wallet(wallet_id) except: - return ( - jsonify({"error": "Not allowed: not an admin"}), - HTTPStatus.FORBIDDEN, - ) + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) print(wallet) print(topup_amount) - return jsonify({"status": "Success"}), HTTPStatus.OK + return {"status": "Success"} -@admin_ext.route("/api/v1/admin/", methods=["POST"]) -@api_check_wallet_key("admin") -@api_validate_post_request(schema={}) -async def api_update_admin(): - body = await request.get_json() +@admin_ext.post("/api/v1/admin/", status_code=HTTPStatus.OK) +async def api_update_admin( + request: Request, + data: UpdateAdminSettings = Body(...), + g: WalletTypeInfo = Depends(require_admin_key) + ): admin = await get_admin() - print(g.wallet[2]) - print(body["admin_user"]) - if not admin.admin_user == g.wallet[2] and admin.admin_user != None: - return ( - jsonify({"error": "Not allowed: not an admin"}), - HTTPStatus.FORBIDDEN, - ) - updated = await update_admin(body) + print(data) + if not admin.user == g.wallet.user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) + updated = await update_admin(user=g.wallet.user, **data.dict()) print(updated) - return jsonify({"status": "Success"}), HTTPStatus.OK \ No newline at end of file + return {"status": "Success"} From b4885de9e2fcf598994dd5ca2360cc29629aaa23 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:18:58 +0000 Subject: [PATCH 110/565] remove core admin html (renamed for now) --- lnbits/core/templates/core/core_admin.html | 717 +++++++++++++++++++++ 1 file changed, 717 insertions(+) create mode 100644 lnbits/core/templates/core/core_admin.html diff --git a/lnbits/core/templates/core/core_admin.html b/lnbits/core/templates/core/core_admin.html new file mode 100644 index 00000000..835fc00a --- /dev/null +++ b/lnbits/core/templates/core/core_admin.html @@ -0,0 +1,717 @@ +{% extends "public.html" %} {% from "macros.jinja" import window_vars with +context %} {% block page %} +
+
+ + +

+
Welcome to LNbits
+

+
+ Fill in the information below to setup your LNbits instance. Details + can be changed later. +
+

+ + +
+ +
Branding
+
+
+ +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ +
Service settings
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details + should be filled in for you
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + +
+
+
+
+ View project in GitHub + Donate +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }} + +{% endblock %} From c41b4e714c2e20c36d6a262c77966598b1fd85c4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:21:38 +0000 Subject: [PATCH 111/565] typo --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index bfaeb515..4192f82e 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,7 @@ DEBUG=false LNBITS_ADMIN_USERS="" # User IDs seperated by comma LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Extensions only admin can access +LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma From 922206b365cff523a12bf5a8014b544aa88398f3 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:22:23 +0000 Subject: [PATCH 112/565] add admin_ui env --- lnbits/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/settings.py b/lnbits/settings.py index 3f4e31cc..43cb87cb 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,5 +1,6 @@ import importlib import subprocess +from email.policy import default from os import path from typing import List @@ -27,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None) LNBITS_ALLOWED_USERS: List[str] = [ x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str) ] +LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False) LNBITS_ADMIN_USERS: List[str] = [ x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) ] From b47ed3068105f2526365f6708efe5134a5fe1c0e Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:23:16 +0000 Subject: [PATCH 113/565] add db config at startup --- lnbits/commands.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lnbits/commands.py b/lnbits/commands.py index 0f7454f2..8c39c338 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -52,6 +52,25 @@ def bundle_vendored(): with open(outputpath, "w") as f: f.write(output) +async def get_admin_settings(): + from lnbits.extensions.admin.models import Admin + + async with core_db.connect() as conn: + + if conn.type == SQLITE: + exists = await conn.fetchone( + "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" + ) + elif conn.type in {POSTGRES, COCKROACH}: + exists = await conn.fetchone( + "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" + ) + if not exists: + return False + + row = await conn.fetchone("SELECT * from admin") + + return Admin(**row) if row else None async def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" From 4bf17c5df2d07443c972be9356be710e4578c79b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:23:53 +0000 Subject: [PATCH 114/565] get admin settings at startup --- lnbits/app.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index 075828ef..7ff9e4eb 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -18,6 +18,7 @@ from loguru import logger import lnbits.settings from lnbits.core.tasks import register_task_listeners +from .commands import get_admin_settings from .core import core_app from .core.views.generic import core_html_routes from .helpers import ( @@ -42,6 +43,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: """Create application factory. :param config_object: The configuration object to use. """ +<<<<<<< HEAD configure_logger() app = FastAPI( @@ -53,6 +55,14 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") +======= + app = FastAPI() + + if lnbits.settings.LNBITS_ADMIN_UI: + check_settings(app) + + app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") +>>>>>>> e3a1b3ae (get admin settings at startup) app.mount( "/core/static", StaticFiles(packages=[("lnbits.core", "static")]), @@ -64,7 +74,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: app.add_middleware( CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"] ) - g().config = lnbits.settings g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}" @@ -101,6 +110,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app +def check_settings(app: FastAPI): + @app.on_event("startup") + async def check_settings_admin(): + while True: + admin_set = await get_admin_settings() + if admin_set : + break + print("ERROR:", admin_set) + await asyncio.sleep(5) + # admin_set = await get_admin_settings() + g().admin_conf = admin_set + def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") async def check_wallet_status(): From 48d6a89e5ba9e1e082a2ff189b3307d685f521e4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Sat, 12 Mar 2022 14:24:11 +0000 Subject: [PATCH 115/565] remove core admin.html --- lnbits/core/templates/core/admin.html | 717 -------------------------- 1 file changed, 717 deletions(-) delete mode 100644 lnbits/core/templates/core/admin.html diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html deleted file mode 100644 index e8176555..00000000 --- a/lnbits/core/templates/core/admin.html +++ /dev/null @@ -1,717 +0,0 @@ -{% extends "public.html" %} {% from "macros.jinja" import window_vars with -context %} {% block page %} -
-
- - -

-
Welcome to LNbits
-

-
- Fill in the information below to setup your LNbits instance. Details - can be changed later. -
-

- - -
- -
Branding
-
-
- -
-
- -
-
-
-
- - - -
-
- - - -
-
- -
Service settings
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
- Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details - should be filled in for you
-
- - - - - - - - - - - - - -
-
- -
-
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
-
-
-
- -
-
- -
-
-
-
-
- - - - -
-
- -
-
- -
-
-
-
-
-
- -
- - -
-
-
-
- View project in GitHub - Donate -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(funding) }} - -{% endblock %} From 87bee88de403723b9d98f64716c573e21289a50a Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Mar 2022 16:55:31 +0000 Subject: [PATCH 116/565] refactor ui --- .../admin/templates/admin/index.html | 727 +++++++++++++++++- 1 file changed, 712 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index a6b45625..65ac9f33 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1,6 +1,670 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %} +
+
+ +
+
+ + + + + + +
+
+ + + + +
Wallets Management
+
+
+
+
+

Funding Source Info

+
    + {%raw%} +
  • Funding Source: {{data.admin.funding_source}}
  • +
  • Balance: {{data.admin.balance / 1000}} sats
  • + {%endraw%} +
+
+
+
+
+
+

Active Funding

+ +
+
+
+ +

TopUp a wallet

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+

Funding Sources

+ + + + + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+
+ +
+ Save +
+
+
+ + +
User Management
+
+

+ Super Admin: {% raw + %}{{this.data.admin.user}}{% endraw %} +

+
+
+

Admin Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Allowed Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+
+
+ + +
Server Management
+
+
+
+
+

Server Info

+
    + {%raw%} +
  • SQlite: {{data.admin.data_folder}}
  • +
  • Postgres: {{data.admin.database_url}}
  • + {%endraw%} +
+
+
+
+
+
+

Service Fee

+ +
+
+
+

Miscelaneous

+ + + Force HTTPS + Prefer secure URLs + + + + + + + + Hide API + Hides wallet api, extensions can choose to honor + + + + + +
+
+
+
+ +
+ Save +
+
+
+ + +
UI Management
+
+
+
+
+

Site Title

+ +
+
+
+

Site Tagline

+ +
+
+
+
+

Site Description

+ +
+
+
+
+

Default Wallet Name

+ +
+
+
+

Denomination

+ +
+
+
+
+
+

Themes

+ +
+
+
+

Advertisement Slots

+ + + +
+ {% raw %} + + {{ space.slice(0, 8) + " ... " + space.slice(-8) }} + + {% endraw %} +
+
+
+
+
+ +
+ Save +
+
+
+
+
+
+
+
+

Admin

-
+
@@ -426,6 +1090,7 @@ return { wallet: {data: {}}, cancel: {}, + tab: 'funding', data: { funding_source: [ 'CLightningWallet', @@ -436,24 +1101,14 @@ 'LnbitsWallet', 'OpenNodeWallet' ], - + admin: { - user: '{{ user.id }}', - site_title: '{{admin.site_title}}', - tagline: '{{admin.site_tagline}}', - description: '{{admin.site_description}}', - admin_users: '{{admin.admin_users}}', - service_fee: parseFloat('{{admin.service_fee}}'), - default_wallet_name: '{{admin.default_wallet_name}}', - data_folder: '{{admin.data_folder}}', - funding_source_primary: '{{admin.funding_source}}', - disabled_ext: '{{admin.disabled_ext}}'.split(','), edited: [], funding: {}, senddata: {} } }, - + themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'], options: [ 'bleskomat', 'captcha', @@ -489,9 +1144,51 @@ for (i = 0; i < funding.length; i++) { self.data.admin.funding[funding[i].backend_wallet] = funding[i] } - console.log(self.data.admin) + let settings = JSON.parse('{{ settings | tojson|safe }}') + settings.balance = '{{ balance }}' + this.data.admin = {...this.data.admin, ...settings} + console.log(this.g.user) }, methods: { + addAdminUser(){ + let addUser = this.data.admin_users_add + let admin_users = this.data.admin.admin_users + if(addUser.length && !admin_users.includes(addUser)){ + admin_users.push(addUser) + this.data.admin.admin_users = admin_users + this.data.admin_users_add = "" + } + }, + removeAdminUser(user){ + let admin_users = this.data.admin.admin_users + this.data.admin.admin_users = admin_users.filter(u => u !== user) + }, + addAllowedUser(){ + let addUser = this.data.allowed_users_add + let allowed_users = this.data.admin.allowed_users + if(addUser.length && !allowed_users.includes(addUser)){ + allowed_users.push(addUser) + this.data.admin.allowed_users = allowed_users + this.data.allowed_users_add = "" + } + }, + removeAllowedUser(user){ + let allowed_users = this.data.admin.allowed_users + this.data.admin.allowed_users = allowed_users.filter(u => u !== user) + }, + addAdSpace(){ + let adSpace = this.data.ad_space_add + let spaces = this.data.admin.ad_space + if(adSpace.length && !spaces.includes(adSpace)){ + spaces.push(adSpace) + this.data.admin.ad_space = spaces + this.data.ad_space_add = "" + } + }, + removeAdSpace(ad){ + let spaces = this.data.admin.ad_space + this.data.admin.ad_space = spaces.filter(s => s !== ad) + }, topupWallet: function () { var self = this LNbits.api From 8c1c7d13b87ba7b7408dccd719a9a9c66e6e4325 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Mar 2022 16:59:06 +0000 Subject: [PATCH 117/565] make it work from g() --- lnbits/app.py | 34 +++--- lnbits/config.py | 62 ++++++++++ lnbits/extensions/admin/crud.py | 2 +- lnbits/extensions/admin/migrations.py | 162 +++++++++++++++++--------- lnbits/extensions/admin/models.py | 27 +++-- lnbits/extensions/admin/views.py | 8 +- lnbits/settings.py | 2 +- 7 files changed, 218 insertions(+), 79 deletions(-) create mode 100644 lnbits/config.py diff --git a/lnbits/app.py b/lnbits/app.py index 7ff9e4eb..1ffedb54 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -19,6 +19,7 @@ import lnbits.settings from lnbits.core.tasks import register_task_listeners from .commands import get_admin_settings +from .config import WALLET, conf from .core import core_app from .core.views.generic import core_html_routes from .helpers import ( @@ -29,7 +30,8 @@ from .helpers import ( url_for_vendored, ) from .requestvars import g -from .settings import WALLET + +# from .settings import WALLET from .tasks import ( catch_everything_and_restart, check_pending_payments, @@ -43,7 +45,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: """Create application factory. :param config_object: The configuration object to use. """ -<<<<<<< HEAD configure_logger() app = FastAPI( @@ -55,20 +56,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") -======= - app = FastAPI() - - if lnbits.settings.LNBITS_ADMIN_UI: - check_settings(app) - - app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") ->>>>>>> e3a1b3ae (get admin settings at startup) app.mount( "/core/static", StaticFiles(packages=[("lnbits.core", "static")]), name="core_static", ) + if lnbits.settings.LNBITS_ADMIN_UI: + g().admin_conf = conf + check_settings(app) + + g().WALLET = WALLET + origins = ["*"] app.add_middleware( @@ -109,18 +108,27 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app - def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): + + def removeEmptyString(arr): + return list(filter(None, arr)) + while True: admin_set = await get_admin_settings() if admin_set : break print("ERROR:", admin_set) await asyncio.sleep(5) - # admin_set = await get_admin_settings() - g().admin_conf = admin_set + + admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) + admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(',')) + admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(',')) + admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(',')) + admin_set.theme = removeEmptyString(admin_set.theme.split(',')) + admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) + g().admin_conf = conf.copy(update=admin_set.dict()) def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") diff --git a/lnbits/config.py b/lnbits/config.py new file mode 100644 index 00000000..02e8cf53 --- /dev/null +++ b/lnbits/config.py @@ -0,0 +1,62 @@ +import importlib +import json +from os import getenv, path +from typing import List, Optional + +from pydantic import BaseSettings, Field, validator + +wallets_module = importlib.import_module("lnbits.wallets") +wallet_class = getattr( + wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet") +) + +WALLET = wallet_class() + +def list_parse_fallback(v): + try: + return json.loads(v) + except Exception as e: + return v.replace(' ','').split(',') + +class Settings(BaseSettings): + # users + admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") + allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") + admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS") + disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS") + funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS") + # ops + data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER") + database_url: str = Field(default=None, env="LNBITS_DATABASE_URL") + force_https: bool = Field(default=True, env="LNBITS_FORCE_HTTPS") + service_fee: float = Field(default=0, env="LNBITS_SERVICE_FEE") + hide_api: bool = Field(default=False, env="LNBITS_HIDE_API") + denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") + # Change theme + site_title: str = Field(default=None, env="LNBITS_SITE_TITLE") + site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE") + site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") + default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME") + theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") + ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") + # .env + env: Optional[str] + debug: Optional[str] + host: Optional[str] + port: Optional[str] + lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) + + # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True) + # def validate(cls, val): + # print(val) + # return val.split(',') + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + case_sensitive = False + json_loads = list_parse_fallback + + +conf = Settings() +WALLET = wallet_class() diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 872d6c97..6fccb8ee 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -40,7 +40,7 @@ async def update_admin(user: str, **kwargs) -> Admin: # new_settings = await get_admin() # return new_settings -async def get_admin() -> List[Admin]: +async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 13b76923..574f772d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -2,93 +2,151 @@ from os import getenv from sqlalchemy.exc import OperationalError # type: ignore +from lnbits.config import conf from lnbits.helpers import urlsafe_short_hash async def m001_create_admin_table(db): - user = None - site_title = None - site_tagline = None - site_description = None - allowed_users = None - admin_users = None - default_wallet_name = None - data_folder = None - disabled_ext = None - force_https = True - service_fee = 0 - funding_source = "" + # users/server + user = conf.admin_users[0] + admin_users = ",".join(conf.admin_users) + allowed_users = ",".join(conf.allowed_users) + admin_ext = ",".join(conf.admin_ext) + disabled_ext = ",".join(conf.disabled_ext) + funding_source = conf.funding_source + #operational + data_folder = conf.data_folder + database_url = conf.database_url + force_https = conf.force_https + service_fee = conf.service_fee + hide_api = conf.hide_api + denomination = conf.denomination + # Theme'ing + site_title = conf.site_title + site_tagline = conf.site_tagline + site_description = conf.site_description + default_wallet_name = conf.default_wallet_name + theme = ",".join(conf.theme) + ad_space = ",".join(conf.ad_space) - if getenv("LNBITS_SITE_TITLE"): - site_title = getenv("LNBITS_SITE_TITLE") + # if getenv("LNBITS_ADMIN_EXTENSIONS"): + # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS") - if getenv("LNBITS_SITE_TAGLINE"): - site_tagline = getenv("LNBITS_SITE_TAGLINE") + # if getenv("LNBITS_DATABASE_URL"): + # database_url = getenv("LNBITS_DATABASE_URL") - if getenv("LNBITS_SITE_DESCRIPTION"): - site_description = getenv("LNBITS_SITE_DESCRIPTION") + # if getenv("LNBITS_HIDE_API"): + # hide_api = getenv("LNBITS_HIDE_API") - if getenv("LNBITS_ALLOWED_USERS"): - allowed_users = getenv("LNBITS_ALLOWED_USERS") + # if getenv("LNBITS_THEME_OPTIONS"): + # theme = getenv("LNBITS_THEME_OPTIONS") - if getenv("LNBITS_ADMIN_USERS"): - admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) - user = admin_users.split(',')[0] + # if getenv("LNBITS_AD_SPACE"): + # ad_space = getenv("LNBITS_AD_SPACE") - if getenv("LNBITS_DEFAULT_WALLET_NAME"): - default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") + # if getenv("LNBITS_SITE_TITLE"): + # site_title = getenv("LNBITS_SITE_TITLE") - if getenv("LNBITS_DATA_FOLDER"): - data_folder = getenv("LNBITS_DATA_FOLDER") + # if getenv("LNBITS_SITE_TAGLINE"): + # site_tagline = getenv("LNBITS_SITE_TAGLINE") - if getenv("LNBITS_DISABLED_EXTENSIONS"): - disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + # if getenv("LNBITS_SITE_DESCRIPTION"): + # site_description = getenv("LNBITS_SITE_DESCRIPTION") - if getenv("LNBITS_FORCE_HTTPS"): - force_https = getenv("LNBITS_FORCE_HTTPS") + # if getenv("LNBITS_ALLOWED_USERS"): + # allowed_users = getenv("LNBITS_ALLOWED_USERS") - if getenv("LNBITS_SERVICE_FEE"): - service_fee = getenv("LNBITS_SERVICE_FEE") + # if getenv("LNBITS_ADMIN_USERS"): + # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) + # user = admin_users.split(',')[0] + + # if getenv("LNBITS_DEFAULT_WALLET_NAME"): + # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") - if getenv("LNBITS_BACKEND_WALLET_CLASS"): - funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") + # if getenv("LNBITS_DATA_FOLDER"): + # data_folder = getenv("LNBITS_DATA_FOLDER") + + # if getenv("LNBITS_DISABLED_EXTENSIONS"): + # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") + + # if getenv("LNBITS_FORCE_HTTPS"): + # force_https = getenv("LNBITS_FORCE_HTTPS") + + # if getenv("LNBITS_SERVICE_FEE"): + # service_fee = getenv("LNBITS_SERVICE_FEE") + + # if getenv("LNBITS_DENOMINATION"): + # denomination = getenv("LNBITS_DENOMINATION", "sats") + + # if getenv("LNBITS_BACKEND_WALLET_CLASS"): + # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") await db.execute( """ CREATE TABLE IF NOT EXISTS admin ( - "user" TEXT, + "user" TEXT PRIMARY KEY, + admin_users TEXT, + allowed_users TEXT, + admin_ext TEXT, + disabled_ext TEXT, + funding_source TEXT, + data_folder TEXT, + database_url TEXT, + force_https BOOLEAN, + service_fee REAL, + hide_api BOOLEAN, + denomination TEXT, site_title TEXT, site_tagline TEXT, site_description TEXT, - admin_users TEXT, - allowed_users TEXT, default_wallet_name TEXT, - data_folder TEXT, - disabled_ext TEXT, - force_https BOOLEAN, - service_fee REAL, - funding_source TEXT + theme TEXT, + ad_space TEXT ); """ ) await db.execute( """ - INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - user.strip(), + INSERT INTO admin ( + "user", + admin_users, + allowed_users, + admin_ext, + disabled_ext, + funding_source, + data_folder, + database_url, + force_https, + service_fee, + hide_api, + denomination, site_title, site_tagline, site_description, - admin_users[1:], - allowed_users, default_wallet_name, - data_folder, + theme, + ad_space) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + user, + admin_users, + allowed_users, + admin_ext, disabled_ext, + funding_source, + data_folder, + database_url, force_https, service_fee, - funding_source, + hide_api, + denomination, + site_title, + site_tagline, + site_description, + default_wallet_name, + theme, + ad_space, ), ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 4080ff01..f7c64de5 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -2,7 +2,7 @@ from sqlite3 import Row from typing import List, Optional from fastapi import Query -from pydantic import BaseModel +from pydantic import BaseModel, Field class UpdateAdminSettings(BaseModel): @@ -19,18 +19,27 @@ class UpdateAdminSettings(BaseModel): funding_source: Optional[str] class Admin(BaseModel): + # users user: str + admin_users: Optional[str] + allowed_users: Optional[str] + admin_ext: Optional[str] + disabled_ext: Optional[str] + funding_source: Optional[str] + # ops + data_folder: Optional[str] + database_url: Optional[str] + force_https: bool = Field(default=True) + service_fee: float = Field(default=0) + hide_api: bool = Field(default=False) + # Change theme site_title: Optional[str] site_tagline: Optional[str] site_description: Optional[str] - allowed_users: Optional[str] - admin_users: str - default_wallet_name: str - data_folder: str - disabled_ext: str - force_https: Optional[bool] = Query(True) - service_fee: float - funding_source: str + default_wallet_name: Optional[str] + denomination: str = Field(default="sats") + theme: Optional[str] + ad_space: Optional[str] @classmethod def from_row(cls, row: Row) -> "Admin": diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 00a0c99f..105f05a1 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -19,15 +19,17 @@ templates = Jinja2Templates(directory="templates") @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() - print(g()) funding = [f.dict() for f in await get_funding()] - + error, balance = await g().WALLET.status() print("ADMIN", admin.dict()) + print(g().admin_conf) return admin_renderer().TemplateResponse( "admin/index.html", { "request": request, "user": user.dict(), "admin": admin.dict(), - "funding": funding + "funding": funding, + "settings": g().admin_conf.dict(), + "balance": balance } ) diff --git a/lnbits/settings.py b/lnbits/settings.py index 43cb87cb..ed5c77f7 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -4,7 +4,7 @@ from email.policy import default from os import path from typing import List -from environs import Env # type: ignore +from environs import Env env = Env() env.read_env() From a72ed98997656a709b00d171c27c387c26dfe0bd Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:28:07 +0000 Subject: [PATCH 118/565] topup wallet endpoint --- lnbits/extensions/admin/crud.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 6fccb8ee..683558f9 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,26 +1,31 @@ +import json from typing import List, Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash from lnbits.settings import * +from lnbits.tasks import internal_invoice_queue from . import db from .models import Admin, Funding -def update_wallet_balance(wallet_id: str, amount: int) -> str: +async def update_wallet_balance(wallet_id: str, amount: int) -> str: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" - create_payment( + + payment = await create_payment( wallet_id=wallet_id, checking_id=internal_id, payment_request="admin_internal", payment_hash="admin_internal", - amount=amount * 1000, + amount=amount*1000, memo="Admin top up", pending=False, ) - return "success" + # manually send this for now + await internal_invoice_queue.put(internal_id) + return payment async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) From 42a9af986ab3620bdb61270961c6adbda4a66c95 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:28:44 +0000 Subject: [PATCH 119/565] update admin settings in db --- lnbits/extensions/admin/models.py | 29 +++++++++++++++++----------- lnbits/extensions/admin/views_api.py | 8 ++++---- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index f7c64de5..36d9b815 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,17 +6,24 @@ from pydantic import BaseModel, Field class UpdateAdminSettings(BaseModel): - site_title: Optional[str] - site_tagline: Optional[str] - site_description: Optional[str] - allowed_users: Optional[str] - admin_users: Optional[str] - default_wallet_name: Optional[str] - data_folder: Optional[str] - disabled_ext: Optional[str] - force_https: Optional[bool] - service_fee: Optional[float] - funding_source: Optional[str] + # users + admin_users: str = Query(None) + allowed_users: str = Query(None) + admin_ext: str = Query(None) + disabled_ext: str = Query(None) + funding_source: str = Query(None) + # ops + force_https: bool = Query(None) + service_fee: float = Query(None, ge=0) + hide_api: bool = Query(None) + # Change theme + site_title: str = Query(None) + site_tagline: str = Query(None) + site_description: str = Query(None) + default_wallet_name: str = Query(None) + denomination: str = Query(None) + theme: str = Query(None) + ad_space: str = Query(None) class Admin(BaseModel): # users diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index b2c65be2..cb526aa5 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -12,16 +12,16 @@ from .crud import get_admin, update_admin, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) -async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)): - print(g.wallet) +async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)): try: wallet = await get_wallet(wallet_id) except: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - print(wallet) - print(topup_amount) + + await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) + return {"status": "Success"} From 2ebb4448d5ff8daa3b1c4729ce33dd4c5c744c7b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 10:29:18 +0000 Subject: [PATCH 120/565] update settings and topup logic --- .../admin/templates/admin/index.html | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 65ac9f33..e9ddc7c4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -30,7 +30,7 @@
- + @@ -61,7 +61,7 @@
- +

TopUp a wallet

@@ -87,13 +87,13 @@
- +
@@ -577,7 +577,6 @@

Site Description

s !== ad) }, - topupWallet: function () { - var self = this + topupWallet() { LNbits.api .request( 'GET', '/admin/api/v1/admin/' + - self.wallet.id + + this.wallet.data.id + '/' + - self.wallet.data.amount, - self.g.user.wallets[0].adminkey + this.wallet.data.amount, + this.g.user.wallets[0].adminkey ) - .then(function (response) { - self.$q.notify({ + .then((response) => { + this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - self.wallet.amount + - ' to ' + - self.wallet.id, + 'Success! Added ' + + this.wallet.data.amount + + ' to ' + + this.wallet.data.id, icon: null }) + this.wallet.data = {} }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -1224,36 +1224,59 @@ self.data.admin.edited.push(source) console.log(self.data.admin.edited) }, - UpdateLNbits: function () { - var self = this - let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin + UpdateLNbits() { + let { + admin_users, + allowed_users, + admin_ext, + disabled_ext, + funding_source, + force_https, + service_fee, + hide_api, + site_title, + site_tagline, + site_description, + default_wallet_name, + denomination, + theme, + ad_space + } = this.data.admin + //console.log("this", this.data.admin) let data = { - site_title, - site_tagline: this.data.admin.tagline, - site_description: this.data.admin.description, - admin_users: admin_users.toString(), - default_wallet_name, - data_folder, + admin_users: admin_users.toString(), + allowed_users: allowed_users.toString(), + admin_ext: admin_ext.toString(), disabled_ext: disabled_ext.toString(), - service_fee, - funding_source: funding_source_primary} + funding_source, + force_https, + service_fee, + hide_api, + site_title, + site_tagline, + site_description, + default_wallet_name, + denomination, + theme: theme.toString(), + ad_space: ad_space.toString() + } console.log(data) LNbits.api .request( 'POST', '/admin/api/v1/admin/', - self.g.user.wallets[0].adminkey, + this.g.user.wallets[0].adminkey, data ) - .then(function (response) { + .then(response => { console.log(response.data) - self.$q.notify({ + this.$q.notify({ type: 'positive', message: 'Success! Added ' + - self.wallet.amount + + this.wallet.amount + ' to ' + - self.wallet.id, + this.wallet.id, icon: null }) }) From 3082a393436b88a24d860f87983560e758a02f66 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:34:47 +0000 Subject: [PATCH 121/565] make removeEmptyString fn as helper fn --- lnbits/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 1ffedb54..c8f5c60a 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -26,6 +26,7 @@ from .helpers import ( get_css_vendored, get_js_vendored, get_valid_extensions, + removeEmptyString, template_renderer, url_for_vendored, ) @@ -112,9 +113,6 @@ def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): - def removeEmptyString(arr): - return list(filter(None, arr)) - while True: admin_set = await get_admin_settings() if admin_set : From c419bd27ebcd212da37f27994e630df08bdba7d4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:35:15 +0000 Subject: [PATCH 122/565] add some defaults --- lnbits/config.py | 6 +++--- lnbits/extensions/admin/models.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 02e8cf53..b2fbfff1 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -33,10 +33,10 @@ class Settings(BaseSettings): hide_api: bool = Field(default=False, env="LNBITS_HIDE_API") denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") # Change theme - site_title: str = Field(default=None, env="LNBITS_SITE_TITLE") - site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE") + site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE") + site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") - default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME") + default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 36d9b815..0f25679d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -17,11 +17,11 @@ class UpdateAdminSettings(BaseModel): service_fee: float = Query(None, ge=0) hide_api: bool = Query(None) # Change theme - site_title: str = Query(None) - site_tagline: str = Query(None) + site_title: str = Query("LNbits") + site_tagline: str = Query("free and open-source lightning wallet") site_description: str = Query(None) - default_wallet_name: str = Query(None) - denomination: str = Query(None) + default_wallet_name: str = Query("LNbits wallet") + denomination: str = Query("sats") theme: str = Query(None) ad_space: str = Query(None) From 0897d0476356d1956711fc0f1c6448fbb88275fb Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:36:04 +0000 Subject: [PATCH 123/565] removeEmtpy sting as helper fn --- lnbits/helpers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e213240c..e456f715 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -154,8 +154,20 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s url = f"{base}{endpoint}{url_params}" return url +def removeEmptyString(arr): + return list(filter(None, arr)) def template_renderer(additional_folders: List = []) -> Jinja2Templates: + if(settings.LNBITS_ADMIN_UI): + _ = g().admin_conf + settings.LNBITS_AD_SPACE = _.ad_space + settings.LNBITS_HIDE_API = _.hide_api + settings.LNBITS_SITE_TITLE = _.site_title + settings.LNBITS_DENOMINATION = _.denomination + settings.LNBITS_SITE_TAGLINE = _.site_tagline + settings.LNBITS_SITE_DESCRIPTION = _.site_description + settings.LNBITS_THEME_OPTIONS = _.theme + t = Jinja2Templates( loader=jinja2.FileSystemLoader( ["lnbits/templates", "lnbits/core/templates", *additional_folders] From 8c93aa304f0d484e908cd40c0a851b95fbbfd992 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:41:11 +0000 Subject: [PATCH 124/565] cleanup --- lnbits/extensions/admin/crud.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 683558f9..e14ad194 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,9 +1,7 @@ -import json -from typing import List, Optional +from typing import List from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import * from lnbits.tasks import internal_invoice_queue from . import db @@ -37,14 +35,6 @@ async def update_admin(user: str, **kwargs) -> Admin: assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None -# async def update_admin(user: str, **kwargs) -> Optional[Admin]: -# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) -# await db.execute( -# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user) -# ) -# new_settings = await get_admin() -# return new_settings - async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None From 5ce73697d47aa6eb96bb6d69c7da1e4521bc4943 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:42:28 +0000 Subject: [PATCH 125/565] make string to list --- lnbits/extensions/admin/views_api.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index cb526aa5..1d4e6a9c 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,5 +1,6 @@ from http import HTTPStatus +# from config import conf from fastapi import Body, Depends, Request from starlette.exceptions import HTTPException @@ -7,6 +8,8 @@ from lnbits.core.crud import get_wallet from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import Admin, UpdateAdminSettings +from lnbits.helpers import removeEmptyString +from lnbits.requestvars import g from .crud import get_admin, update_admin, update_wallet_balance @@ -19,7 +22,7 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - + await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) return {"status": "Success"} @@ -29,14 +32,24 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D async def api_update_admin( request: Request, data: UpdateAdminSettings = Body(...), - g: WalletTypeInfo = Depends(require_admin_key) + w: WalletTypeInfo = Depends(require_admin_key) ): admin = await get_admin() print(data) - if not admin.user == g.wallet.user: + if not admin.user == w.wallet.user: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" ) - updated = await update_admin(user=g.wallet.user, **data.dict()) - print(updated) + updated = await update_admin(user=w.wallet.user, **data.dict()) + + updated.admin_users = removeEmptyString(updated.admin_users.split(',')) + updated.allowed_users = removeEmptyString(updated.allowed_users.split(',')) + updated.admin_ext = removeEmptyString(updated.admin_ext.split(',')) + updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(',')) + updated.theme = removeEmptyString(updated.theme.split(',')) + updated.ad_space = removeEmptyString(updated.ad_space.split(',')) + + g().admin_conf = g().admin_conf.copy(update=updated.dict()) + + print(g().admin_conf) return {"status": "Success"} From c299927e7ca1e16c8bc29a98b5af38ed402496de Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Mar 2022 11:42:47 +0000 Subject: [PATCH 126/565] success message --- lnbits/extensions/admin/templates/admin/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index e9ddc7c4..9aa4f12a 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1273,10 +1273,7 @@ this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - this.wallet.amount + - ' to ' + - this.wallet.id, + 'Success! Settings changed!', icon: null }) }) From 2a63fb191423b2f23de7f70e8197109a290f3689 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 14 Apr 2022 10:42:26 +0100 Subject: [PATCH 127/565] allow html to be passed to description --- lnbits/core/templates/core/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index 68a7b7ed..146fc6ad 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -82,7 +82,7 @@ >
-

{{SITE_DESCRIPTION}}

+

{{SITE_DESCRIPTION | safe}}

From 3fbdac127adf9459d8af4453a802248f7c3d94fc Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 14 Apr 2022 16:37:13 +0100 Subject: [PATCH 128/565] update funding wallets --- lnbits/extensions/admin/crud.py | 12 + .../admin/templates/admin/index.html | 523 ++++++++++-------- lnbits/extensions/admin/views.py | 3 +- lnbits/extensions/admin/views_api.py | 19 +- 4 files changed, 315 insertions(+), 242 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index e14ad194..dd39e8e4 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -39,6 +39,18 @@ async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin") return Admin(**row) if row else None +async def update_funding(data: Funding) -> Funding: + await db.execute( + """ + UPDATE funding + SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? + WHERE id = ? + """, + (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), + ) + row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,)) + assert row, "Newly updated settings couldn't be retrieved" + return Funding(**row) if row else None async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM funding") diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 9aa4f12a..d56b3d79 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -31,11 +31,11 @@
- - - -
Wallets Management
-
+ + + +
Wallets Management
+
@@ -62,43 +62,96 @@
-

TopUp a wallet

-
-
- -
-
-
- -
+

TopUp a wallet

+
+
+ +
-
- +
+
+
+
+ +

Funding Sources

- + {% raw %} + + + + + + + + + + + + + + {% endraw %} + +
+ + + + +
User Management
+
+

+ Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %} +

+

Admin Users

+ hint="Users with admin privileges" + >
{% raw %} -
-
-
-

Allowed Users

- - - +
- {% raw %} - Allowed Users

+ - {{ user }} -
- {% endraw %} + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+ + + + +
Server Management

-
-
-
-

Admin Extensions

- -
-
-
-

Disabled Extensions

- -
-
-
-
- Save -
-
-
- - -
Server Management
-

Server Info

    {%raw%} -
  • SQlite: {{data.admin.data_folder}}
  • -
  • Postgres: {{data.admin.database_url}}
  • +
  • + SQlite: {{data.admin.data_folder}} +
  • +
  • + Postgres: {{data.admin.database_url}} +
  • {%endraw%}

@@ -520,7 +576,10 @@ Hide API - Hides wallet api, extensions can choose to honor + Hides wallet api, extensions can choose to + honor
-
- -
- Save -
-
-
- - -
UI Management
-
+
+ +
+ Save +
+ + + + +
UI Management
+
@@ -575,15 +629,15 @@
-

Site Description

- -
-
+

Site Description

+ +
+

Default Wallet Name

@@ -628,12 +682,15 @@ @keydown.enter="addAdSpace" type="text" label="Ad image URL" - hint="Ad image filepaths or urls, extensions can choose to honor"> + hint="Ad image filepaths or urls, extensions can choose to honor" + >
{% raw %} -
-
- -
- Save -
-
-
- - +
+ +
+ Save +
+
+
+
+
-
- -

Admin

-

+ + - -
- - -
Wallet topup
-
-
- -
-
- -
-
-
- -
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -1100,14 +1114,22 @@ 'LnbitsWallet', 'OpenNodeWallet' ], - + admin: { edited: [], - funding: {}, + funding: [], senddata: {} } }, - themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'], + themes: [ + 'classic', + 'bitcoin', + 'flamingo', + 'mint', + 'autumn', + 'monochrome', + 'salvador' + ], options: [ 'bleskomat', 'captcha', @@ -1139,10 +1161,13 @@ self.cancel.on = true } funding = JSON.parse(String('{{ funding | tojson|safe }}')) - var i + funding.map(f => { + this.data.admin.funding.push(f) + }) + /*var i for (i = 0; i < funding.length; i++) { self.data.admin.funding[funding[i].backend_wallet] = funding[i] - } + }*/ let settings = JSON.parse('{{ settings | tojson|safe }}') settings.balance = '{{ balance }}' this.data.admin = {...this.data.admin, ...settings} @@ -1150,42 +1175,42 @@ console.log(settings) }, methods: { - addAdminUser(){ + addAdminUser() { let addUser = this.data.admin_users_add let admin_users = this.data.admin.admin_users - if(addUser.length && !admin_users.includes(addUser)){ + if (addUser.length && !admin_users.includes(addUser)) { admin_users.push(addUser) this.data.admin.admin_users = admin_users - this.data.admin_users_add = "" + this.data.admin_users_add = '' } }, - removeAdminUser(user){ + removeAdminUser(user) { let admin_users = this.data.admin.admin_users this.data.admin.admin_users = admin_users.filter(u => u !== user) }, - addAllowedUser(){ + addAllowedUser() { let addUser = this.data.allowed_users_add let allowed_users = this.data.admin.allowed_users - if(addUser.length && !allowed_users.includes(addUser)){ + if (addUser.length && !allowed_users.includes(addUser)) { allowed_users.push(addUser) this.data.admin.allowed_users = allowed_users - this.data.allowed_users_add = "" + this.data.allowed_users_add = '' } }, - removeAllowedUser(user){ + removeAllowedUser(user) { let allowed_users = this.data.admin.allowed_users this.data.admin.allowed_users = allowed_users.filter(u => u !== user) }, - addAdSpace(){ + addAdSpace() { let adSpace = this.data.ad_space_add let spaces = this.data.admin.ad_space - if(adSpace.length && !spaces.includes(adSpace)){ + if (adSpace.length && !spaces.includes(adSpace)) { spaces.push(adSpace) this.data.admin.ad_space = spaces - this.data.ad_space_add = "" + this.data.ad_space_add = '' } }, - removeAdSpace(ad){ + removeAdSpace(ad) { let spaces = this.data.admin.ad_space this.data.admin.ad_space = spaces.filter(s => s !== ad) }, @@ -1199,14 +1224,14 @@ this.wallet.data.amount, this.g.user.wallets[0].adminkey ) - .then((response) => { + .then(response => { this.$q.notify({ type: 'positive', message: - 'Success! Added ' + - this.wallet.data.amount + - ' to ' + - this.wallet.data.id, + 'Success! Added ' + + this.wallet.data.amount + + ' to ' + + this.wallet.data.id, icon: null }) this.wallet.data = {} @@ -1224,6 +1249,29 @@ self.data.admin.edited.push(source) console.log(self.data.admin.edited) }, + updateFunding(fund) { + let data = this.data.admin.funding.find(v => v.backend_wallet == fund) + + LNbits.api + .request( + 'POST', + '/admin/api/v1/admin/funding', + this.g.user.wallets[0].adminkey, + data + ) + .then(response => { + //let wallet = response.data.backend_wallet + //this.data.admin.funding[wallet] = response.data + //this.data.admin.funding[wallet].endpoint = response.data.endpoint + //console.log(this.data.admin.funding) + //console.log(this.data.admin) + this.$q.notify({ + type: 'positive', + message: `Success! ${response.data.backend_wallet} changed!`, + icon: null + }) + }) + }, UpdateLNbits() { let { admin_users, @@ -1272,8 +1320,7 @@ console.log(response.data) this.$q.notify({ type: 'positive', - message: - 'Success! Settings changed!', + message: 'Success! Settings changed!', icon: null }) }) diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 105f05a1..24b8ca85 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -21,8 +21,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() funding = [f.dict() for f in await get_funding()] error, balance = await g().WALLET.status() - print("ADMIN", admin.dict()) - print(g().admin_conf) + return admin_renderer().TemplateResponse( "admin/index.html", { "request": request, diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 1d4e6a9c..b797dc2d 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.extensions.admin import admin_ext -from lnbits.extensions.admin.models import Admin, UpdateAdminSettings +from lnbits.extensions.admin.models import Admin, Funding, UpdateAdminSettings from lnbits.helpers import removeEmptyString from lnbits.requestvars import g -from .crud import get_admin, update_admin, update_wallet_balance +from .crud import get_admin, update_admin, update_funding, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) @@ -53,3 +53,18 @@ async def api_update_admin( print(g().admin_conf) return {"status": "Success"} + +@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) +async def api_update_funding( + request: Request, + data: Funding = Body(...), + w: WalletTypeInfo = Depends(require_admin_key) + ): + admin = await get_admin() + + if not admin.user == w.wallet.user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) + funding = await update_funding(data=data) + return funding From a07fbf0187c72b45e0e102951faf3ed6cbfb75fc Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 18 Apr 2022 14:25:06 +0100 Subject: [PATCH 129/565] allow user settings without restart --- lnbits/core/views/generic.py | 7 ++++++- lnbits/decorators.py | 8 ++++++++ lnbits/helpers.py | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 31a7b030..83648c44 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,7 +15,9 @@ from lnbits.core import db from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer, url_for +from lnbits.requestvars import g from lnbits.settings import ( + LNBITS_ADMIN_UI, LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, LNBITS_CUSTOM_LOGO, @@ -37,7 +39,6 @@ from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) - @core_html_routes.get("/favicon.ico", response_class=FileResponse) async def favicon(): return FileResponse("lnbits/core/static/favicon.ico") @@ -119,6 +120,10 @@ async def wallet( wallet_name = nme service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + if not user_id: user = await get_user((await create_account()).id) logger.info(f"Create user {user.id}") # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index d4aa63ae..f951163f 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -16,6 +16,7 @@ from lnbits.core.models import User, Wallet from lnbits.requestvars import g from lnbits.settings import ( LNBITS_ADMIN_EXTENSIONS, + LNBITS_ADMIN_UI, LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, ) @@ -138,6 +139,9 @@ async def get_key_type( detail="Invoice (or Admin) key required.", ) + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + for typenr, WalletChecker in zip( [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] ): @@ -231,6 +235,10 @@ async def check_user_exists(usr: UUID4) -> User: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) + + if LNBITS_ADMIN_UI: + LNBITS_ADMIN_USERS = g().admin_conf.admin_users + LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: raise HTTPException( diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e456f715..1167143f 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -24,6 +24,9 @@ class Extension(NamedTuple): class ExtensionManager: def __init__(self): + if settings.LNBITS_ADMIN_UI: + settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext + settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS self._admin_only: List[str] = [ x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS From 931286b476bed767e3900a3b60cf10ab962c29e1 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 18 Apr 2022 14:25:26 +0100 Subject: [PATCH 130/565] advert for server restart option --- lnbits/extensions/admin/templates/admin/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d56b3d79..089c5f1c 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -51,7 +51,9 @@
-

Active Funding

+

+ Active Funding (Requires server restart) +

Date: Thu, 21 Apr 2022 11:08:26 +0100 Subject: [PATCH 131/565] cleanup prints and console logs --- lnbits/extensions/admin/crud.py | 2 +- lnbits/extensions/admin/templates/admin/index.html | 4 ++-- lnbits/extensions/admin/views_api.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index dd39e8e4..f866bc1a 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -27,7 +27,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - print("UPDATE", q) + # print("UPDATE", q) await db.execute( f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 089c5f1c..584d3a33 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1310,7 +1310,7 @@ theme: theme.toString(), ad_space: ad_space.toString() } - console.log(data) + //console.log(data) LNbits.api .request( 'POST', @@ -1319,7 +1319,7 @@ data ) .then(response => { - console.log(response.data) + //console.log(response.data) this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index b797dc2d..c0650c8a 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -35,7 +35,7 @@ async def api_update_admin( w: WalletTypeInfo = Depends(require_admin_key) ): admin = await get_admin() - print(data) + # print(data) if not admin.user == w.wallet.user: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" @@ -51,7 +51,7 @@ async def api_update_admin( g().admin_conf = g().admin_conf.copy(update=updated.dict()) - print(g().admin_conf) + # print(g().admin_conf) return {"status": "Success"} @admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) From 3f38a9094b9c9e5e3c6b29df67b3508efbc41be6 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 10:49:21 +0100 Subject: [PATCH 132/565] create first user on fresh install --- .env.example | 4 ++-- lnbits/config.py | 3 ++- lnbits/extensions/admin/README.md | 15 ++++++++------- lnbits/extensions/admin/migrations.py | 15 ++++++++++++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 4192f82e..f0e21aa8 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,8 @@ PORT=5000 DEBUG=false LNBITS_ADMIN_USERS="" # User IDs seperated by comma -LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS +LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access +LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma diff --git a/lnbits/config.py b/lnbits/config.py index b2fbfff1..3ce51c3c 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -19,6 +19,7 @@ def list_parse_fallback(v): return v.replace(' ','').split(',') class Settings(BaseSettings): + admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI") # users admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") @@ -37,7 +38,7 @@ class Settings(BaseSettings): site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") - theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS") + theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env env: Optional[str] diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md index 27729459..6cf073a1 100644 --- a/lnbits/extensions/admin/README.md +++ b/lnbits/extensions/admin/README.md @@ -1,11 +1,12 @@ -

Example Extension

-

*tagline*

-This is an example extension to help you organise and build you own. +# Admin Extension -Try to include an image - +## Dashboard to manage LNbits from the UI +With AdminUI you can manage your LNbits from the UI -

If your extension has API endpoints, include useful ones here

+![AdminUI](https://i.imgur.com/BIyLkyG.png) -curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" +## Before you start + +**This extension doesn't discard the need for the `.env` file!** +In the .env file, set the `LNBITS_ADMIN_USERS` variable to include at least your user id. diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 574f772d..0e22e667 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,9 +6,22 @@ from lnbits.config import conf from lnbits.helpers import urlsafe_short_hash +async def get_admin_user(): + if(conf.admin_users[0]): + return conf.admin_users[0] + from lnbits.core.crud import create_account, get_user + print("Seems like there's no admin users yet. Let's create an account for you!") + account = await create_account() + user = account.id + assert user, "Newly created user couldn't be retrieved" + print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + return user + + + async def m001_create_admin_table(db): # users/server - user = conf.admin_users[0] + user = await get_admin_user() admin_users = ",".join(conf.admin_users) allowed_users = ",".join(conf.allowed_users) admin_ext = ",".join(conf.admin_ext) From 0a29fb736093aeca3987122a7a78381b6f760499 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 12:29:58 +0100 Subject: [PATCH 133/565] fix schemas for admin --- lnbits/commands.py | 11 ++++++++--- lnbits/extensions/admin/crud.py | 12 ++++++------ lnbits/extensions/admin/migrations.py | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lnbits/commands.py b/lnbits/commands.py index 8c39c338..7d9b49e2 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -55,8 +55,12 @@ def bundle_vendored(): async def get_admin_settings(): from lnbits.extensions.admin.models import Admin - async with core_db.connect() as conn: + try: + ext_db = importlib.import_module(f"lnbits.extensions.admin").db + except: + return False + async with ext_db.connect() as conn: if conn.type == SQLITE: exists = await conn.fetchone( "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" @@ -65,11 +69,12 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) + print("EXISTS", exists) if not exists: return False - row = await conn.fetchone("SELECT * from admin") - + row = await conn.fetchone("SELECT * from admin.admin") + return Admin(**row) if row else None async def migrate_databases(): diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index f866bc1a..67fbc614 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -29,30 +29,30 @@ async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) await db.execute( - f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) + f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,)) + row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,)) assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None async def get_admin() -> Admin: - row = await db.fetchone("SELECT * FROM admin") + row = await db.fetchone("SELECT * FROM admin.admin") return Admin(**row) if row else None async def update_funding(data: Funding) -> Funding: await db.execute( """ - UPDATE funding + UPDATE admin.funding SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? WHERE id = ? """, (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), ) - row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,)) + row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,)) assert row, "Newly updated settings couldn't be retrieved" return Funding(**row) if row else None async def get_funding() -> List[Funding]: - rows = await db.fetchall("SELECT * FROM funding") + rows = await db.fetchall("SELECT * FROM admin.funding") return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 0e22e667..c94d140b 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -96,7 +96,7 @@ async def m001_create_admin_table(db): await db.execute( """ - CREATE TABLE IF NOT EXISTS admin ( + CREATE TABLE IF NOT EXISTS admin.admin ( "user" TEXT PRIMARY KEY, admin_users TEXT, allowed_users TEXT, @@ -120,7 +120,7 @@ async def m001_create_admin_table(db): ) await db.execute( """ - INSERT INTO admin ( + INSERT INTO admin.admin ( "user", admin_users, allowed_users, @@ -171,7 +171,7 @@ async def m001_create_funding_table(db): # Make the funding table, if it does not already exist await db.execute( """ - CREATE TABLE IF NOT EXISTS funding ( + CREATE TABLE IF NOT EXISTS admin.funding ( id TEXT PRIMARY KEY, backend_wallet TEXT, endpoint TEXT, @@ -188,7 +188,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, selected) VALUES (?, ?, ?, ?) """, ( @@ -200,7 +200,7 @@ async def m001_create_funding_table(db): ) await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -214,7 +214,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -228,7 +228,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( @@ -244,7 +244,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?) """, ( @@ -259,7 +259,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) VALUES (?, ?, ?, ?, ?, ?) """, ( @@ -274,7 +274,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -288,7 +288,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -302,7 +302,7 @@ async def m001_create_funding_table(db): await db.execute( """ - INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) VALUES (?, ?, ?, ?, ?) """, ( @@ -317,7 +317,7 @@ async def m001_create_funding_table(db): ## PLACEHOLDER FOR ECLAIR WALLET # await db.execute( # """ - # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected) + # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) # VALUES (?, ?, ?, ?, ?) # """, # ( From ac74cfaab7e70d8df8229241e040403d473f04e1 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 12:53:47 +0100 Subject: [PATCH 134/565] fix sqlite and show user account --- lnbits/app.py | 1 + lnbits/commands.py | 2 +- lnbits/extensions/admin/migrations.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/app.py b/lnbits/app.py index c8f5c60a..2b483758 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -116,6 +116,7 @@ def check_settings(app: FastAPI): while True: admin_set = await get_admin_settings() if admin_set : + print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") break print("ERROR:", admin_set) await asyncio.sleep(5) diff --git a/lnbits/commands.py b/lnbits/commands.py index 7d9b49e2..763a5b90 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -69,7 +69,7 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) - print("EXISTS", exists) + if not exists: return False diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index c94d140b..6c5b507d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -15,6 +15,7 @@ async def get_admin_user(): user = account.id assert user, "Newly created user couldn't be retrieved" print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + conf.admin_users.insert(0, user) return user From 1ebd557b1d544f6baab770111a4bf89f31abea66 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 16 May 2022 15:35:04 +0100 Subject: [PATCH 135/565] cleanup and info to user on startup --- lnbits/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 2b483758..eaa33136 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -112,13 +112,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI: def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): - while True: admin_set = await get_admin_settings() if admin_set : - print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") break - print("ERROR:", admin_set) + print("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) @@ -128,6 +126,7 @@ def check_settings(app: FastAPI): admin_set.theme = removeEmptyString(admin_set.theme.split(',')) admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) g().admin_conf = conf.copy(update=admin_set.dict()) + print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") From e04e24faec18e01c306d86a6bd45f238a74e1d38 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 8 Jun 2022 11:00:43 +0100 Subject: [PATCH 136/565] add custom logo --- lnbits/config.py | 1 + lnbits/extensions/admin/migrations.py | 58 ++----------------- lnbits/extensions/admin/models.py | 2 + .../admin/templates/admin/index.html | 20 +++++-- lnbits/helpers.py | 3 +- 5 files changed, 26 insertions(+), 58 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 3ce51c3c..d07ca044 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -39,6 +39,7 @@ class Settings(BaseSettings): site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") + custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env env: Optional[str] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 6c5b507d..aad66f02 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -41,60 +41,9 @@ async def m001_create_admin_table(db): site_description = conf.site_description default_wallet_name = conf.default_wallet_name theme = ",".join(conf.theme) + custom_logo = conf.custom_logo ad_space = ",".join(conf.ad_space) - # if getenv("LNBITS_ADMIN_EXTENSIONS"): - # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS") - - # if getenv("LNBITS_DATABASE_URL"): - # database_url = getenv("LNBITS_DATABASE_URL") - - # if getenv("LNBITS_HIDE_API"): - # hide_api = getenv("LNBITS_HIDE_API") - - # if getenv("LNBITS_THEME_OPTIONS"): - # theme = getenv("LNBITS_THEME_OPTIONS") - - # if getenv("LNBITS_AD_SPACE"): - # ad_space = getenv("LNBITS_AD_SPACE") - - # if getenv("LNBITS_SITE_TITLE"): - # site_title = getenv("LNBITS_SITE_TITLE") - - # if getenv("LNBITS_SITE_TAGLINE"): - # site_tagline = getenv("LNBITS_SITE_TAGLINE") - - # if getenv("LNBITS_SITE_DESCRIPTION"): - # site_description = getenv("LNBITS_SITE_DESCRIPTION") - - # if getenv("LNBITS_ALLOWED_USERS"): - # allowed_users = getenv("LNBITS_ALLOWED_USERS") - - # if getenv("LNBITS_ADMIN_USERS"): - # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split()) - # user = admin_users.split(',')[0] - - # if getenv("LNBITS_DEFAULT_WALLET_NAME"): - # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME") - - # if getenv("LNBITS_DATA_FOLDER"): - # data_folder = getenv("LNBITS_DATA_FOLDER") - - # if getenv("LNBITS_DISABLED_EXTENSIONS"): - # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS") - - # if getenv("LNBITS_FORCE_HTTPS"): - # force_https = getenv("LNBITS_FORCE_HTTPS") - - # if getenv("LNBITS_SERVICE_FEE"): - # service_fee = getenv("LNBITS_SERVICE_FEE") - - # if getenv("LNBITS_DENOMINATION"): - # denomination = getenv("LNBITS_DENOMINATION", "sats") - - # if getenv("LNBITS_BACKEND_WALLET_CLASS"): - # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS") - await db.execute( """ CREATE TABLE IF NOT EXISTS admin.admin ( @@ -115,6 +64,7 @@ async def m001_create_admin_table(db): site_description TEXT, default_wallet_name TEXT, theme TEXT, + custom_logo TEXT, ad_space TEXT ); """ @@ -139,8 +89,9 @@ async def m001_create_admin_table(db): site_description, default_wallet_name, theme, + custom_logo, ad_space) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( user, @@ -160,6 +111,7 @@ async def m001_create_admin_table(db): site_description, default_wallet_name, theme, + custom_logo, ad_space, ), ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 0f25679d..3b17e720 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -23,6 +23,7 @@ class UpdateAdminSettings(BaseModel): default_wallet_name: str = Query("LNbits wallet") denomination: str = Query("sats") theme: str = Query(None) + custom_logo: str = Query(None) ad_space: str = Query(None) class Admin(BaseModel): @@ -46,6 +47,7 @@ class Admin(BaseModel): default_wallet_name: Optional[str] denomination: str = Field(default="sats") theme: Optional[str] + custom_logo: Optional[str] ad_space: Optional[str] @classmethod diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 584d3a33..d9790051 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -705,6 +705,19 @@
+
+
+

Custom Logo

+ +
+
+
@@ -718,10 +731,7 @@
- +
@@ -1292,6 +1323,8 @@ disabled_ext, funding_source, force_https, + reserve_fee_min, + reserve_fee_pct, service_fee, hide_api, site_title, @@ -1311,6 +1344,8 @@ disabled_ext: disabled_ext.toString(), funding_source, force_https, + reserve_fee_min, + reserve_fee_pct, service_fee, hide_api, site_title, diff --git a/lnbits/settings.py b/lnbits/settings.py index ed5c77f7..8e5c321a 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,6 +1,5 @@ import importlib import subprocess -from email.policy import default from os import path from typing import List From 929d174ba4bc20c074ff7bfd2741521c846d005b Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 8 Jun 2022 15:38:28 +0100 Subject: [PATCH 138/565] calle's semantics --- lnbits/extensions/admin/templates/admin/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 832629bc..d34b9068 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -67,7 +67,7 @@
-

Minimum wallet reserve

+

Fee reserve

From 8e0baf7b2dcc2eb018ed972aba856ff3aca41b18 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 5 Jul 2022 16:25:02 +0100 Subject: [PATCH 139/565] blacked --- lnbits/extensions/admin/__init__.py | 1 + lnbits/extensions/admin/crud.py | 23 ++++++++++++--- lnbits/extensions/admin/migrations.py | 10 ++++--- lnbits/extensions/admin/models.py | 2 ++ lnbits/extensions/admin/views.py | 10 ++++--- lnbits/extensions/admin/views_api.py | 41 ++++++++++++++------------- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py index 6a56b2bb..24b91fe2 100644 --- a/lnbits/extensions/admin/__init__.py +++ b/lnbits/extensions/admin/__init__.py @@ -7,6 +7,7 @@ db = Database("ext_admin") admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"]) + def admin_renderer(): return template_renderer(["lnbits/extensions/admin/templates"]) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 67fbc614..0d7019cc 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -11,13 +11,13 @@ from .models import Admin, Funding async def update_wallet_balance(wallet_id: str, amount: int) -> str: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" - + payment = await create_payment( wallet_id=wallet_id, checking_id=internal_id, payment_request="admin_internal", payment_hash="admin_internal", - amount=amount*1000, + amount=amount * 1000, memo="Admin top up", pending=False, ) @@ -25,6 +25,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: await internal_invoice_queue.put(internal_id) return payment + async def update_admin(user: str, **kwargs) -> Admin: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) @@ -35,23 +36,37 @@ async def update_admin(user: str, **kwargs) -> Admin: assert row, "Newly updated settings couldn't be retrieved" return Admin(**row) if row else None + async def get_admin() -> Admin: row = await db.fetchone("SELECT * FROM admin.admin") return Admin(**row) if row else None + async def update_funding(data: Funding) -> Funding: await db.execute( """ UPDATE admin.funding SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ? WHERE id = ? - """, - (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,), + """, + ( + data.backend_wallet, + data.endpoint, + data.port, + data.read_key, + data.invoice_key, + data.admin_key, + data.cert, + data.balance, + data.selected, + data.id, + ), ) row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,)) assert row, "Newly updated settings couldn't be retrieved" return Funding(**row) if row else None + async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM admin.funding") diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index f3663435..388f5ec6 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -7,19 +7,21 @@ from lnbits.helpers import urlsafe_short_hash async def get_admin_user(): - if(conf.admin_users[0]): + if conf.admin_users[0]: return conf.admin_users[0] from lnbits.core.crud import create_account, get_user + print("Seems like there's no admin users yet. Let's create an account for you!") account = await create_account() user = account.id assert user, "Newly created user couldn't be retrieved" - print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.") + print( + f"Your newly created account/user id is: {user}. This will be the Super Admin user." + ) conf.admin_users.insert(0, user) return user - async def m001_create_admin_table(db): # users/server user = await get_admin_user() @@ -28,7 +30,7 @@ async def m001_create_admin_table(db): admin_ext = ",".join(conf.admin_ext) disabled_ext = ",".join(conf.disabled_ext) funding_source = conf.funding_source - #operational + # operational data_folder = conf.data_folder database_url = conf.database_url force_https = conf.force_https diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 3d8efdcd..6e95d68f 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -28,6 +28,7 @@ class UpdateAdminSettings(BaseModel): custom_logo: str = Query(None) ad_space: str = Query(None) + class Admin(BaseModel): # users user: str @@ -59,6 +60,7 @@ class Admin(BaseModel): data = dict(row) return cls(**data) + class Funding(BaseModel): id: str backend_wallet: str diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index 24b8ca85..ceda5192 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -16,19 +16,21 @@ from .crud import get_admin, get_funding templates = Jinja2Templates(directory="templates") + @admin_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): admin = await get_admin() funding = [f.dict() for f in await get_funding()] error, balance = await g().WALLET.status() - + return admin_renderer().TemplateResponse( - "admin/index.html", { + "admin/index.html", + { "request": request, "user": user.dict(), "admin": admin.dict(), "funding": funding, "settings": g().admin_conf.dict(), - "balance": balance - } + "balance": balance, + }, ) diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c0650c8a..784ad97f 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -15,16 +15,18 @@ from .crud import get_admin, update_admin, update_funding, update_wallet_balance @admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK) -async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)): +async def api_update_balance( + wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key) +): try: wallet = await get_wallet(wallet_id) except: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) - + return {"status": "Success"} @@ -32,39 +34,40 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D async def api_update_admin( request: Request, data: UpdateAdminSettings = Body(...), - w: WalletTypeInfo = Depends(require_admin_key) - ): + w: WalletTypeInfo = Depends(require_admin_key), +): admin = await get_admin() # print(data) if not admin.user == w.wallet.user: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) updated = await update_admin(user=w.wallet.user, **data.dict()) - updated.admin_users = removeEmptyString(updated.admin_users.split(',')) - updated.allowed_users = removeEmptyString(updated.allowed_users.split(',')) - updated.admin_ext = removeEmptyString(updated.admin_ext.split(',')) - updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(',')) - updated.theme = removeEmptyString(updated.theme.split(',')) - updated.ad_space = removeEmptyString(updated.ad_space.split(',')) + updated.admin_users = removeEmptyString(updated.admin_users.split(",")) + updated.allowed_users = removeEmptyString(updated.allowed_users.split(",")) + updated.admin_ext = removeEmptyString(updated.admin_ext.split(",")) + updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(",")) + updated.theme = removeEmptyString(updated.theme.split(",")) + updated.ad_space = removeEmptyString(updated.ad_space.split(",")) g().admin_conf = g().admin_conf.copy(update=updated.dict()) - + # print(g().admin_conf) return {"status": "Success"} + @admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK) async def api_update_funding( request: Request, data: Funding = Body(...), - w: WalletTypeInfo = Depends(require_admin_key) - ): + w: WalletTypeInfo = Depends(require_admin_key), +): admin = await get_admin() if not admin.user == w.wallet.user: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" - ) + status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin" + ) funding = await update_funding(data=data) return funding From 429217f5a4876920277ad0719458daca52045b90 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:28:13 +0100 Subject: [PATCH 140/565] Had to add a couple of tries --- lnbits/core/views/generic.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 83648c44..63f7af68 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -133,12 +133,19 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User does not exist."} ) - if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: - return template_renderer().TemplateResponse( - "error.html", {"request": request, "err": "User not authorized."} - ) - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True + try: + if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: + return template_renderer().TemplateResponse( + "error.html", {"request": request, "err": "User not authorized."} + ) + except: + pass + + try: + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True + except: + pass if not wallet_id: if user.wallets and not wallet_name: # type: ignore wallet = user.wallets[0] # type: ignore From 1aa2f01d29a25dd5fffac385afd3b5814e10b45c Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:31:31 +0100 Subject: [PATCH 141/565] Added couple more tries --- lnbits/decorators.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index f951163f..a810892d 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -239,13 +239,16 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users - - if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - - if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: - g().user.admin = True - + try: + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + except: + pass + try: + if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: + g().user.admin = True + except: + pass return g().user From 10a065a7ca4f98c725e690d66f9c4f5c60d0c4e3 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:35:06 +0100 Subject: [PATCH 142/565] Reverted try --- lnbits/decorators.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index a810892d..904ca1c2 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -239,16 +239,12 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users - try: - if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: - raise HTTPException( - status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." - ) - except: - pass - try: - if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: - g().user.admin = True + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) + if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: + g().user.admin = True except: pass return g().user From 3129692ab16fbd66727faba6fb04c87e56b6e0f7 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:37:07 +0100 Subject: [PATCH 143/565] reverted other try --- lnbits/core/views/generic.py | 19 ++++++------------- lnbits/decorators.py | 2 -- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 63f7af68..83648c44 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -133,19 +133,12 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User does not exist."} ) - try: - if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: - return template_renderer().TemplateResponse( - "error.html", {"request": request, "err": "User not authorized."} - ) - except: - pass - - try: - if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: - user.admin = True - except: - pass + if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: + return template_renderer().TemplateResponse( + "error.html", {"request": request, "err": "User not authorized."} + ) + if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS: + user.admin = True if not wallet_id: if user.wallets and not wallet_name: # type: ignore wallet = user.wallets[0] # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 904ca1c2..dd26d8fe 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -245,6 +245,4 @@ async def check_user_exists(usr: UUID4) -> User: ) if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS: g().user.admin = True - except: - pass return g().user From 42f6acd9f4f2f076cf278a843d48db11cbfb2b63 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 21 Sep 2022 18:40:46 +0100 Subject: [PATCH 144/565] fix main merge missing settings --- lnbits/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lnbits/app.py b/lnbits/app.py index eaa33136..176c6bb9 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -56,6 +56,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI: "url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE", }, ) + if lnbits.settings.LNBITS_ADMIN_UI: + g().admin_conf = conf + check_settings(app) + + g().WALLET = WALLET app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") app.mount( "/core/static", From a6bdd8c575f74685c9f71dfd142c908543b1d210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 10:46:11 +0200 Subject: [PATCH 145/565] format --- lnbits/app.py | 22 +++++++++++++--------- lnbits/commands.py | 6 +++++- lnbits/config.py | 25 ++++++++++++++++++------- lnbits/core/views/generic.py | 1 + lnbits/decorators.py | 2 +- lnbits/helpers.py | 8 +++++--- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 176c6bb9..f4e44a0f 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -114,24 +114,28 @@ def create_app(config_object="lnbits.settings") -> FastAPI: return app + def check_settings(app: FastAPI): @app.on_event("startup") async def check_settings_admin(): while True: admin_set = await get_admin_settings() - if admin_set : + if admin_set: break print("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) - - admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(',')) - admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(',')) - admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(',')) - admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(',')) - admin_set.theme = removeEmptyString(admin_set.theme.split(',')) - admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(',')) + + admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(",")) + admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(",")) + admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(",")) + admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(",")) + admin_set.theme = removeEmptyString(admin_set.theme.split(",")) + admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(",")) g().admin_conf = conf.copy(update=admin_set.dict()) - print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}") + print( + f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}" + ) + def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") diff --git a/lnbits/commands.py b/lnbits/commands.py index 763a5b90..86868f1f 100644 --- a/lnbits/commands.py +++ b/lnbits/commands.py @@ -5,6 +5,7 @@ import re import warnings import click +from genericpath import exists from loguru import logger from .core import db as core_db @@ -52,6 +53,7 @@ def bundle_vendored(): with open(outputpath, "w") as f: f.write(output) + async def get_admin_settings(): from lnbits.extensions.admin.models import Admin @@ -61,6 +63,7 @@ async def get_admin_settings(): return False async with ext_db.connect() as conn: + if conn.type == SQLITE: exists = await conn.fetchone( "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'" @@ -69,7 +72,7 @@ async def get_admin_settings(): exists = await conn.fetchone( "SELECT * FROM information_schema.tables WHERE table_name = 'admin'" ) - + if not exists: return False @@ -77,6 +80,7 @@ async def get_admin_settings(): return Admin(**row) if row else None + async def migrate_databases(): """Creates the necessary databases if they don't exist already; or migrates them.""" diff --git a/lnbits/config.py b/lnbits/config.py index 37b700fd..cf26ad21 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -6,17 +6,19 @@ from typing import List, Optional from pydantic import BaseSettings, Field wallets_module = importlib.import_module("lnbits.wallets") -wallet_class = getattr( +wallet_class = getattr( wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet") ) WALLET = wallet_class() + def list_parse_fallback(v): try: return json.loads(v) except Exception as e: - return v.replace(' ','').split(',') + return v.replace(" ", "").split(",") + class Settings(BaseSettings): admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI") @@ -24,7 +26,9 @@ class Settings(BaseSettings): admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS") allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS") admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS") - disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS") + disabled_ext: List[str] = Field( + default_factory=list, env="LNBITS_DISABLED_EXTENSIONS" + ) funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS") # ops data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER") @@ -37,10 +41,17 @@ class Settings(BaseSettings): denomination: str = Field(default="sats", env="LNBITS_DENOMINATION") # Change theme site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE") - site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE") + site_tagline: str = Field( + default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE" + ) site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION") - default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME") - theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS") + default_wallet_name: str = Field( + default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME" + ) + theme: List[str] = Field( + default=["classic, flamingo, mint, salvador, monochrome, autumn"], + env="LNBITS_THEME_OPTIONS", + ) custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env @@ -48,7 +59,7 @@ class Settings(BaseSettings): debug: Optional[str] host: Optional[str] port: Optional[str] - lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) + lnbits_path: Optional[str] = path.dirname(path.realpath(__file__)) # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True) # def validate(cls, val): diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 83648c44..3a1fbdfc 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -39,6 +39,7 @@ from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) + @core_html_routes.get("/favicon.ico", response_class=FileResponse) async def favicon(): return FileResponse("lnbits/core/static/favicon.ico") diff --git a/lnbits/decorators.py b/lnbits/decorators.py index dd26d8fe..58b025aa 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -235,7 +235,7 @@ async def check_user_exists(usr: UUID4) -> User: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="User does not exist." ) - + if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users diff --git a/lnbits/helpers.py b/lnbits/helpers.py index 7bd1b54a..f4255c86 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -157,11 +157,13 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s url = f"{base}{endpoint}{url_params}" return url + def removeEmptyString(arr): return list(filter(None, arr)) + def template_renderer(additional_folders: List = []) -> Jinja2Templates: - if(settings.LNBITS_ADMIN_UI): + if settings.LNBITS_ADMIN_UI: _ = g().admin_conf settings.LNBITS_AD_SPACE = _.ad_space settings.LNBITS_HIDE_API = _.hide_api @@ -170,8 +172,8 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates: settings.LNBITS_SITE_TAGLINE = _.site_tagline settings.LNBITS_SITE_DESCRIPTION = _.site_description settings.LNBITS_THEME_OPTIONS = _.theme - settings.LNBITS_CUSTOM_LOGO = _.custom_logo - + settings.LNBITS_CUSTOM_LOGO = _.custom_logo + t = Jinja2Templates( loader=jinja2.FileSystemLoader( ["lnbits/templates", "lnbits/core/templates", *additional_folders] From 635bcf66d6f8cf7a65322a41abed92153039cd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 11:47:24 +0200 Subject: [PATCH 146/565] format black --- lnbits/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/app.py b/lnbits/app.py index f4e44a0f..118bea98 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -56,6 +56,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: "url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE", }, ) + if lnbits.settings.LNBITS_ADMIN_UI: g().admin_conf = conf check_settings(app) From 11393ef7e9ed808d9034baa8515eabcd37918a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 15:29:12 +0200 Subject: [PATCH 147/565] fix AD_SPACE --- lnbits/core/templates/core/wallet.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 4bf6067c..cc45eb68 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -385,12 +385,9 @@ - {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = - ADS.split(';') %} + {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - {% endfor %} {% endif %}
From 0beb112d5b4f938201adfc6fc2814717ddac20a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 15:55:37 +0200 Subject: [PATCH 148/565] fix some javascript errors when adding users --- lnbits/extensions/admin/templates/admin/index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d34b9068..1e881cb6 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1221,7 +1221,7 @@ addAdminUser() { let addUser = this.data.admin_users_add let admin_users = this.data.admin.admin_users - if (addUser.length && !admin_users.includes(addUser)) { + if (addUser && addUser.length && !admin_users.includes(addUser)) { admin_users.push(addUser) this.data.admin.admin_users = admin_users this.data.admin_users_add = '' @@ -1234,7 +1234,7 @@ addAllowedUser() { let addUser = this.data.allowed_users_add let allowed_users = this.data.admin.allowed_users - if (addUser.length && !allowed_users.includes(addUser)) { + if (addUser && addUser.length && !allowed_users.includes(addUser)) { allowed_users.push(addUser) this.data.admin.allowed_users = allowed_users this.data.allowed_users_add = '' @@ -1336,7 +1336,6 @@ custom_logo, ad_space } = this.data.admin - //console.log("this", this.data.admin) let data = { admin_users: admin_users.toString(), allowed_users: allowed_users.toString(), From bfff5f3775cd090b454869c434042b4acdb37434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 16:04:55 +0200 Subject: [PATCH 149/565] fix ADMIN_UI=false errors --- lnbits/core/views/generic.py | 3 +++ lnbits/decorators.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 3a1fbdfc..db4fac43 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -124,6 +124,9 @@ async def wallet( if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + else: + LNBITS_ADMIN_USERS = [] + LNBITS_ALLOWED_USERS = [] if not user_id: user = await get_user((await create_account()).id) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 58b025aa..5a3c0a5c 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -141,6 +141,8 @@ async def get_key_type( if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users + else: + LNBITS_ADMIN_USERS = [] for typenr, WalletChecker in zip( [0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker] @@ -239,6 +241,10 @@ async def check_user_exists(usr: UUID4) -> User: if LNBITS_ADMIN_UI: LNBITS_ADMIN_USERS = g().admin_conf.admin_users LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users + else: + LNBITS_ADMIN_USERS = [] + LNBITS_ALLOWED_USERS = [] + if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: raise HTTPException( status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." From cede3317f3655c09b196c5293174a1dbaaaa18ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 16:14:17 +0200 Subject: [PATCH 150/565] prettier --- lnbits/core/templates/core/wallet.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index cc45eb68..5393007d 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -386,8 +386,7 @@ {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - - {% endfor %} {% endif %} From b442acc24f02c91971d29fbfe03dda9d5b5fc34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 22 Sep 2022 18:35:47 +0200 Subject: [PATCH 151/565] fix migration tests --- lnbits/extensions/admin/migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 388f5ec6..196c9fc0 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -7,7 +7,7 @@ from lnbits.helpers import urlsafe_short_hash async def get_admin_user(): - if conf.admin_users[0]: + if len(conf.admin_users) > 0: return conf.admin_users[0] from lnbits.core.crud import create_account, get_user From 6db5fb16c85d3fd654afd55ee8d6969d243572bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 26 Sep 2022 16:54:19 +0200 Subject: [PATCH 152/565] change comments to use multiple lines --- .env.example | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index f0e21aa8..673470b7 100644 --- a/.env.example +++ b/.env.example @@ -3,11 +3,15 @@ PORT=5000 DEBUG=false -LNBITS_ADMIN_USERS="" # User IDs seperated by comma -LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access -LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available +# User IDs seperated by comma +LNBITS_ADMIN_USERS="" +# Extensions only admin can access +LNBITS_ADMIN_EXTENSIONS="ngrok, admin" +# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available +LNBITS_ADMIN_UI=false -LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma +# Restricts access, User IDs seperated by comma +LNBITS_ALLOWED_USERS="" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" From affec50a3da3ac812b5046c49483676e93962b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 26 Sep 2022 16:59:30 +0200 Subject: [PATCH 153/565] fix merge error --- lnbits/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 118bea98..82a8f20e 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -57,10 +57,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI: }, ) - if lnbits.settings.LNBITS_ADMIN_UI: - g().admin_conf = conf - check_settings(app) - g().WALLET = WALLET app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static") app.mount( From 7aba2f989cdd78539ca22c69dc1a2e7d5ceb3d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:14:36 +0200 Subject: [PATCH 154/565] use logger in app.py --- lnbits/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 82a8f20e..e07d2ada 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -31,8 +31,6 @@ from .helpers import ( url_for_vendored, ) from .requestvars import g - -# from .settings import WALLET from .tasks import ( catch_everything_and_restart, check_pending_payments, @@ -119,7 +117,7 @@ def check_settings(app: FastAPI): admin_set = await get_admin_settings() if admin_set: break - print("Waiting for admin settings... retrying in 5 seconds!") + logger.info("Waiting for admin settings... retrying in 5 seconds!") await asyncio.sleep(5) admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(",")) @@ -129,7 +127,7 @@ def check_settings(app: FastAPI): admin_set.theme = removeEmptyString(admin_set.theme.split(",")) admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(",")) g().admin_conf = conf.copy(update=admin_set.dict()) - print( + logger.info( f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}" ) From 212b8f9fdd67c73b16f2c0d97cd549bc7bb8f29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:14:57 +0200 Subject: [PATCH 155/565] fix config --- lnbits/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index cf26ad21..874effae 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -17,7 +17,11 @@ def list_parse_fallback(v): try: return json.loads(v) except Exception as e: - return v.replace(" ", "").split(",") + replaced = v.replace(" ", "") + if replaced: + return replaced.split(",") + else: + return [] class Settings(BaseSettings): @@ -48,10 +52,7 @@ class Settings(BaseSettings): default_wallet_name: str = Field( default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME" ) - theme: List[str] = Field( - default=["classic, flamingo, mint, salvador, monochrome, autumn"], - env="LNBITS_THEME_OPTIONS", - ) + theme: List[str] = Field(default_factory=list, env="LNBITS_THEME_OPTIONS") custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO") ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE") # .env @@ -74,4 +75,5 @@ class Settings(BaseSettings): conf = Settings() +print(conf) WALLET = wallet_class() From b6a0a321844ff8a185f728b42f4ede3142334637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 14:17:20 +0200 Subject: [PATCH 156/565] concatenate first migrations script fixup --- lnbits/config.py | 1 - lnbits/extensions/admin/migrations.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lnbits/config.py b/lnbits/config.py index 874effae..fe8dabf9 100644 --- a/lnbits/config.py +++ b/lnbits/config.py @@ -75,5 +75,4 @@ class Settings(BaseSettings): conf = Settings() -print(conf) WALLET = wallet_class() diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 196c9fc0..2d48a8e4 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -23,6 +23,8 @@ async def get_admin_user(): async def m001_create_admin_table(db): + + # users/server user = await get_admin_user() admin_users = ",".join(conf.admin_users) @@ -78,7 +80,7 @@ async def m001_create_admin_table(db): await db.execute( """ INSERT INTO admin.admin ( - "user", + "user", admin_users, allowed_users, admin_ext, @@ -126,9 +128,6 @@ async def m001_create_admin_table(db): ), ) - -async def m001_create_funding_table(db): - funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") # Make the funding table, if it does not already exist From 1eeb9de7de8c2409f9a1d6b828e051ea640870d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Sep 2022 16:37:08 +0200 Subject: [PATCH 157/565] add restart button to frontend --- .../admin/templates/admin/index.html | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 1e881cb6..319ca3f0 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -65,6 +65,14 @@ :options="data.funding_source" >

+
+ +

Fee reserve

@@ -89,7 +97,7 @@ >
-
+ @@ -1257,6 +1265,24 @@ let spaces = this.data.admin.ad_space this.data.admin.ad_space = spaces.filter(s => s !== ad) }, + restartServer() { + LNbits.api + .request( + 'GET', + '/admin/api/v1/admin/restart/', + this.g.user.wallets[0].adminkey + ) + .then(response => { + this.$q.notify({ + type: 'positive', + message: 'Success! Restarted Server', + icon: null + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, topupWallet() { LNbits.api .request( From 82e322ae43658bef7735974324fd3bec7919b756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:34:52 +0200 Subject: [PATCH 158/565] make extension use new settings --- lnbits/extensions/boltz/boltz.py | 14 ++++++-------- lnbits/extensions/boltz/mempool.py | 15 ++++++--------- lnbits/extensions/boltz/views_api.py | 4 ++-- lnbits/extensions/lndhub/views_api.py | 4 ++-- lnbits/extensions/tpos/views.py | 14 +++++++------- 5 files changed, 23 insertions(+), 28 deletions(-) diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py index ac99d4f4..424d0bf7 100644 --- a/lnbits/extensions/boltz/boltz.py +++ b/lnbits/extensions/boltz/boltz.py @@ -12,7 +12,7 @@ from loguru import logger from lnbits.core.services import create_invoice, pay_invoice from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL +from lnbits.settings import settings from .crud import update_swap_status from .mempool import ( @@ -33,9 +33,7 @@ from .models import ( ) from .utils import check_balance, get_timestamp, req_wrap -net = NETWORKS[BOLTZ_NETWORK] -logger.trace(f"BOLTZ_URL: {BOLTZ_URL}") -logger.trace(f"Bitcoin Network: {net['name']}") +net = NETWORKS[settings.boltz_network] async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: @@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: res = req_wrap( "post", - f"{BOLTZ_URL}/createswap", + f"{settings.boltz_url}/createswap", json={ "type": "submarine", "pairId": "BTC/BTC", @@ -129,7 +127,7 @@ async def create_reverse_swap( res = req_wrap( "post", - f"{BOLTZ_URL}/createswap", + f"{settings.boltz_url}/createswap", json={ "type": "reversesubmarine", "pairId": "BTC/BTC", @@ -409,7 +407,7 @@ def check_boltz_limits(amount): def get_boltz_pairs(): res = req_wrap( "get", - f"{BOLTZ_URL}/getpairs", + f"{settings.boltz_url}/getpairs", headers={"Content-Type": "application/json"}, ) return res.json() @@ -418,7 +416,7 @@ def get_boltz_pairs(): def get_boltz_status(boltzid): res = req_wrap( "post", - f"{BOLTZ_URL}/swapstatus", + f"{settings.boltz_url}/swapstatus", json={"id": boltzid}, ) return res.json() diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py index a44c0f02..a64cadad 100644 --- a/lnbits/extensions/boltz/mempool.py +++ b/lnbits/extensions/boltz/mempool.py @@ -7,14 +7,11 @@ import websockets from embit.transaction import Transaction from loguru import logger -from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS +from lnbits.settings import settings from .utils import req_wrap -logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") -logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") - -websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws" +websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws" async def wait_for_websocket_message(send, message_string): @@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string): def get_mempool_tx(address): res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs", + f"{settings.boltz_mempool_space_url}/api/address/{address}/txs", headers={"Content-Type": "text/plain"}, ) txs = res.json() @@ -70,7 +67,7 @@ def get_fee_estimation() -> int: def get_mempool_fees() -> int: res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended", + f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended", headers={"Content-Type": "text/plain"}, ) fees = res.json() @@ -80,7 +77,7 @@ def get_mempool_fees() -> int: def get_mempool_blockheight() -> int: res = req_wrap( "get", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height", + f"{settings.boltz_mempool_space_url}/api/blocks/tip/height", headers={"Content-Type": "text/plain"}, ) return int(res.text) @@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction): logger.debug(f"Boltz - mempool sending onchain tx...") req_wrap( "post", - f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx", + f"{settings.boltz_mempool_space_url}/api/tx", headers={"Content-Type": "text/plain"}, content=raw, ) diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py index a4b7d318..18ca14cb 100644 --- a/lnbits/extensions/boltz/views_api.py +++ b/lnbits/extensions/boltz/views_api.py @@ -14,7 +14,7 @@ from starlette.requests import Request from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL +from lnbits.settings import settings from . import boltz_ext from .boltz import ( @@ -55,7 +55,7 @@ from .utils import check_balance response_model=str, ) async def api_mempool_url(): - return BOLTZ_MEMPOOL_SPACE_URL + return settings.boltz_mempool_space_url # NORMAL SWAP diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py index 8cbe5a6b..b2328c39 100644 --- a/lnbits/extensions/lndhub/views_api.py +++ b/lnbits/extensions/lndhub/views_api.py @@ -12,7 +12,7 @@ from lnbits import bolt11 from lnbits.core.crud import delete_expired_invoices, get_payments from lnbits.core.services import create_invoice, pay_invoice from lnbits.decorators import WalletTypeInfo -from lnbits.settings import LNBITS_SITE_TITLE, WALLET +from lnbits.settings import WALLET, settings from . import lndhub_ext from .decorators import check_wallet, require_admin_key @@ -56,7 +56,7 @@ async def lndhub_addinvoice( _, pr = await create_invoice( wallet_id=wallet.wallet.id, amount=int(data.amt), - memo=data.memo or LNBITS_SITE_TITLE, + memo=data.memo or settings.lnbits_site_title, extra={"tag": "lndhub"}, ) except: diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py index e1f1d21e..dac129a9 100644 --- a/lnbits/extensions/tpos/views.py +++ b/lnbits/extensions/tpos/views.py @@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE +from lnbits.settings import settings from . import tpos_ext, tpos_renderer from .crud import get_tpos @@ -50,12 +50,12 @@ async def manifest(tpos_id: str): ) return { - "short_name": LNBITS_SITE_TITLE, - "name": tpos.name + " - " + LNBITS_SITE_TITLE, + "short_name": settings.lnbits_site_title, + "name": tpos.name + " - " + settings.lnbits_site_title, "icons": [ { - "src": LNBITS_CUSTOM_LOGO - if LNBITS_CUSTOM_LOGO + "src": settings.lnbits_custom_logo + if settings.lnbits_custom_logo else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", "type": "image/png", "sizes": "900x900", @@ -69,9 +69,9 @@ async def manifest(tpos_id: str): "theme_color": "#1F2234", "shortcuts": [ { - "name": tpos.name + " - " + LNBITS_SITE_TITLE, + "name": tpos.name + " - " + settings.lnbits_site_title, "short_name": tpos.name, - "description": tpos.name + " - " + LNBITS_SITE_TITLE, + "description": tpos.name + " - " + settings.lnbits_site_title, "url": "/tpos/" + tpos_id, } ], From 5aa9cdd45631379341f31d17d9484c5e3ad05a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:35:26 +0200 Subject: [PATCH 159/565] remove enviroms --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7418de27..92c43dce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ charset-normalizer = "2.0.6" click = "8.0.1" ecdsa = "0.17.0" embit = "0.4.9" -environs = "9.3.3" fastapi = "0.78.0" h11 = "0.12.0" httpcore = "0.15.0" From 6a2c7414783a5edb4b7932355f3fe145053332a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 3 Oct 2022 16:36:14 +0200 Subject: [PATCH 160/565] fix admin --- lnbits/extensions/admin/crud.py | 24 +- lnbits/extensions/admin/migrations.py | 337 +---- lnbits/extensions/admin/models.py | 58 +- .../admin/templates/admin/_tab_funding.html | 158 +++ .../admin/templates/admin/_tab_server.html | 78 ++ .../admin/templates/admin/_tab_theme.html | 122 ++ .../admin/templates/admin/_tab_users.html | 96 ++ .../admin/templates/admin/index.html | 1133 +---------------- lnbits/extensions/admin/views.py | 16 +- lnbits/extensions/admin/views_api.py | 28 +- 10 files changed, 576 insertions(+), 1474 deletions(-) create mode 100644 lnbits/extensions/admin/templates/admin/_tab_funding.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_server.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_theme.html create mode 100644 lnbits/extensions/admin/templates/admin/_tab_users.html diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 0d7019cc..e4cb5d77 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -2,10 +2,11 @@ from typing import List from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash +from lnbits.settings import Settings from lnbits.tasks import internal_invoice_queue from . import db -from .models import Admin, Funding +from .models import Funding async def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -23,26 +24,26 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: ) # manually send this for now await internal_invoice_queue.put(internal_id) - return payment -async def update_admin(user: str, **kwargs) -> Admin: +async def update_settings(user: str, **kwargs) -> Settings: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) # print("UPDATE", q) await db.execute( - f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user) + f'UPDATE admin.settings SET {q} WHERE "user" = ?', (*kwargs.values(), user) ) - row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,)) + row = await db.fetchone('SELECT * FROM admin.settings WHERE "user" = ?', (user,)) assert row, "Newly updated settings couldn't be retrieved" - return Admin(**row) if row else None - - -async def get_admin() -> Admin: - row = await db.fetchone("SELECT * FROM admin.admin") - return Admin(**row) if row else None + return Settings(**row) if row else None async def update_funding(data: Funding) -> Funding: + await db.execute( + """ + UPDATE admin.settings SET funding_source = ? WHERE user = ? + """, + (data.backend_wallet, data.user), + ) await db.execute( """ UPDATE admin.funding @@ -69,5 +70,4 @@ async def update_funding(data: Funding) -> Funding: async def get_funding() -> List[Funding]: rows = await db.fetchall("SELECT * FROM admin.funding") - return [Funding(**row) for row in rows] diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 2d48a8e4..8f6c76a0 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -1,292 +1,57 @@ -from os import getenv - -from sqlalchemy.exc import OperationalError # type: ignore - -from lnbits.config import conf -from lnbits.helpers import urlsafe_short_hash - - -async def get_admin_user(): - if len(conf.admin_users) > 0: - return conf.admin_users[0] - from lnbits.core.crud import create_account, get_user - - print("Seems like there's no admin users yet. Let's create an account for you!") - account = await create_account() - user = account.id - assert user, "Newly created user couldn't be retrieved" - print( - f"Your newly created account/user id is: {user}. This will be the Super Admin user." - ) - conf.admin_users.insert(0, user) - return user - - -async def m001_create_admin_table(db): - - - # users/server - user = await get_admin_user() - admin_users = ",".join(conf.admin_users) - allowed_users = ",".join(conf.allowed_users) - admin_ext = ",".join(conf.admin_ext) - disabled_ext = ",".join(conf.disabled_ext) - funding_source = conf.funding_source - # operational - data_folder = conf.data_folder - database_url = conf.database_url - force_https = conf.force_https - reserve_fee_min = conf.reserve_fee_min - reserve_fee_pct = conf.reserve_fee_pct - service_fee = conf.service_fee - hide_api = conf.hide_api - denomination = conf.denomination - # Theme'ing - site_title = conf.site_title - site_tagline = conf.site_tagline - site_description = conf.site_description - default_wallet_name = conf.default_wallet_name - theme = ",".join(conf.theme) - custom_logo = conf.custom_logo - ad_space = ",".join(conf.ad_space) - +async def m001_create_admin_settings_table(db): await db.execute( """ - CREATE TABLE IF NOT EXISTS admin.admin ( - "user" TEXT PRIMARY KEY, - admin_users TEXT, - allowed_users TEXT, - admin_ext TEXT, - disabled_ext TEXT, - funding_source TEXT, - data_folder TEXT, - database_url TEXT, - force_https BOOLEAN, - reserve_fee_min INT, - reserve_fee_pct REAL, - service_fee REAL, - hide_api BOOLEAN, - denomination TEXT, - site_title TEXT, - site_tagline TEXT, - site_description TEXT, - default_wallet_name TEXT, - theme TEXT, - custom_logo TEXT, - ad_space TEXT - ); - """ - ) - await db.execute( - """ - INSERT INTO admin.admin ( - "user", - admin_users, - allowed_users, - admin_ext, - disabled_ext, - funding_source, - data_folder, - database_url, - force_https, - reserve_fee_min, - reserve_fee_pct, - service_fee, - hide_api, - denomination, - site_title, - site_tagline, - site_description, - default_wallet_name, - theme, - custom_logo, - ad_space) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - user, - admin_users, - allowed_users, - admin_ext, - disabled_ext, - funding_source, - data_folder, - database_url, - force_https, - reserve_fee_min, - reserve_fee_pct, - service_fee, - hide_api, - denomination, - site_title, - site_tagline, - site_description, - default_wallet_name, - theme, - custom_logo, - ad_space, - ), - ) - - funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS") - - # Make the funding table, if it does not already exist - await db.execute( - """ - CREATE TABLE IF NOT EXISTS admin.funding ( - id TEXT PRIMARY KEY, - backend_wallet TEXT, - endpoint TEXT, + CREATE TABLE IF NOT EXISTS admin.settings ( + lnbits_admin_ui TEXT, + debug TEXT, + host TEXT, port INT, - read_key TEXT, - invoice_key TEXT, - admin_key TEXT, - cert TEXT, - balance INT, - selected INT + lnbits_path TEXT, + lnbits_commit TEXT, + lnbits_admin_users TEXT, + lnbits_allowed_users TEXT, + lnbits_allowed_funding_sources TEXT, + lnbits_admin_extensions TEXT, + lnbits_disabled_extensions TEXT, + lnbits_site_title TEXT, + lnbits_site_tagline TEXT, + lnbits_site_description TEXT, + lnbits_default_wallet_name TEXT, + lnbits_theme_options TEXT, + lnbits_custom_logo TEXT, + lnbits_ad_space TEXT, + lnbits_data_folder TEXT, + lnbits_database_url TEXT, + lnbits_force_https TEXT, + lnbits_reserve_fee_min TEXT, + lnbits_reserve_fee_percent TEXT, + lnbits_service_fee TEXT, + lnbits_hide_api TEXT, + lnbits_denomination TEXT, + lnbits_backend_wallet_class TEXT, + fake_wallet_secret TEXT, + lnbits_endpoint TEXT, + lnbits_key TEXT, + cliche_endpoint TEXT, + corelightning_rpc TEXT, + eclair_url TEXT, + eclair_pass TEXT, + lnd_rest_endpoint TEXT, + lnd_rest_cert TEXT, + lnd_rest_macaroon TEXT, + lnpay_api_endpoint TEXT, + lnpay_api_key TEXT, + lnpay_wallet_key TEXT, + lntxbot_api_endpoint TEXT, + lntxbot_key TEXT, + opennode_api_endpoint TEXT, + opennode_key TEXT, + spark_url TEXT, + spark_token TEXT, + boltz_network TEXT, + boltz_url TEXT, + boltz_mempool_space_url TEXT, + boltz_mempool_space_url_ws TEXT ); """ ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, selected) - VALUES (?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "CLightningWallet", - getenv("CLIGHTNING_RPC"), - 1 if funding_wallet == "CLightningWallet" else 0, - ), - ) - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "SparkWallet", - getenv("SPARK_URL"), - getenv("SPARK_TOKEN"), - 1 if funding_wallet == "SparkWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LnbitsWallet", - getenv("LNBITS_ENDPOINT"), - getenv("LNBITS_KEY"), - 1 if funding_wallet == "LnbitsWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LndWallet", - getenv("LND_GRPC_ENDPOINT"), - getenv("LND_GRPC_PORT"), - getenv("LND_GRPC_MACAROON"), - getenv("LND_GRPC_CERT"), - 1 if funding_wallet == "LndWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LndRestWallet", - getenv("LND_REST_ENDPOINT"), - getenv("LND_REST_MACAROON"), - getenv("LND_REST_CERT"), - 1 if funding_wallet == "LndWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected) - VALUES (?, ?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LNPayWallet", - getenv("LNPAY_API_ENDPOINT"), - getenv("LNPAY_WALLET_KEY"), - getenv("LNPAY_API_KEY"), # this is going in as the cert - 1 if funding_wallet == "LNPayWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "LntxbotWallet", - getenv("LNTXBOT_API_ENDPOINT"), - getenv("LNTXBOT_KEY"), - 1 if funding_wallet == "LntxbotWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "OpenNodeWallet", - getenv("OPENNODE_API_ENDPOINT"), - getenv("OPENNODE_KEY"), - 1 if funding_wallet == "OpenNodeWallet" else 0, - ), - ) - - await db.execute( - """ - INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - VALUES (?, ?, ?, ?, ?) - """, - ( - urlsafe_short_hash(), - "SparkWallet", - getenv("SPARK_URL"), - getenv("SPARK_TOKEN"), - 1 if funding_wallet == "SparkWallet" else 0, - ), - ) - - ## PLACEHOLDER FOR ECLAIR WALLET - # await db.execute( - # """ - # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected) - # VALUES (?, ?, ?, ?, ?) - # """, - # ( - # urlsafe_short_hash(), - # "EclairWallet", - # getenv("ECLAIR_URL"), - # getenv("ECLAIR_PASS"), - # 1 if funding_wallet == "EclairWallet" else 0, - # ), - # ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 6e95d68f..ef57cadd 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -29,36 +29,36 @@ class UpdateAdminSettings(BaseModel): ad_space: str = Query(None) -class Admin(BaseModel): - # users - user: str - admin_users: Optional[str] - allowed_users: Optional[str] - admin_ext: Optional[str] - disabled_ext: Optional[str] - funding_source: Optional[str] - # ops - data_folder: Optional[str] - database_url: Optional[str] - force_https: bool = Field(default=True) - reserve_fee_min: Optional[int] - reserve_fee_pct: Optional[float] - service_fee: float = Optional[float] - hide_api: bool = Field(default=False) - # Change theme - site_title: Optional[str] - site_tagline: Optional[str] - site_description: Optional[str] - default_wallet_name: Optional[str] - denomination: str = Field(default="sats") - theme: Optional[str] - custom_logo: Optional[str] - ad_space: Optional[str] +# class Admin(BaseModel): +# # users +# user: str +# admin_users: Optional[str] +# allowed_users: Optional[str] +# admin_ext: Optional[str] +# disabled_ext: Optional[str] +# funding_source: Optional[str] +# # ops +# data_folder: Optional[str] +# database_url: Optional[str] +# force_https: bool = Field(default=True) +# reserve_fee_min: Optional[int] +# reserve_fee_pct: Optional[float] +# service_fee: float = Optional[float] +# hide_api: bool = Field(default=False) +# # Change theme +# site_title: Optional[str] +# site_tagline: Optional[str] +# site_description: Optional[str] +# default_wallet_name: Optional[str] +# denomination: str = Field(default="sats") +# theme: Optional[str] +# custom_logo: Optional[str] +# ad_space: Optional[str] - @classmethod - def from_row(cls, row: Row) -> "Admin": - data = dict(row) - return cls(**data) +# @classmethod +# def from_row(cls, row: Row) -> "Admin": +# data = dict(row) +# return cls(**data) class Funding(BaseModel): diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html new file mode 100644 index 00000000..2ed0aae2 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -0,0 +1,158 @@ + + +
Wallets Management
+
+
+
+
+

Funding Source Info

+
    + {%raw%} +
  • + Funding Source: {{data.settings.lnbits_backend_wallet_class}} +
  • +
  • Balance: {{data.balance / 1000}} sats
  • + {%endraw%} +
+
+
+
+
+
+
+
+

Active Funding (Requires server restart)

+ +
+
+ +
+
+
+

Fee reserve

+
+
+ +
+
+ +
+
+
+
+
+
+

TopUp a wallet

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+

Funding Sources

+ {% raw %} + + + + + + + + + + + + + + {% endraw %} +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_server.html b/lnbits/extensions/admin/templates/admin/_tab_server.html new file mode 100644 index 00000000..2924e6a4 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_server.html @@ -0,0 +1,78 @@ + + +
Server Management
+
+
+
+
+

Server Info

+
    + {%raw%} +
  • + SQlite: {{data.settings.lnbits_data_folder}} +
  • +
  • + Postgres: {{data.settings.lnbits_database_url}} +
  • + {%endraw%} +
+
+
+
+
+
+

Service Fee

+ +
+
+
+

Miscelaneous

+ + + Force HTTPS + Prefer secure URLs + + + + + + + + Hide API + Hides wallet api, extensions can choose to honor + + + + + +
+
+
+
+ +
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html new file mode 100644 index 00000000..41dc0447 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html @@ -0,0 +1,122 @@ + + +
UI Management
+
+
+
+
+

Site Title

+ +
+
+
+

Site Tagline

+ +
+
+
+
+

Site Description

+ +
+
+
+
+

Default Wallet Name

+ +
+
+
+

Denomination

+ +
+
+
+
+
+

Themes

+ +
+
+
+

Advertisement Slots

+ + + +
+ {% raw %} + + {{ space.slice(0, 8) + " ... " + space.slice(-8) }} + + {% endraw %} +
+
+
+
+
+
+

Custom Logo

+ +
+
+
+
+ +
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html new file mode 100644 index 00000000..3eb53ac4 --- /dev/null +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -0,0 +1,96 @@ + + +
User Management
+
+

+ Super Admin: {% raw %}{{this.data.settings.lnbits_admin_users[0]}}{% + endraw %} +

+
+
+

Admin Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+

Allowed Users

+ + + +
+ {% raw %} + + {{ user }} + + {% endraw %} +
+
+
+
+
+

Admin Extensions

+ +
+
+
+

Disabled Extensions

+ +
+
+
+
+ Save +
+
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 319ca3f0..87e89321 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -29,1118 +29,16 @@ - - - -
Wallets Management
-
-
-
-
-

Funding Source Info

-
    - {%raw%} -
  • Funding Source: {{data.admin.funding_source}}
  • -
  • Balance: {{data.admin.balance / 1000}} sats
  • - {%endraw%} -
-
-
-
-
-
-
-
-

- Active Funding - (Requires server restart) -

- -
-
- -
-
-
-

Fee reserve

-
-
- -
-
- -
-
- -
-
-
-
- -

TopUp a wallet

-
-
- -
-
-
- -
-
-
- -
- -
-
-
-

Funding Sources

- {% raw %} - - - - - - - - - - - - - - {% endraw %} - -
-
-
- - -
User Management
-
-

- Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %} -

-
-
-

Admin Users

- - - -
- {% raw %} - - {{ user }} - - {% endraw %} -
-
-
-
-

Allowed Users

- - - -
- {% raw %} - - {{ user }} - - {% endraw %} -
-
-
-
-
-

Admin Extensions

- -
-
-
-

Disabled Extensions

- -
-
-
-
- Save -
-
-
- - -
Server Management
-
-
-
-
-

Server Info

-
    - {%raw%} -
  • - SQlite: {{data.admin.data_folder}} -
  • -
  • - Postgres: {{data.admin.database_url}} -
  • - {%endraw%} -
-
-
-
-
-
-

Service Fee

- -
-
-
-

Miscelaneous

- - - Force HTTPS - Prefer secure URLs - - - - - - - - Hide API - Hides wallet api, extensions can choose to - honor - - - - - -
-
-
-
- -
- Save -
-
-
- - -
UI Management
-
-
-
-
-

Site Title

- -
-
-
-

Site Tagline

- -
-
-
-
-

Site Description

- -
-
-
-
-

Default Wallet Name

- -
-
-
-

Denomination

- -
-
-
-
-
-

Themes

- -
-
-
-

Advertisement Slots

- - - -
- {% raw %} - - {{ space.slice(0, 8) + " ... " + space.slice(-8) }} - - {% endraw %} -
-
-
-
-
-
-

Custom Logo

- -
-
-
-
- -
- Save -
-
-
+ {% include "admin/_tab_funding.html" %} {% include + "admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {% + include "admin/_tab_theme.html" %}
- - -
- -
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} From 04b37458983094ff9deec4ba010ec2a93c002cd9 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 7 Oct 2022 19:24:07 +0100 Subject: [PATCH 186/565] make saving possible (possible will change in future) --- .../admin/templates/admin/index.html | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 72352651..18df16a9 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -121,8 +121,11 @@ created: function () { this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data this.balance = +'{{ balance|safe }}' - this.formData = this.settings //model + this.formData = _.clone(this.settings) //model + //this.formData.lnbits_ad_space = "hdh" console.log(this.formData) + console.log(_.isEqual(this.settings, this.formData)) + }, methods: { addAdminUser() { @@ -206,18 +209,27 @@ }, updateSettings() { let data = { - ...this.settings, - ...this.formData + lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class, + lnbits_admin_users: this.formData.lnbits_admin_users.toString(), + lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(), + lnbits_admin_ext: this.formData.lnbits_admin_ext, + lnbits_disabled_ext: this.formData.lnbits_disabled_ext, + lnbits_funding_source: this.formData.lnbits_funding_source, + lnbits_force_https: this.formData.lnbits_force_https, + lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min, + lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent, + lnbits_service_fee: this.formData.lnbits_service_fee, + lnbits_hide_api: this.formData.lnbits_hide_api, + lnbits_site_title: this.formData.lnbits_site_title, + lnbits_site_tagline: this.formData.lnbits_site_tagline, + lnbits_site_description: this.formData.lnbits_site_description, + lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name, + lnbits_denomination: this.formData.lnbits_denomination, + lnbits_theme: this.formData.lnbits_theme, + lnbits_custom_logo: this.formData.lnbits_custom_logo, + lnbits_ad_space: this.formData.lnbits_ad_space.toString() } - /* - const formElement = document.getElementById('settings_form') - const formData = new FormData(formElement) - const data = {} - formData.forEach((value, key) => (data[key] = value)) - // only for debugging - for (const [key, value] of formData) { - console.log(`${key}: ${value}\n`) - }*/ + console.log(data) LNbits.api .request( 'PUT', From cc42df12f4d3927c854675f114aff0b75bef6d19 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 7 Oct 2022 19:44:03 +0100 Subject: [PATCH 187/565] some more refining --- lnbits/extensions/admin/models.py | 10 +++++----- .../extensions/admin/templates/admin/_tab_users.html | 2 ++ lnbits/extensions/admin/templates/admin/index.html | 12 +++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 13a6cd23..45cd990d 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -4,10 +4,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: str = Query(None) - lnbits_allowed_users: str = Query(None) - lnbits_admin_ext: str = Query(None) - lnbits_disabled_ext: str = Query(None) + lnbits_admin_users: str = Query(None) #this should be List[str] ?? + lnbits_allowed_users: str = Query(None) #this should be List[str] ?? + lnbits_admin_ext: str = Query(None) #this should be List[str] ?? + lnbits_disabled_ext: str = Query(None) #this should be List[str] ?? lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -21,4 +21,4 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: str = Query(None) + lnbits_ad_space: str = Query(None) #this should be List[str] ?? diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html index c396ba7d..08b08a62 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_users.html +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -71,6 +71,7 @@ multiple hint="Extensions only user with admin privileges can use" label="Admin extensions" + :options="g.extensions.map(e => e.name)" >
@@ -79,6 +80,7 @@
- + Date: Mon, 10 Oct 2022 12:17:35 +0100 Subject: [PATCH 188/565] get saved data and alert when data changed --- .../admin/templates/admin/index.html | 18 +++++++----------- lnbits/extensions/admin/views_api.py | 5 +++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 4754656d..4e401cb4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,7 @@
- + { + this.settings = response.data.settings + this.formData = _.clone(this.settings) this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c8120564..c2079e37 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -43,8 +43,9 @@ async def api_update_settings( user: User = Depends(check_admin), data: UpdateSettings = Body(...), ): - await update_settings(data) - return {"status": "Success"} + settings = await update_settings(data) + logger.debug(settings) + return {"status": "Success", "settings": settings.dict()} @admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK) From 001f6589bb3c173c9ada0f86ec0a2eca08f3b051 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 10 Oct 2022 12:23:19 +0100 Subject: [PATCH 189/565] cleanup and typing fix for data --- lnbits/extensions/admin/models.py | 12 +++++----- .../admin/templates/admin/index.html | 22 ++----------------- lnbits/extensions/admin/views_api.py | 1 - 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 45cd990d..94fa56bb 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,13 +1,15 @@ +from typing import List + from fastapi import Query from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: str = Query(None) #this should be List[str] ?? - lnbits_allowed_users: str = Query(None) #this should be List[str] ?? - lnbits_admin_ext: str = Query(None) #this should be List[str] ?? - lnbits_disabled_ext: str = Query(None) #this should be List[str] ?? + lnbits_admin_users: List[str] = Query(None) + lnbits_allowed_users: List[str] = Query(None) + lnbits_admin_ext: List[str] = Query(None) + lnbits_disabled_ext: List[str] = Query(None) lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -21,4 +23,4 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: str = Query(None) #this should be List[str] ?? + lnbits_ad_space: List[str] = Query(None) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 4e401cb4..d8111595 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -209,26 +209,8 @@ }) }, updateSettings() { - let data = { - lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class, - lnbits_admin_users: this.formData.lnbits_admin_users.toString(), - lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(), - lnbits_admin_ext: this.formData.lnbits_admin_ext, - lnbits_disabled_ext: this.formData.lnbits_disabled_ext, - lnbits_funding_source: this.formData.lnbits_funding_source, - lnbits_force_https: this.formData.lnbits_force_https, - lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min, - lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent, - lnbits_service_fee: this.formData.lnbits_service_fee, - lnbits_hide_api: this.formData.lnbits_hide_api, - lnbits_site_title: this.formData.lnbits_site_title, - lnbits_site_tagline: this.formData.lnbits_site_tagline, - lnbits_site_description: this.formData.lnbits_site_description, - lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name, - lnbits_denomination: this.formData.lnbits_denomination, - lnbits_theme: this.formData.lnbits_theme, - lnbits_custom_logo: this.formData.lnbits_custom_logo, - lnbits_ad_space: this.formData.lnbits_ad_space.toString() + let data = { + ...this.formData } LNbits.api .request( diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index c2079e37..19b52e35 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -44,7 +44,6 @@ async def api_update_settings( data: UpdateSettings = Body(...), ): settings = await update_settings(data) - logger.debug(settings) return {"status": "Success", "settings": settings.dict()} From 1cc54ff4b7e19366499743fc9cb881bb58ccf2c6 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 12 Oct 2022 19:04:46 +0100 Subject: [PATCH 190/565] add funding sources options --- lnbits/app.py | 1 + lnbits/extensions/admin/models.py | 41 +++- .../admin/templates/admin/_tab_funding.html | 25 +- .../admin/templates/admin/index.html | 217 +++++++++++++++++- 4 files changed, 259 insertions(+), 25 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index a8371950..50f218b7 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -84,6 +84,7 @@ async def check_funding_source() -> None: def signal_handler(signal, frame): logger.debug(f"SIGINT received, terminating LNbits.") sys.exit(1) + signal.signal(signal.SIGINT, signal_handler) WALLET = get_wallet_class() diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 94fa56bb..d9d2b22f 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -6,10 +6,10 @@ from pydantic import BaseModel class UpdateSettings(BaseModel): lnbits_backend_wallet_class: str = Query(None) - lnbits_admin_users: List[str] = Query(None) - lnbits_allowed_users: List[str] = Query(None) - lnbits_admin_ext: List[str] = Query(None) - lnbits_disabled_ext: List[str] = Query(None) + lnbits_admin_users: List[str] = Query(None) + lnbits_allowed_users: List[str] = Query(None) + lnbits_admin_ext: List[str] = Query(None) + lnbits_disabled_ext: List[str] = Query(None) lnbits_funding_source: str = Query(None) lnbits_force_https: bool = Query(None) lnbits_reserve_fee_min: int = Query(None, ge=0) @@ -23,4 +23,35 @@ class UpdateSettings(BaseModel): lnbits_denomination: str = Query(None) lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) - lnbits_ad_space: List[str] = Query(None) + lnbits_ad_space: List[str] = Query(None) + + # funding sources + fake_wallet_secret: str = Query(None) + lnbits_endpoint: str = Query(None) + lnbits_key: str = Query(None) + cliche_endpoint: str = Query(None) + corelightning_rpc: str = Query(None) + eclair_url: str = Query(None) + eclair_pass: str = Query(None) + lnd_rest_endpoint: str = Query(None) + lnd_rest_cert: str = Query(None) + lnd_rest_macaroon: str = Query(None) + lnd_rest_macaroon_encrypted: str = Query(None) + lnd_cert: str = Query(None) + lnd_admin_macaroon: str = Query(None) + lnd_invoice_macaroon: str = Query(None) + lnd_grpc_endpoint: str = Query(None) + lnd_grpc_cert: str = Query(None) + lnd_grpc_port: int = Query(None, ge=0) + lnd_grpc_admin_macaroon: str = Query(None) + lnd_grpc_invoice_macaroon: str = Query(None) + lnd_grpc_macaroon_encrypted: str = Query(None) + lnpay_api_endpoint: str = Query(None) + lnpay_api_key: str = Query(None) + lnpay_wallet_key: str = Query(None) + lntxbot_api_endpoint: str = Query(None) + lntxbot_key: str = Query(None) + opennode_api_endpoint: str = Query(None) + opennode_key: str = Query(None) + spark_url: str = Query(None) + spark_token: str = Query(None) diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index 8b5456f1..a523d4e5 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -8,9 +8,7 @@

Funding Source Info

    {%raw%} -
  • - Funding Source: {{settings.lnbits_backend_wallet_class}} -
  • +
  • Funding Source: {{settings.lnbits_backend_wallet_class}}
  • Balance: {{balance / 1000}} sats
  • {%endraw%}
@@ -60,21 +58,30 @@
-

Funding Sources

+

Funding Sources (Requires server restart)

- + - - + diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index d8111595..ccaddda4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -3,7 +3,13 @@
- + u !== user - ) + this.settings.lnbits_admin_users = admin_users.filter(u => u !== user) }, addAllowedUser() { let addUser = this.formData.allowed_users_add @@ -155,7 +335,9 @@ }, removeAllowedUser(user) { let allowed_users = this.settings.lnbits_allowed_users - this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user) + this.settings.lnbits_allowed_users = allowed_users.filter( + u => u !== user + ) }, addAdSpace() { let adSpace = this.formData.ad_space_add @@ -208,8 +390,19 @@ LNbits.utils.notifyApiError(error) }) }, + updateFundingData(){ + this.settings.lnbits_allowed_funding_sources.map(f => { + let opts = this.funding_sources.get(f) + if (!opts) return + + Object.keys(opts).forEach(e => { + opts[e].value = this.settings[e] + }) + }) + console.log("funding", this.funding_sources) + }, updateSettings() { - let data = { + let data = { ...this.formData } LNbits.api @@ -222,11 +415,13 @@ .then(response => { this.settings = response.data.settings this.formData = _.clone(this.settings) + this.updateFundingData() this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', icon: null }) + console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -262,7 +457,7 @@ LNbits.utils.notifyApiError(error) }) } - }, + } }) {% endblock %} From 761fc427defc590913124f1ad2f1b518633fbad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 10 Oct 2022 23:27:46 +0200 Subject: [PATCH 191/565] add callback for saas app --- lnbits/app.py | 2 +- lnbits/extensions/admin/migrations.py | 3 +++ lnbits/extensions/admin/views_api.py | 5 ----- lnbits/settings.py | 32 +++++++++++++++++++++++---- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 50f218b7..49ad8d77 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -64,7 +64,7 @@ def create_app() -> FastAPI: # TODO: why those 2? g().config = settings - # g().base_url = f"http://{settings.host}:{settings.port}" + g().base_url = f"http://{settings.host}:{settings.port}" app.add_middleware(GZipMiddleware, minimum_size=1000) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index c4bc98d8..ea698c27 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,6 +6,9 @@ async def m001_create_admin_settings_table(db): debug TEXT, host TEXT, port INTEGER, + lnbits_saas_instance_id TEXT, + lnbits_saas_callback TEXT, + lnbits_saas_secret TEXT, lnbits_path TEXT, lnbits_commit TEXT, lnbits_admin_users TEXT, diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 19b52e35..ae2959bc 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -53,8 +53,3 @@ async def api_delete_settings( ): await delete_settings() return {"status": "Success"} - - -@admin_ext.get("/api/v1/backup/", status_code=HTTPStatus.OK) -async def api_backup(user: User = Depends(check_admin)): - return {"status": "not implemented"} diff --git a/lnbits/settings.py b/lnbits/settings.py index ffcdcc0a..f183211c 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,6 +1,7 @@ import importlib import json import subprocess +import httpx from os import path from sqlite3 import Row from typing import List, Optional @@ -9,6 +10,7 @@ from loguru import logger from pydantic import BaseSettings, Field, validator + def list_parse_fallback(v): try: return json.loads(v) @@ -34,6 +36,11 @@ class Settings(BaseSettings): lnbits_path: str = Field(default=".") lnbits_commit: str = Field(default="unknown") + # saas + lnbits_saas_callback: Optional[str] = Field(default=None) + lnbits_saas_secret: Optional[str] = Field(default=None) + lnbits_saas_instance_id: Optional[str] = Field(default=None) + # users lnbits_admin_users: List[str] = Field(default=[]) lnbits_allowed_users: List[str] = Field(default=[]) @@ -230,11 +237,28 @@ async def check_admin_settings(): http = "https" if settings.lnbits_force_https else "http" user = settings.lnbits_admin_users[0] - logger.warning( - f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}" - ) + + admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + logger.warning(f"✔️ Access admin user account at: {admin_url}") + + if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id: + with httpx.Client() as client: + headers = { + "Content-Type": "application/json; charset=utf-8", + "X-API-KEY": settings.lnbits_saas_secret + } + payload = { + "instance_id": settings.lnbits_saas_instance_id, + "adminuser": user + } + try: + r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload) + logger.warning("sent admin user to saas application") + except: + logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}") + except: - logger.warning("admin.settings tables does not exist.") + logger.error("admin.settings tables does not exist.") raise From 620fd2569655aa0f45b486201bcac75654f28326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 12 Oct 2022 13:08:59 +0200 Subject: [PATCH 192/565] bugfixes and fix topup wallet --- .../admin/templates/admin/index.html | 6 ++-- lnbits/extensions/admin/views_api.py | 36 ++++++++++--------- lnbits/server.py | 1 + lnbits/settings.py | 29 ++++++++++----- lnbits/wallets/fake.py | 2 +- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index ccaddda4..575b377f 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -369,10 +369,10 @@ topupWallet() { LNbits.api .request( - 'POST', + 'PUT', '/admin/api/v1/topup/?usr=' + this.g.user.id, - this.wallet.id, - this.wallet.amount + this.g.user.wallets[0].adminkey, + this.wallet ) .then(response => { this.$q.notify({ diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index ae2959bc..63ed5b3c 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,7 +1,6 @@ from http import HTTPStatus -from fastapi import Body, Depends, Request -from loguru import logger +from fastapi import Body, Depends, Query from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet @@ -9,47 +8,50 @@ from lnbits.core.models import User from lnbits.decorators import check_admin from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings -from lnbits.requestvars import g from lnbits.server import server_restart -from lnbits.settings import settings from .crud import delete_settings, update_settings, update_wallet_balance -@admin_ext.get("/api/v1/restart/", status_code=HTTPStatus.OK) -async def api_restart_server(user: User = Depends(check_admin)): +@admin_ext.get( + "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_restart_server() -> dict[str, str]: server_restart.set() return {"status": "Success"} -@admin_ext.put("/api/v1/topup/", status_code=HTTPStatus.OK) +@admin_ext.put( + "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_update_balance( - wallet_id, topup_amount: int, user: User = Depends(check_admin) -): + id: str = Body(...), amount: int = Body(...) +) -> dict[str, str]: try: - wallet = await get_wallet(wallet_id) + await get_wallet(id) except: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist." ) - await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount)) + await update_wallet_balance(wallet_id=id, amount=int(amount)) return {"status": "Success"} -@admin_ext.put("/api/v1/settings/", status_code=HTTPStatus.OK) +@admin_ext.put( + "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_update_settings( - user: User = Depends(check_admin), data: UpdateSettings = Body(...), ): settings = await update_settings(data) return {"status": "Success", "settings": settings.dict()} -@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK) -async def api_delete_settings( - user: User = Depends(check_admin), -): +@admin_ext.delete( + "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_delete_settings() -> dict[str, str]: await delete_settings() return {"status": "Success"} diff --git a/lnbits/server.py b/lnbits/server.py index 79af8112..6d4cd2e7 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -52,6 +52,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: port=port, host=host, reload=reload, + forwarded_allow_ips="*", ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, **d diff --git a/lnbits/settings.py b/lnbits/settings.py index f183211c..61dbd6f2 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -1,20 +1,19 @@ import importlib import json import subprocess -import httpx from os import path from sqlite3 import Row from typing import List, Optional +import httpx from loguru import logger from pydantic import BaseSettings, Field, validator - def list_parse_fallback(v): try: return json.loads(v) - except Exception as e: + except Exception: replaced = v.replace(" ", "") if replaced: return replaced.split(",") @@ -238,24 +237,36 @@ async def check_admin_settings(): http = "https" if settings.lnbits_force_https else "http" user = settings.lnbits_admin_users[0] - admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + admin_url = ( + f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + ) logger.warning(f"✔️ Access admin user account at: {admin_url}") - if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id: + if ( + settings.lnbits_saas_callback + and settings.lnbits_saas_secret + and settings.lnbits_saas_instance_id + ): with httpx.Client() as client: headers = { "Content-Type": "application/json; charset=utf-8", - "X-API-KEY": settings.lnbits_saas_secret + "X-API-KEY": settings.lnbits_saas_secret, } payload = { "instance_id": settings.lnbits_saas_instance_id, - "adminuser": user + "adminuser": user, } try: - r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload) + client.post( + settings.lnbits_saas_callback, + headers=headers, + json=payload, + ) logger.warning("sent admin user to saas application") except: - logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}") + logger.error( + f"error sending admin user to saas: {settings.lnbits_saas_callback}" + ) except: logger.error("admin.settings tables does not exist.") diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 73458e8c..94ff5f48 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -19,7 +19,6 @@ from .base import ( class FakeWallet(Wallet): - queue: asyncio.Queue = asyncio.Queue(0) secret: str = settings.fake_wallet_secret privkey: str = hashlib.pbkdf2_hmac( "sha256", @@ -98,6 +97,7 @@ class FakeWallet(Wallet): return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + self.queue: asyncio.Queue = asyncio.Queue(0) while True: value: Invoice = await self.queue.get() yield value.payment_hash From 9dbcd89c6ebdba4db6c0bc7d70d570a8ee5fffaa Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 13 Oct 2022 15:52:02 +0100 Subject: [PATCH 193/565] added tooltips, moved reset, and warnings --- .../admin/templates/admin/index.html | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 575b377f..7d268301 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -1,8 +1,14 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %}
-
- +
+ + Save your changes - - - + + + Restart the server for changes to take effect + + + + + Add funds to a wallet. + + > --> + + Delete all settings and reset to defaults. +
@@ -121,6 +136,7 @@ show: false }, tab: 'funding', + needsRestart: false, funding_sources: new Map([ ['VoidWallet', null], [ @@ -302,13 +318,12 @@ this.balance = +'{{ balance|safe }}' this.formData = _.clone(this.settings) //model this.updateFundingData() - console.log(this.settings) }, computed: { checkChanges() { return !_.isEqual(this.settings, this.formData) - }, + } }, methods: { addAdminUser() { @@ -361,6 +376,7 @@ message: 'Success! Restarted Server', icon: null }) + this.needsRestart = false }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -390,16 +406,15 @@ LNbits.utils.notifyApiError(error) }) }, - updateFundingData(){ + updateFundingData() { this.settings.lnbits_allowed_funding_sources.map(f => { let opts = this.funding_sources.get(f) if (!opts) return - + Object.keys(opts).forEach(e => { opts[e].value = this.settings[e] }) }) - console.log("funding", this.funding_sources) }, updateSettings() { let data = { @@ -415,31 +430,41 @@ .then(response => { this.settings = response.data.settings this.formData = _.clone(this.settings) + this.needsRestart = true this.updateFundingData() this.$q.notify({ type: 'positive', message: 'Success! Settings changed!', icon: null }) - console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) }, deleteSettings() { - LNbits.api - .request('DELETE', '/admin/api/v1/settings/?usr=' + this.g.user.id) - .then(response => { - this.$q.notify({ - type: 'positive', - message: - 'Success! Restored settings to defaults, restart required!', - icon: null - }) - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) + LNbits.utils + .confirmDialog( + 'Are you sure you want to restore settings to default?' + ) + .onOk(() => { + LNbits.api + .request( + 'DELETE', + '/admin/api/v1/settings/?usr=' + this.g.user.id + ) + .then(response => { + this.$q.notify({ + type: 'positive', + message: + 'Success! Restored settings to defaults, restart required!', + icon: null + }) + this.needsRestart = true + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) }) }, downloadBackup() { From 31a7a87774e5fcd87962440bc8c20510f607a328 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 13 Oct 2022 15:52:26 +0100 Subject: [PATCH 194/565] moved to columns --- .../admin/templates/admin/_tab_funding.html | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html index a523d4e5..a69ecb47 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_funding.html +++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html @@ -27,38 +27,38 @@ :options="settings.lnbits_allowed_funding_sources" >
-
-

Fee reserve

-
-
- -
-
- -
+
+
+
+
+

Fee reserve

+
+
+ + +
+
+
-
-
-
-

Funding Sources (Requires server restart)

+

+ Funding Sources (Requires server restart) +

Date: Thu, 13 Oct 2022 15:52:39 +0100 Subject: [PATCH 195/565] themes not displaying fixed --- lnbits/extensions/admin/templates/admin/_tab_theme.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html index 46bf83e9..c327733f 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_theme.html +++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html @@ -63,7 +63,7 @@

Themes

Date: Fri, 21 Oct 2022 10:00:47 +0200 Subject: [PATCH 196/565] add loop to uvicorn --- lnbits/server.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lnbits/server.py b/lnbits/server.py index 6d4cd2e7..eb7c12b1 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,12 +1,7 @@ -import asyncio - import uvloop - uvloop.install() -import contextlib import multiprocessing as mp -import sys import time import click @@ -49,6 +44,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: while True: config = uvicorn.Config( "lnbits.__main__:app", + loop="uvloop", port=port, host=host, reload=reload, @@ -65,9 +61,10 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload: server_restart.clear() server.should_exit = True server.force_exit = True + time.sleep(3) process.terminate() process.join() - time.sleep(3) + time.sleep(1) server_restart = mp.Event() From b14b9f3b3a48f5a2544864783de7129e8ae5bfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 21 Oct 2022 11:13:40 +0200 Subject: [PATCH 197/565] add get settings endpoint with only values you can also save --- lnbits/app.py | 6 +---- lnbits/extensions/admin/crud.py | 12 +++++++++- lnbits/extensions/admin/models.py | 6 ++++- .../admin/templates/admin/index.html | 22 +++++++++++++++---- lnbits/extensions/admin/views_api.py | 7 +++++- lnbits/server.py | 1 + 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 49ad8d77..959a8168 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -62,10 +62,6 @@ def create_app() -> FastAPI: CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ) - # TODO: why those 2? - g().config = settings - g().base_url = f"http://{settings.host}:{settings.port}" - app.add_middleware(GZipMiddleware, minimum_size=1000) register_startup(app) @@ -174,7 +170,7 @@ def register_assets(app: FastAPI): @app.on_event("startup") async def vendored_assets_variable(): - if g().config.debug: + if settings.debug: g().VENDORED_JS = map(url_for_vendored, get_js_vendored()) g().VENDORED_CSS = map(url_for_vendored, get_css_vendored()) else: diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index cc937b5e..2ce91612 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -6,7 +6,7 @@ from lnbits.settings import Settings, read_only_variables from lnbits.tasks import internal_invoice_queue from . import db -from .models import UpdateSettings +from .models import AdminSettings, UpdateSettings async def update_wallet_balance(wallet_id: str, amount: int) -> str: @@ -26,6 +26,16 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str: await internal_invoice_queue.put(internal_id) +async def get_settings() -> AdminSettings: + row = await db.fetchone("SELECT * FROM admin.settings") + all_settings = Settings(**row) + settings = AdminSettings() + for key, value in row.items(): + if hasattr(settings, key): + setattr(settings, key, getattr(all_settings, key)) + return settings + + async def update_settings(data: UpdateSettings) -> Settings: fields = [] for key, value in data.dict(exclude_none=True).items(): diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index d9d2b22f..31811659 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from fastapi import Query from pydantic import BaseModel @@ -55,3 +55,7 @@ class UpdateSettings(BaseModel): opennode_key: str = Query(None) spark_url: str = Query(None) spark_token: str = Query(None) + + +class AdminSettings(UpdateSettings): + lnbits_allowed_funding_sources: Optional[List[str]] diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 7d268301..10391261 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -314,11 +314,8 @@ } }, created: function () { - this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data + this.getSettings() this.balance = +'{{ balance|safe }}' - this.formData = _.clone(this.settings) //model - this.updateFundingData() - console.log(this.settings) }, computed: { checkChanges() { @@ -416,6 +413,23 @@ }) }) }, + getSettings() { + LNbits.api + .request( + 'GET', + '/admin/api/v1/settings/?usr=' + this.g.user.id, + this.g.user.wallets[0].adminkey + ) + .then(response => { + this.settings = response.data + this.formData = _.clone(this.settings) + this.updateFundingData() + console.log(this.settings) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, updateSettings() { let data = { ...this.formData diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 63ed5b3c..57d62ed4 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -10,7 +10,7 @@ from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings from lnbits.server import server_restart -from .crud import delete_settings, update_settings, update_wallet_balance +from .crud import delete_settings, get_settings, update_settings, update_wallet_balance @admin_ext.get( @@ -21,6 +21,11 @@ async def api_restart_server() -> dict[str, str]: return {"status": "Success"} +@admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)]) +async def api_get_settings() -> UpdateSettings: + return await get_settings() + + @admin_ext.put( "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) diff --git a/lnbits/server.py b/lnbits/server.py index eb7c12b1..ecf7ff62 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -1,4 +1,5 @@ import uvloop + uvloop.install() import multiprocessing as mp From 8fbf10909961c13b6c39008ae4ce3aaa750b9a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 25 Oct 2022 09:25:37 +0200 Subject: [PATCH 198/565] fix poetry lockCCC --- poetry.lock | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index 45968390..bbce3c5c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -258,24 +258,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "environs" -version = "9.3.3" -description = "simplified environment variable parsing" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -marshmallow = ">=3.0.0" -python-dotenv = "*" - -[package.extras] -dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] -django = ["dj-database-url", "dj-email-url", "django-cache-url"] -lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] - [[package]] name = "fastapi" version = "0.78.0" @@ -1051,7 +1033,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "1.1" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42" +content-hash = "e798b36b5941b43ee249bc196fcfb28d8ee712947336d21467651c672ba0106b" [metadata.files] aiofiles = [ @@ -1307,10 +1289,6 @@ enum34 = [ {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, ] -environs = [ - {file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"}, - {file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"}, -] fastapi = [ {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"}, {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"}, From d862598179c0daa469a7582a3c43c843ecf55120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 24 Nov 2022 11:35:03 +0100 Subject: [PATCH 199/565] readd global baseurl needed for lnurlp --- lnbits/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lnbits/app.py b/lnbits/app.py index 959a8168..3218f48a 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -58,6 +58,10 @@ def create_app() -> FastAPI: name="core_static", ) + # needed for lnurlw? + # g().config = settings + g().base_url = f"http://{settings.host}:{settings.port}" + app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ) From aefea9b8018101354d016feb13b139e590342921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 24 Nov 2022 14:53:11 +0100 Subject: [PATCH 200/565] mypy and formatting --- lnbits/extensions/admin/crud.py | 6 +++--- lnbits/extensions/admin/views.py | 2 +- lnbits/extensions/admin/views_api.py | 6 ++++-- lnbits/server.py | 6 +++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 2ce91612..7f9779b0 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash @@ -9,7 +9,7 @@ from . import db from .models import AdminSettings, UpdateSettings -async def update_wallet_balance(wallet_id: str, amount: int) -> str: +async def update_wallet_balance(wallet_id: str, amount: int) -> None: temp_id = f"temp_{urlsafe_short_hash()}" internal_id = f"internal_{urlsafe_short_hash()}" @@ -36,7 +36,7 @@ async def get_settings() -> AdminSettings: return settings -async def update_settings(data: UpdateSettings) -> Settings: +async def update_settings(data: UpdateSettings) -> Optional[Settings]: fields = [] for key, value in data.dict(exclude_none=True).items(): if not key in read_only_variables: diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py index aa933017..b1d68026 100644 --- a/lnbits/extensions/admin/views.py +++ b/lnbits/extensions/admin/views.py @@ -17,7 +17,7 @@ templates = Jinja2Templates(directory="templates") @admin_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_admin)): +async def index(request: Request, user: User = Depends(check_admin)): # type: ignore WALLET = get_wallet_class() error, balance = await WALLET.status() diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 57d62ed4..eb1eff80 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -1,6 +1,7 @@ from http import HTTPStatus -from fastapi import Body, Depends, Query +from fastapi import Body +from fastapi.params import Depends from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet @@ -51,7 +52,8 @@ async def api_update_settings( data: UpdateSettings = Body(...), ): settings = await update_settings(data) - return {"status": "Success", "settings": settings.dict()} + if settings: + return {"status": "Success", "settings": settings.dict()} @admin_ext.delete( diff --git a/lnbits/server.py b/lnbits/server.py index 3d209961..70b15868 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -19,7 +19,11 @@ from lnbits.settings import set_cli_settings, settings ) @click.option("--port", default=settings.port, help="Port to listen on") @click.option("--host", default=settings.host, help="Host to run LNBits on") -@click.option("--forwarded-allow-ips", default=settings.forwarded_allow_ips, help="Allowed proxy servers") +@click.option( + "--forwarded-allow-ips", + default=settings.forwarded_allow_ips, + help="Allowed proxy servers", +) @click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile") @click.option("--ssl-certfile", default=None, help="Path to SSL certificate") @click.pass_context From acc27a471415570da08bd007e3718545187cf2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 24 Nov 2022 15:43:15 +0100 Subject: [PATCH 201/565] fix lndgrpc regtest errors --- lnbits/extensions/admin/migrations.py | 1 + lnbits/extensions/admin/models.py | 1 + lnbits/settings.py | 1 + 3 files changed, 3 insertions(+) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index ea698c27..2a9424e6 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -47,6 +47,7 @@ async def m001_create_admin_settings_table(db): lnd_grpc_port INTEGER, lnd_grpc_admin_macaroon TEXT, lnd_grpc_invoice_macaroon TEXT, + lnd_grpc_macaroon TEXT, lnd_grpc_macaroon_encrypted TEXT, lnpay_api_endpoint TEXT, lnpay_api_key TEXT, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 31811659..7440fae7 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -45,6 +45,7 @@ class UpdateSettings(BaseModel): lnd_grpc_port: int = Query(None, ge=0) lnd_grpc_admin_macaroon: str = Query(None) lnd_grpc_invoice_macaroon: str = Query(None) + lnd_grpc_macaroon: str = Query(None) lnd_grpc_macaroon_encrypted: str = Query(None) lnpay_api_endpoint: str = Query(None) lnpay_api_key: str = Query(None) diff --git a/lnbits/settings.py b/lnbits/settings.py index 0e58f8c7..c3356938 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -102,6 +102,7 @@ class Settings(BaseSettings): lnd_grpc_port: Optional[int] = Field(default=None) lnd_grpc_admin_macaroon: Optional[str] = Field(default=None) lnd_grpc_invoice_macaroon: Optional[str] = Field(default=None) + lnd_grpc_macaroon: Optional[str] = Field(default=None) lnd_grpc_macaroon_encrypted: Optional[str] = Field(default=None) lnpay_api_endpoint: Optional[str] = Field(default=None) lnpay_api_key: Optional[str] = Field(default=None) From b49545f48bab1b7f3ebe360d9f3cc31daadf0a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 29 Nov 2022 20:29:08 +0100 Subject: [PATCH 202/565] exlude forwarded_allow_ips --- lnbits/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/settings.py b/lnbits/settings.py index c3356938..67a1ca5a 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -21,7 +21,7 @@ def list_parse_fallback(v): return [] -read_only_variables = ["host", "port", "lnbits_commit", "lnbits_path"] +read_only_variables = ["host", "port", "lnbits_commit", "lnbits_path", "forwarded_allow_ips"] class Settings(BaseSettings): From 8b86936b079b403f4c92429b87cfdd16acd21522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 29 Nov 2022 20:30:22 +0100 Subject: [PATCH 203/565] exlude forwarded_allow_ips --- lnbits/extensions/admin/migrations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index 2a9424e6..ddf4d4dd 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -6,6 +6,7 @@ async def m001_create_admin_settings_table(db): debug TEXT, host TEXT, port INTEGER, + forwarded_allow_ips TEXT, lnbits_saas_instance_id TEXT, lnbits_saas_callback TEXT, lnbits_saas_secret TEXT, From e740ad3cf8a5ccd5d20d6f70533136840bc41f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 30 Nov 2022 16:53:46 +0100 Subject: [PATCH 204/565] fix lnbitswallet issue, add ad_space_title, change lntips funding source --- lnbits/extensions/admin/migrations.py | 7 ++++++- lnbits/extensions/admin/models.py | 5 +++++ lnbits/extensions/admin/views_api.py | 4 +++- lnbits/settings.py | 13 ++++++++++++- lnbits/wallets/lntips.py | 11 ++++++----- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index ddf4d4dd..45480a75 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -24,6 +24,7 @@ async def m001_create_admin_settings_table(db): lnbits_theme_options TEXT, lnbits_custom_logo TEXT, lnbits_ad_space TEXT, + lnbits_ad_space_title TEXT, lnbits_data_folder TEXT, lnbits_database_url TEXT, lnbits_force_https TEXT, @@ -62,7 +63,11 @@ async def m001_create_admin_settings_table(db): boltz_network TEXT, boltz_url TEXT, boltz_mempool_space_url TEXT, - boltz_mempool_space_url_ws TEXT + boltz_mempool_space_url_ws TEXT, + lntips_api_endpoint TEXT, + lntips_api_key TEXT, + lntips_admin_key TEXT, + lntips_invoice_key TEXT ); """ ) diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 7440fae7..dc665531 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -24,6 +24,7 @@ class UpdateSettings(BaseModel): lnbits_theme: str = Query(None) lnbits_custom_logo: str = Query(None) lnbits_ad_space: List[str] = Query(None) + lnbits_ad_space_title: str = Query(None) # funding sources fake_wallet_secret: str = Query(None) @@ -56,6 +57,10 @@ class UpdateSettings(BaseModel): opennode_key: str = Query(None) spark_url: str = Query(None) spark_token: str = Query(None) + lntips_api_endpoint: str = Query(None) + lntips_api_key: str = Query(None) + lntips_admin_key: str = Query(None) + lntips_invoice_key: str = Query(None) class AdminSettings(UpdateSettings): diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index eb1eff80..dfd6497e 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -5,7 +5,6 @@ from fastapi.params import Depends from starlette.exceptions import HTTPException from lnbits.core.crud import get_wallet -from lnbits.core.models import User from lnbits.decorators import check_admin from lnbits.extensions.admin import admin_ext from lnbits.extensions.admin.models import UpdateSettings @@ -13,6 +12,8 @@ from lnbits.server import server_restart from .crud import delete_settings, get_settings, update_settings, update_wallet_balance +from lnbits.settings import settings, set_settings + @admin_ext.get( "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] @@ -52,6 +53,7 @@ async def api_update_settings( data: UpdateSettings = Body(...), ): settings = await update_settings(data) + set_settings(settings) if settings: return {"status": "Success", "settings": settings.dict()} diff --git a/lnbits/settings.py b/lnbits/settings.py index 67a1ca5a..f84cfb4b 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -57,6 +57,7 @@ class Settings(BaseSettings): ) lnbits_custom_logo: str = Field(default=None) lnbits_ad_space: List[str] = Field(default=[]) + lnbits_ad_space_title: str = Field(default="") # ops lnbits_data_folder: str = Field(default="./data") @@ -79,8 +80,9 @@ class Settings(BaseSettings): "LndWallet", "LntxbotWallet", "LNPayWallet", - "LnbitsWallet", + "LNbitsWallet", "OpenNodeWallet", + "LnTipsWallet", ] ) fake_wallet_secret: str = Field(default="ToTheMoon1") @@ -113,6 +115,10 @@ class Settings(BaseSettings): opennode_key: Optional[str] = Field(default=None) spark_url: Optional[str] = Field(default=None) spark_token: Optional[str] = Field(default=None) + lntips_api_endpoint: Optional[str] = Field(default=None) + lntips_api_key: Optional[str] = Field(default=None) + lntips_admin_key: Optional[str] = Field(default=None) + lntips_invoice_key: Optional[str] = Field(default=None) # boltz boltz_network: str = Field(default="main") @@ -170,6 +176,11 @@ if not settings.lnbits_admin_ui: logger.debug(f"{key}: {value}") +def set_settings(**kwargs): + for key, value in kwargs.items(): + setattr(settings, key, value) + + def set_cli_settings(**kwargs): for key, value in kwargs.items(): setattr(settings, key, value) diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py index 54220c85..9dd43437 100644 --- a/lnbits/wallets/lntips.py +++ b/lnbits/wallets/lntips.py @@ -2,7 +2,6 @@ import asyncio import hashlib import json import time -from os import getenv from typing import AsyncGenerator, Dict, Optional import httpx @@ -16,16 +15,18 @@ from .base import ( Wallet, ) +from lnbits.settings import settings + class LnTipsWallet(Wallet): def __init__(self): - endpoint = getenv("LNTIPS_API_ENDPOINT") + endpoint = settings.lntips_api_endpoint self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint key = ( - getenv("LNTIPS_API_KEY") - or getenv("LNTIPS_ADMIN_KEY") - or getenv("LNTIPS_INVOICE_KEY") + settings.lntips_api_key + or settings.lntips_admin_key + or settings.lntips_invoice_key ) self.auth = {"Authorization": f"Basic {key}"} From 86d2780c9f57df5dd9d4c59c554e3b923704c9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 30 Nov 2022 23:47:52 +0100 Subject: [PATCH 205/565] fix template typos and also update settings in memory after a admin PUT --- lnbits/core/crud.py | 4 ++-- lnbits/extensions/admin/crud.py | 10 ++++++---- .../extensions/admin/templates/admin/_tab_users.html | 8 +++----- lnbits/extensions/admin/templates/admin/index.html | 2 +- lnbits/extensions/admin/views_api.py | 2 -- lnbits/settings.py | 5 ----- 6 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index eb0d64de..6e498c60 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -232,8 +232,8 @@ async def get_wallet_payment( async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5): rows = await db.fetchall( f""" - SELECT * FROM apipayments - WHERE pending = 'false' + SELECT * FROM apipayments + WHERE pending = 'false' AND extra LIKE ? AND extra LIKE ? ORDER BY time DESC LIMIT {limit} diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 7f9779b0..c6c5a31a 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash @@ -7,10 +7,10 @@ from lnbits.tasks import internal_invoice_queue from . import db from .models import AdminSettings, UpdateSettings +from lnbits.settings import settings -async def update_wallet_balance(wallet_id: str, amount: int) -> None: - temp_id = f"temp_{urlsafe_short_hash()}" +async def update_wallet_balance(wallet_id: str, amount: int): internal_id = f"internal_{urlsafe_short_hash()}" payment = await create_payment( @@ -25,6 +25,8 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> None: # manually send this for now await internal_invoice_queue.put(internal_id) + return payment + async def get_settings() -> AdminSettings: row = await db.fetchone("SELECT * FROM admin.settings") @@ -39,6 +41,7 @@ async def get_settings() -> AdminSettings: async def update_settings(data: UpdateSettings) -> Optional[Settings]: fields = [] for key, value in data.dict(exclude_none=True).items(): + setattr(settings, key, value) if not key in read_only_variables: if type(value) == list: joined = ",".join(value) @@ -52,7 +55,6 @@ async def update_settings(data: UpdateSettings) -> Optional[Settings]: fields.append(f"{key} = '{value}'") q = ", ".join(fields) - print("UPDATE", q) await db.execute(f"UPDATE admin.settings SET {q}") row = await db.fetchone("SELECT * FROM admin.settings") assert row, "Newly updated settings couldn't be retrieved" diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html index 08b08a62..b0320fda 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_users.html +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -3,7 +3,7 @@
User Management

- Super Admin: {% raw %}{{settings.lnbits_admin_users[0]}}{% endraw %} + Super Admin: {{ settings.lnbits_admin_users[0] }}


@@ -19,18 +19,16 @@
- {% raw %} - {{ user }} + {{ user.id }} - {% endraw %}

diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index 10391261..afe63cb4 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -263,7 +263,7 @@ } ], [ - 'LnbitsWallet', + 'LNbitsWallet', { lnbits_endpoint: { value: null, diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index dfd6497e..0dedb9fc 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -12,7 +12,6 @@ from lnbits.server import server_restart from .crud import delete_settings, get_settings, update_settings, update_wallet_balance -from lnbits.settings import settings, set_settings @admin_ext.get( @@ -53,7 +52,6 @@ async def api_update_settings( data: UpdateSettings = Body(...), ): settings = await update_settings(data) - set_settings(settings) if settings: return {"status": "Success", "settings": settings.dict()} diff --git a/lnbits/settings.py b/lnbits/settings.py index f84cfb4b..4fb86846 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -176,11 +176,6 @@ if not settings.lnbits_admin_ui: logger.debug(f"{key}: {value}") -def set_settings(**kwargs): - for key, value in kwargs.items(): - setattr(settings, key, value) - - def set_cli_settings(**kwargs): for key, value in kwargs.items(): setattr(settings, key, value) From e3e62fc07303fe6ea27faa38ef32997fb7c0e550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 30 Nov 2022 23:52:13 +0100 Subject: [PATCH 206/565] formatting --- lnbits/extensions/admin/crud.py | 3 +-- lnbits/extensions/admin/templates/admin/_tab_users.html | 4 +--- lnbits/extensions/admin/views_api.py | 1 - lnbits/settings.py | 8 +++++++- lnbits/wallets/lntips.py | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index c6c5a31a..e75854e0 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -2,12 +2,11 @@ from typing import Optional from lnbits.core.crud import create_payment from lnbits.helpers import urlsafe_short_hash -from lnbits.settings import Settings, read_only_variables +from lnbits.settings import Settings, read_only_variables, settings from lnbits.tasks import internal_invoice_queue from . import db from .models import AdminSettings, UpdateSettings -from lnbits.settings import settings async def update_wallet_balance(wallet_id: str, amount: int): diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html index b0320fda..4ae3f315 100644 --- a/lnbits/extensions/admin/templates/admin/_tab_users.html +++ b/lnbits/extensions/admin/templates/admin/_tab_users.html @@ -2,9 +2,7 @@
User Management

-

- Super Admin: {{ settings.lnbits_admin_users[0] }} -

+

Super Admin: {{ settings.lnbits_admin_users[0] }}


Admin Users

diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py index 0dedb9fc..2d64be49 100644 --- a/lnbits/extensions/admin/views_api.py +++ b/lnbits/extensions/admin/views_api.py @@ -13,7 +13,6 @@ from lnbits.server import server_restart from .crud import delete_settings, get_settings, update_settings, update_wallet_balance - @admin_ext.get( "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) diff --git a/lnbits/settings.py b/lnbits/settings.py index 4fb86846..edaab7d9 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -21,7 +21,13 @@ def list_parse_fallback(v): return [] -read_only_variables = ["host", "port", "lnbits_commit", "lnbits_path", "forwarded_allow_ips"] +read_only_variables = [ + "host", + "port", + "lnbits_commit", + "lnbits_path", + "forwarded_allow_ips", +] class Settings(BaseSettings): diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py index 9dd43437..f0d08cf1 100644 --- a/lnbits/wallets/lntips.py +++ b/lnbits/wallets/lntips.py @@ -7,6 +7,8 @@ from typing import AsyncGenerator, Dict, Optional import httpx from loguru import logger +from lnbits.settings import settings + from .base import ( InvoiceResponse, PaymentResponse, @@ -15,8 +17,6 @@ from .base import ( Wallet, ) -from lnbits.settings import settings - class LnTipsWallet(Wallet): def __init__(self): From dade3d19379160c2cecf1b1b187b64bef99909af Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 1 Dec 2022 10:01:16 +0000 Subject: [PATCH 207/565] add LnTipsWallet --- lnbits/extensions/admin/templates/admin/index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index afe63cb4..bb768511 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -309,6 +309,19 @@ label: 'Token' } } + ], + [ + 'LnTipsWallet', + { + lntips_api_endpoint: { + value: null, + label: 'Endpoint' + }, + lntips_api_key: { + value: null, + label: 'API Key' + } + } ] ]) } From eca123b3f8464e26ddbfa136d592194012c94834 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 1 Dec 2022 10:57:42 +0000 Subject: [PATCH 208/565] only ask for restart on funding changes --- lnbits/extensions/admin/templates/admin/index.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html index bb768511..ba8b49c0 100644 --- a/lnbits/extensions/admin/templates/admin/index.html +++ b/lnbits/extensions/admin/templates/admin/index.html @@ -437,7 +437,6 @@ this.settings = response.data this.formData = _.clone(this.settings) this.updateFundingData() - console.log(this.settings) }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -455,9 +454,11 @@ data ) .then(response => { + this.needsRestart = + this.settings.lnbits_backend_wallet_class !== + response.data.settings.lnbits_backend_wallet_class this.settings = response.data.settings this.formData = _.clone(this.settings) - this.needsRestart = true this.updateFundingData() this.$q.notify({ type: 'positive', From 93cc79e0c677e97e8e2d1600af7c7665cdeafc45 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 15 Sep 2022 18:11:59 +0100 Subject: [PATCH 209/565] rebuild pages From e54eb535a9cb3509e2c0e6c42196baef2c539b6e Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 16 Sep 2022 13:20:42 +0100 Subject: [PATCH 210/565] UI works well --- lnbits/extensions/cashu/README.md | 11 + lnbits/extensions/cashu/__init__.py | 25 + lnbits/extensions/cashu/config.json | 6 + lnbits/extensions/cashu/crud.py | 50 ++ lnbits/extensions/cashu/migrations.py | 33 + lnbits/extensions/cashu/models.py | 34 + lnbits/extensions/cashu/tasks.py | 70 ++ .../cashu/templates/cashu/_api_docs.html | 79 ++ .../cashu/templates/cashu/_cashu.html | 15 + .../cashu/templates/cashu/index.html | 262 ++++++ .../cashu/templates/cashu/mint.html | 33 + .../cashu/templates/cashu/wallet.html | 753 ++++++++++++++++++ lnbits/extensions/cashu/views.py | 69 ++ lnbits/extensions/cashu/views_api.py | 160 ++++ 14 files changed, 1600 insertions(+) create mode 100644 lnbits/extensions/cashu/README.md create mode 100644 lnbits/extensions/cashu/__init__.py create mode 100644 lnbits/extensions/cashu/config.json create mode 100644 lnbits/extensions/cashu/crud.py create mode 100644 lnbits/extensions/cashu/migrations.py create mode 100644 lnbits/extensions/cashu/models.py create mode 100644 lnbits/extensions/cashu/tasks.py create mode 100644 lnbits/extensions/cashu/templates/cashu/_api_docs.html create mode 100644 lnbits/extensions/cashu/templates/cashu/_cashu.html create mode 100644 lnbits/extensions/cashu/templates/cashu/index.html create mode 100644 lnbits/extensions/cashu/templates/cashu/mint.html create mode 100644 lnbits/extensions/cashu/templates/cashu/wallet.html create mode 100644 lnbits/extensions/cashu/views.py create mode 100644 lnbits/extensions/cashu/views_api.py diff --git a/lnbits/extensions/cashu/README.md b/lnbits/extensions/cashu/README.md new file mode 100644 index 00000000..8f53b474 --- /dev/null +++ b/lnbits/extensions/cashu/README.md @@ -0,0 +1,11 @@ +# Cashu + +## Create ecash mint for pegging in/out of ecash + + + +### Usage + +1. Enable extension +2. Create a Mint +3. Share wallet diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py new file mode 100644 index 00000000..fa549ad2 --- /dev/null +++ b/lnbits/extensions/cashu/__init__.py @@ -0,0 +1,25 @@ +import asyncio + +from fastapi import APIRouter + +from lnbits.db import Database +from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart + +db = Database("ext_cashu") + +cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"]) + + +def cashu_renderer(): + return template_renderer(["lnbits/extensions/cashu/templates"]) + + +from .tasks import wait_for_paid_invoices +from .views import * # noqa +from .views_api import * # noqa + + +def cashu_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json new file mode 100644 index 00000000..c688b22c --- /dev/null +++ b/lnbits/extensions/cashu/config.json @@ -0,0 +1,6 @@ +{ + "name": "Cashu Ecash", + "short_description": "Ecash mints with LN peg in/out", + "icon": "approval", + "contributors": ["shinobi", "arcbtc", "calle"] +} diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py new file mode 100644 index 00000000..ce83653f --- /dev/null +++ b/lnbits/extensions/cashu/crud.py @@ -0,0 +1,50 @@ +from typing import List, Optional, Union + +from lnbits.helpers import urlsafe_short_hash + +from . import db +from .models import Cashu, Pegs + + +async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: + cashu_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + cashu_id, + wallet_id, + data.name, + data.tickershort, + data.fraction, + data.maxsats, + data.coins + ), + ) + + cashu = await get_cashu(cashu_id) + assert cashu, "Newly created cashu couldn't be retrieved" + return cashu + + +async def get_cashu(cashu_id: str) -> Optional[Cashu]: + row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) + return Cashu(**row) if row else None + + +async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM cashu.cashu WHERE wallet IN ({q})", (*wallet_ids,) + ) + + return [Cashu(**row) for row in rows] + + +async def delete_cashu(cashu_id: str) -> None: + await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,)) diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py new file mode 100644 index 00000000..95dc4815 --- /dev/null +++ b/lnbits/extensions/cashu/migrations.py @@ -0,0 +1,33 @@ +async def m001_initial(db): + """ + Initial cashu table. + """ + await db.execute( + """ + CREATE TABLE cashu.cashu ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + name TEXT NOT NULL, + tickershort TEXT NOT NULL, + fraction BOOL, + maxsats INT, + coins INT + + ); + """ + ) + + """ + Initial cashus table. + """ + await db.execute( + """ + CREATE TABLE cashu.pegs ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + inout BOOL NOT NULL, + amount INT + ); + """ + ) + diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py new file mode 100644 index 00000000..0de15362 --- /dev/null +++ b/lnbits/extensions/cashu/models.py @@ -0,0 +1,34 @@ +from sqlite3 import Row +from typing import Optional + +from fastapi import Query +from pydantic import BaseModel + + +class Cashu(BaseModel): + id: str = Query(None) + name: str = Query(None) + wallet: str = Query(None) + tickershort: str + fraction: bool = Query(None) + maxsats: int = Query(0) + coins: int = Query(0) + + + @classmethod + def from_row(cls, row: Row) -> "TPoS": + return cls(**dict(row)) + +class Pegs(BaseModel): + id: str + wallet: str + inout: str + amount: str + + + @classmethod + def from_row(cls, row: Row) -> "TPoS": + return cls(**dict(row)) + +class PayLnurlWData(BaseModel): + lnurl: str \ No newline at end of file diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py new file mode 100644 index 00000000..fe00a591 --- /dev/null +++ b/lnbits/extensions/cashu/tasks.py @@ -0,0 +1,70 @@ +import asyncio +import json + +from lnbits.core import db as core_db +from lnbits.core.crud import create_payment +from lnbits.core.models import Payment +from lnbits.helpers import urlsafe_short_hash +from lnbits.tasks import internal_invoice_queue, register_invoice_listener + +from .crud import get_cashu + + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue) + + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + if payment.extra.get("tag") == "cashu" and payment.extra.get("tipSplitted"): + # already splitted, ignore + return + + # now we make some special internal transfers (from no one to the receiver) + cashu = await get_cashu(payment.extra.get("cashuId")) + tipAmount = payment.extra.get("tipAmount") + + if tipAmount is None: + # no tip amount + return + + tipAmount = tipAmount * 1000 + amount = payment.amount - tipAmount + + # mark the original payment with one extra key, "splitted" + # (this prevents us from doing this process again and it's informative) + # and reduce it by the amount we're going to send to the producer + await core_db.execute( + """ + UPDATE apipayments + SET extra = ?, amount = ? + WHERE hash = ? + AND checking_id NOT LIKE 'internal_%' + """, + ( + json.dumps(dict(**payment.extra, tipSplitted=True)), + amount, + payment.payment_hash, + ), + ) + + # perform the internal transfer using the same payment_hash + internal_checking_id = f"internal_{urlsafe_short_hash()}" + await create_payment( + wallet_id=cashu.tip_wallet, + checking_id=internal_checking_id, + payment_request="", + payment_hash=payment.payment_hash, + amount=tipAmount, + memo=f"Tip for {payment.memo}", + pending=False, + extra={"tipSplitted": True}, + ) + + # manually send this for now + await internal_invoice_queue.put(internal_checking_id) + return diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html new file mode 100644 index 00000000..7378eb08 --- /dev/null +++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html @@ -0,0 +1,79 @@ + + + + + + GET /cashu/api/v1/cashus +
Headers
+ {"X-Api-Key": <invoice_key>}
+
Body (application/json)
+
+ Returns 200 OK (application/json) +
+ [<cashu_object>, ...] +
Curl example
+ curl -X GET {{ request.base_url }}cashu/api/v1/cashus -H "X-Api-Key: + <invoice_key>" + +
+
+
+ + + + POST /cashu/api/v1/cashus +
Headers
+ {"X-Api-Key": <invoice_key>}
+
Body (application/json)
+ {"name": <string>, "currency": <string*ie USD*>} +
+ Returns 201 CREATED (application/json) +
+ {"currency": <string>, "id": <string>, "name": + <string>, "wallet": <string>} +
Curl example
+ curl -X POST {{ request.base_url }}cashu/api/v1/cashus -d '{"name": + <string>, "currency": <string>}' -H "Content-type: + application/json" -H "X-Api-Key: <admin_key>" + +
+
+
+ + + + + DELETE + /cashu/api/v1/cashus/<cashu_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Returns 204 NO CONTENT
+ +
Curl example
+ curl -X DELETE {{ request.base_url + }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: <admin_key>" + +
+
+
+
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html new file mode 100644 index 00000000..3c2a38f5 --- /dev/null +++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html @@ -0,0 +1,15 @@ + + + +

+ Make Ecash mints with peg in/out to a wallet, that can create and manage ecash. +

+ Created by + Calle. +
+
+
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html new file mode 100644 index 00000000..17b2a919 --- /dev/null +++ b/lnbits/extensions/cashu/templates/cashu/index.html @@ -0,0 +1,262 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + + New Mint + + + + + +
+
+
Mints
+
+
+ Export to CSV +
+
+ + {% raw %} + + + + {% endraw %} + +
+
+
+ +
+ + +
{{SITE_TITLE}} Cashu extension
+
+ + + + {% include "cashu/_api_docs.html" %} + + {% include "cashu/_cashu.html" %} + + +
+
+ + + + + + + + +
+ +
+
+ + Use with hedging extension to create a stablecoin! + +
+
+ +
+
+ + +
+
+ Create Mint + + Cancel +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} \ No newline at end of file diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html new file mode 100644 index 00000000..0f3e0e09 --- /dev/null +++ b/lnbits/extensions/cashu/templates/cashu/mint.html @@ -0,0 +1,33 @@ +{% extends "public.html" %} {% block page %} +
+
+ + +
+ +

{{ mint_name }}

+
+
+
Some data about mint here:
* whether its online
* Who to contact for support
* etc...
+
+
+
+ + {% endblock %} {% block scripts %} + + + + {% endblock %} +
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html new file mode 100644 index 00000000..a5d5f371 --- /dev/null +++ b/lnbits/extensions/cashu/templates/cashu/wallet.html @@ -0,0 +1,753 @@ +{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet {% endraw %} + +{% endblock %} {% block footer %}{% endblock %} {% block page_container %} + + +
+ + +
+ + +

+
{% raw %} {{balanceAmount}} + {{tickershort}}{% endraw %}
+

+
+ +
+ + +
+
+ Receive +
+
+ Send +
+
+ Peg in/out + +
+
+ scan + +
+ +
+ + + + +
+
+
Transactions
+
+
{% raw %} + {% endraw %} + Mint details + + Export to CSV + + + Show chart + +
+
+ + + + {% raw %} + + + {% endraw %} + +
+
+
+ + + {% raw %} + + +

+ {{receive.lnurl.domain}} is requesting an invoice: +

+ {% endraw %} {% if LNBITS_DENOMINATION != 'sats' %} + + {% else %} + + + {% endif %} + + + {% raw %} +
+ + + Withdraw from {{receive.lnurl.domain}} + + Create invoice + + Cancel +
+ +
+
+ + +
+ Copy invoice + Close +
+
+ {% endraw %} +
+ + + +
+
+ {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",", + "")) / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %} +
+
+ {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {% + raw %} +
+ +

+ Description: {{ parse.invoice.description }}
+ Expire date: {{ parse.invoice.expireDate }}
+ Hash: {{ parse.invoice.hash }} +

+ {% endraw %} +
+ Pay + Cancel +
+
+ Not enough funds! + Cancel +
+
+
+ {% raw %} + +

+ Authenticate with {{ parse.lnurlauth.domain }}? +

+ +

+ For every website and for every LNbits wallet, a new keypair + will be deterministically generated so your identity can't be + tied to your LNbits wallet or linked across websites. No other + data will be shared with {{ parse.lnurlauth.domain }}. +

+

Your public key for {{ parse.lnurlauth.domain }} is:

+

+ {{ parse.lnurlauth.pubkey }} +

+
+ Login + Cancel +
+
+ {% endraw %} +
+
+ + + +
+ Read + Cancel +
+
+
+ + + +
+ + Cancel + +
+
+
+
+
+ + + +
+ +
+
+ Cancel +
+
+
+ + + + + + + + + + + + + + + + + + +
Warning
+

+ BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots + and "Save to homescreen"/"Install app"! +

+

+ Ecash is a bearer asset, meaning you have the funds saved on this + page, losing the page without exporting the page will mean you will + lose the funds. +

+
+ Copy wallet URL + I understand +
+
+
+
+
+
+{% endblock %} {% block styles %} + +{% endblock %} {% block scripts %} + +{% endblock %} diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py new file mode 100644 index 00000000..4ac1f1ce --- /dev/null +++ b/lnbits/extensions/cashu/views.py @@ -0,0 +1,69 @@ +from http import HTTPStatus + +from fastapi import Request +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.exceptions import HTTPException +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists +from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE + +from . import cashu_ext, cashu_renderer +from .crud import get_cashu + +templates = Jinja2Templates(directory="templates") + + +@cashu_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): + return cashu_renderer().TemplateResponse( + "cashu/index.html", {"request": request, "user": user.dict()} + ) + + +@cashu_ext.get("/wallet") +async def cashu(request: Request): + return cashu_renderer().TemplateResponse("cashu/wallet.html",{"request": request}) + +@cashu_ext.get("/mint/{mintID}") +async def cashu(request: Request, mintID): + cashu = await get_cashu(mintID) + return cashu_renderer().TemplateResponse("cashu/mint.html",{"request": request, "mint_name": cashu.name}) + +@cashu_ext.get("/manifest/{cashu_id}.webmanifest") +async def manifest(cashu_id: str): + cashu = await get_cashu(cashu_id) + if not cashu: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." + ) + + return { + "short_name": LNBITS_SITE_TITLE, + "name": cashu.name + " - " + LNBITS_SITE_TITLE, + "icons": [ + { + "src": LNBITS_CUSTOM_LOGO + if LNBITS_CUSTOM_LOGO + else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", + "type": "image/png", + "sizes": "900x900", + } + ], + "start_url": "/cashu/" + cashu_id, + "background_color": "#1F2234", + "description": "Bitcoin Lightning tPOS", + "display": "standalone", + "scope": "/cashu/" + cashu_id, + "theme_color": "#1F2234", + "shortcuts": [ + { + "name": cashu.name + " - " + LNBITS_SITE_TITLE, + "short_name": cashu.name, + "description": cashu.name + " - " + LNBITS_SITE_TITLE, + "url": "/cashu/" + cashu_id, + } + ], + } diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py new file mode 100644 index 00000000..0b16e4ba --- /dev/null +++ b/lnbits/extensions/cashu/views_api.py @@ -0,0 +1,160 @@ +from http import HTTPStatus + +import httpx +from fastapi import Query +from fastapi.params import Depends +from lnurl import decode as decode_lnurl +from loguru import logger +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_user +from lnbits.core.services import create_invoice +from lnbits.core.views.api import api_payment +from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key + +from . import cashu_ext +from .crud import create_cashu, delete_cashu, get_cashu, get_cashus +from .models import Cashu, Pegs, PayLnurlWData + + +@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK) +async def api_cashus( + all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) +): + wallet_ids = [wallet.wallet.id] + if all_wallets: + wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids + + return [cashu.dict() for cashu in await get_cashus(wallet_ids)] + + +@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED) +async def api_cashu_create( + data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type) +): + cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data) + logger.debug(cashu) + return cashu.dict() + + +@cashu_ext.delete("/api/v1/cashus/{cashu_id}") +async def api_cashu_delete( + cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) +): + cashu = await get_cashu(cashu_id) + + if not cashu: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." + ) + + if cashu.wallet != wallet.wallet.id: + raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your TPoS.") + + await delete_cashu(cashu_id) + raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + + +@cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED) +async def api_cashu_create_invoice( + amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None +): + cashu = await get_cashu(cashu_id) + + if not cashu: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." + ) + + if tipAmount: + amount += tipAmount + + try: + payment_hash, payment_request = await create_invoice( + wallet_id=cashu.wallet, + amount=amount, + memo=f"{cashu.name}", + extra={"tag": "cashu", "tipAmount": tipAmount, "cashuId": cashu_id}, + ) + except Exception as e: + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) + + return {"payment_hash": payment_hash, "payment_request": payment_request} + + +@cashu_ext.post( + "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay", status_code=HTTPStatus.OK +) +async def api_cashu_pay_invoice( + lnurl_data: PayLnurlWData, payment_request: str = None, cashu_id: str = None +): + cashu = await get_cashu(cashu_id) + + if not cashu: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." + ) + + lnurl = ( + lnurl_data.lnurl.replace("lnurlw://", "") + .replace("lightning://", "") + .replace("LIGHTNING://", "") + .replace("lightning:", "") + .replace("LIGHTNING:", "") + ) + + if lnurl.lower().startswith("lnurl"): + lnurl = decode_lnurl(lnurl) + else: + lnurl = "https://" + lnurl + + async with httpx.AsyncClient() as client: + try: + r = await client.get(lnurl, follow_redirects=True) + if r.is_error: + lnurl_response = {"success": False, "detail": "Error loading"} + else: + resp = r.json() + if resp["tag"] != "withdrawRequest": + lnurl_response = {"success": False, "detail": "Wrong tag type"} + else: + r2 = await client.get( + resp["callback"], + follow_redirects=True, + params={ + "k1": resp["k1"], + "pr": payment_request, + }, + ) + resp2 = r2.json() + if r2.is_error: + lnurl_response = { + "success": False, + "detail": "Error loading callback", + } + elif resp2["status"] == "ERROR": + lnurl_response = {"success": False, "detail": resp2["reason"]} + else: + lnurl_response = {"success": True, "detail": resp2} + except (httpx.ConnectError, httpx.RequestError): + lnurl_response = {"success": False, "detail": "Unexpected error occurred"} + + return lnurl_response + + +@cashu_ext.get( + "/api/v1/cashus/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK +) +async def api_cashu_check_invoice(cashu_id: str, payment_hash: str): + cashu = await get_cashu(cashu_id) + if not cashu: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." + ) + try: + status = await api_payment(payment_hash) + + except Exception as exc: + logger.error(exc) + return {"paid": False} + return status From f743feb232656d7d6f06ac738776c4d1a25a3f46 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 21 Sep 2022 15:16:38 +0100 Subject: [PATCH 211/565] Broke, started added private/public key --- lnbits/extensions/cashu/crud.py | 26 +++++++++++++++++++++++--- lnbits/extensions/cashu/migrations.py | 5 +++-- lnbits/extensions/cashu/models.py | 3 ++- lnbits/extensions/cashu/views_api.py | 12 +++++++++++- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index ce83653f..f50da111 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -5,13 +5,20 @@ from lnbits.helpers import urlsafe_short_hash from . import db from .models import Cashu, Pegs +from embit import script +from embit import ec +from embit.networks import NETWORKS +from binascii import unhexlify, hexlify async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: cashu_id = urlsafe_short_hash() + prv = ec.PrivateKey.from_wif(urlsafe_short_hash()) + pub = prv.get_public_key() + await db.execute( """ - INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins) - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins, prvkey, pubkey) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( cashu_id, @@ -20,7 +27,9 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: data.tickershort, data.fraction, data.maxsats, - data.coins + data.coins, + prv, + pub ), ) @@ -29,6 +38,17 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: return cashu +async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]: + if not wif: + prv = ec.PrivateKey.from_wif(urlsafe_short_hash()) + else: + prv = ec.PrivateKey.from_wif(wif) + pub = prv.get_public_key() + await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", (hexlify(prv.serialize()), hexlify(pub.serialize()), cashu_id)) + row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) + return Cashu(**row) if row else None + + async def get_cashu(cashu_id: str) -> Optional[Cashu]: row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) return Cashu(**row) if row else None diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py index 95dc4815..53420062 100644 --- a/lnbits/extensions/cashu/migrations.py +++ b/lnbits/extensions/cashu/migrations.py @@ -11,8 +11,9 @@ async def m001_initial(db): tickershort TEXT NOT NULL, fraction BOOL, maxsats INT, - coins INT - + coins INT, + prvkey TEXT NOT NULL, + pubkey TEXT NOT NULL ); """ ) diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py index 0de15362..892abdf1 100644 --- a/lnbits/extensions/cashu/models.py +++ b/lnbits/extensions/cashu/models.py @@ -13,7 +13,8 @@ class Cashu(BaseModel): fraction: bool = Query(None) maxsats: int = Query(0) coins: int = Query(0) - + prvkey: str = Query(None) + pubkey: str = Query(None) @classmethod def from_row(cls, row: Row) -> "TPoS": diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 0b16e4ba..49383945 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -13,7 +13,7 @@ from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import cashu_ext -from .crud import create_cashu, delete_cashu, get_cashu, get_cashus +from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys from .models import Cashu, Pegs, PayLnurlWData @@ -36,6 +36,16 @@ async def api_cashu_create( logger.debug(cashu) return cashu.dict() +@cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED) +async def api_cashu_update_keys( + data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type) +): + cashu = await get_cashu(data.id) + + cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data) + logger.debug(cashu) + return cashu.dict() + @cashu_ext.delete("/api/v1/cashus/{cashu_id}") async def api_cashu_delete( From 2ea636ec9ba8aa15f5744f7d9223b91004ca2514 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 29 Sep 2022 20:42:13 +0200 Subject: [PATCH 212/565] adjust versions --- lnbits/extensions/cashu/__init__.py | 5 +- lnbits/extensions/cashu/config.json | 8 +- poetry.lock | 812 +++++++++++++++++----------- pyproject.toml | 35 +- 4 files changed, 528 insertions(+), 332 deletions(-) diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index fa549ad2..d1a1d09c 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -6,9 +6,12 @@ from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart +from cashu.mint.router import router as cashu_router + db = Database("ext_cashu") -cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"]) +cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"]) +cashu_ext.include_router(router=cashu_router) def cashu_renderer(): diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json index c688b22c..d242a3a7 100644 --- a/lnbits/extensions/cashu/config.json +++ b/lnbits/extensions/cashu/config.json @@ -1,6 +1,6 @@ { - "name": "Cashu Ecash", - "short_description": "Ecash mints with LN peg in/out", - "icon": "approval", - "contributors": ["shinobi", "arcbtc", "calle"] + "name": "Cashu Mint", + "short_description": "Chaumian Ecash mint", + "icon": "donut_small_rounded", + "contributors": ["calle"] } diff --git a/poetry.lock b/poetry.lock index bbce3c5c..bf757cfe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -26,11 +26,11 @@ trio = ["trio (>=0.16,<0.22)"] [[package]] name = "asgiref" -version = "3.4.1" +version = "3.5.2" description = "ASGI specs, helper code, and adapters" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -59,11 +59,11 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} [[package]] name = "attrs" -version = "21.2.0" +version = "22.1.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -122,7 +122,64 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] -name = "cerberus" +name = "cashu" +version = "0.1.11" +description = "Ecash wallet and mint with Bitcoin Lightning support" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = {version = "3.6.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""} +ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +importlib-metadata = {version = "4.12.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} +iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +psycopg2-binary = {version = "2.9.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +typing-extensions = {version = "4.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""} +uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} +win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""} +zipp = {version = "3.8.1", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} + +[package.source] +type = "file" +url = "../cashu/dist/cashu-0.1.11-py3-none-any.whl" + +[[package]] +name = "Cerberus" version = "1.3.4" description = "Lightweight, extensible schema and data validation tool for Python dictionaries." category = "main" @@ -134,15 +191,15 @@ setuptools = "*" [[package]] name = "certifi" -version = "2021.5.30" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cffi" -version = "1.15.0" +version = "1.15.1" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -153,7 +210,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.6" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -164,7 +221,7 @@ unicode-backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.1" +version = "8.0.4" description = "Composable command line interface toolkit" category = "main" optional = false @@ -229,7 +286,7 @@ test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0 [[package]] name = "ecdsa" -version = "0.17.0" +version = "0.18.0" description = "ECDSA cryptographic signature library (pure python)" category = "main" optional = false @@ -260,7 +317,7 @@ python-versions = "*" [[package]] name = "fastapi" -version = "0.78.0" +version = "0.83.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false @@ -349,7 +406,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" -version = "3.2" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -357,11 +414,11 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.8.1" +version = "4.12.0" description = "Read metadata from Python packages" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -376,7 +433,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -395,8 +452,8 @@ plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] -name = "jinja2" -version = "3.0.1" +name = "Jinja2" +version = "3.0.3" description = "A very fast and expressive template engine." category = "main" optional = false @@ -423,7 +480,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "loguru" -version = "0.5.3" +version = "0.6.0" description = "Python logging made (stupidly) simple" category = "main" optional = false @@ -437,16 +494,16 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] [[package]] -name = "markupsafe" -version = "2.0.1" +name = "MarkupSafe" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "marshmallow" -version = "3.17.0" +version = "3.18.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "main" optional = false @@ -503,11 +560,11 @@ python-versions = "*" [[package]] name = "outcome" -version = "1.1.0" +version = "1.2.0" description = "Capture the outcome of Python function calls." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] attrs = ">=19.2.0" @@ -558,7 +615,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -579,7 +636,7 @@ python-versions = ">=3.7" [[package]] name = "psycopg2-binary" -version = "2.9.1" +version = "2.9.3" description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" optional = false @@ -589,7 +646,7 @@ python-versions = ">=3.6" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -611,14 +668,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydantic" -version = "1.8.2" -description = "Data validation and settings management using python 3.6 type hinting" +version = "1.10.2" +description = "Data validation and settings management using python type hints" category = "main" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" [package.dependencies] -typing-extensions = ">=3.7.4.3" +typing-extensions = ">=4.1.0" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] @@ -679,7 +736,7 @@ optional = false python-versions = "*" [[package]] -name = "pyqrcode" +name = "PyQRCode" version = "1.2.1" description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." category = "main" @@ -714,7 +771,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "pytest" version = "7.1.3" description = "pytest: simple powerful testing with Python" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -735,7 +792,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "pytest-asyncio" version = "0.19.0" description = "Pytest support for asyncio" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -763,17 +820,17 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dotenv" -version = "0.19.0" +version = "0.21.0" description = "Read key-value pairs from a .env file and set them as environment variables" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [package.extras] cli = ["click (>=5.0)"] [[package]] -name = "pyyaml" +name = "PyYAML" version = "5.4.1" description = "YAML parser and emitter for Python" category = "main" @@ -781,7 +838,7 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] -name = "represent" +name = "Represent" version = "1.6.0.post0" description = "Create __repr__ automatically or declaratively." category = "main" @@ -850,15 +907,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "sniffio" -version = "1.2.0" +version = "1.3.0" description = "Sniff out which async library your code is running under" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [[package]] -name = "sqlalchemy" -version = "1.3.23" +name = "SQLAlchemy" +version = "1.3.24" description = "Database Abstraction Library" category = "main" optional = false @@ -921,7 +978,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -943,15 +1000,28 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.18.1" +version = "0.18.3" description = "The lightning-fast ASGI server." category = "main" optional = false @@ -1020,11 +1090,11 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "zipp" -version = "3.5.0" +version = "3.8.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] @@ -1045,8 +1115,8 @@ anyio = [ {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, ] asgiref = [ - {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, - {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, ] asn1crypto = [ {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, @@ -1057,8 +1127,12 @@ async-timeout = [ {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +base58 = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, ] base58 = [ {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, @@ -1096,72 +1170,125 @@ black = [ {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, ] -cerberus = [ +cashu = [ + {file = "cashu-0.1.11-py3-none-any.whl", hash = "sha256:d2c5a72648fa4487fdf694e5669dfaa8d62445d83cced33b5bc63eccbce6a00c"}, +] +Cerberus = [ {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, ] certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, - {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, +] +coincurve = [ + {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, + {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"}, + {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"}, + {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"}, + {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"}, + {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"}, + {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"}, + {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"}, + {file = "coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"}, + {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"}, + {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"}, + {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"}, + {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"}, + {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"}, + {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"}, + {file = "coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"}, + {file = "coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"}, + {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"}, + {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"}, + {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"}, + {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"}, + {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"}, + {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"}, + {file = "coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"}, + {file = "coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"}, + {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"}, + {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"}, + {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"}, + {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"}, + {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"}, + {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"}, + {file = "coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"}, + {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"}, + {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"}, ] coincurve = [ {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, @@ -1278,8 +1405,8 @@ cryptography = [ {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] ecdsa = [ - {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, - {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"}, + {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, ] embit = [ {file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"}, @@ -1290,8 +1417,55 @@ enum34 = [ {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, ] fastapi = [ - {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"}, - {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"}, + {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"}, + {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"}, +] +grpcio = [ + {file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"}, + {file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"}, + {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"}, + {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"}, + {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"}, + {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"}, + {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"}, + {file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"}, + {file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"}, + {file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"}, + {file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"}, + {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"}, + {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"}, + {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"}, + {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"}, + {file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"}, + {file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"}, + {file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"}, + {file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"}, + {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"}, + {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"}, + {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"}, + {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"}, + {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"}, + {file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"}, + {file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"}, + {file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"}, + {file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"}, + {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"}, + {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"}, + {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"}, + {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"}, + {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"}, + {file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"}, + {file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"}, + {file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"}, + {file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"}, + {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"}, + {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"}, + {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"}, + {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"}, + {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"}, + {file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"}, + {file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"}, + {file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"}, ] grpcio = [ {file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"}, @@ -1389,12 +1563,12 @@ httpx = [ {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, ] idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -1404,92 +1578,63 @@ isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -jinja2 = [ - {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, - {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, +Jinja2 = [ + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] lnurl = [ {file = "lnurl-0.3.6-py3-none-any.whl", hash = "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92"}, {file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"}, ] loguru = [ - {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, - {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, + {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, + {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, ] -markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +MarkupSafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] marshmallow = [ - {file = "marshmallow-3.17.0-py3-none-any.whl", hash = "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb"}, - {file = "marshmallow-3.17.0.tar.gz", hash = "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"}, + {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"}, + {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"}, ] mock = [ {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, @@ -1525,8 +1670,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] outcome = [ - {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"}, - {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"}, + {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, + {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, @@ -1565,42 +1710,62 @@ protobuf = [ {file = "protobuf-4.21.8.tar.gz", hash = "sha256:427426593b55ff106c84e4a88cac855175330cb6eb7e889e85aaa7b5652b686d"}, ] psycopg2-binary = [ - {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661509f51531ec125e52357a489ea3806640d0ca37d9dada461ffc69ee1e7b6e"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:d92272c7c16e105788efe2cfa5d680f07e34e0c29b03c1908f8636f55d5f915a"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:736b8797b58febabb85494142c627bd182b50d2a7ec65322983e71065ad3034c"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-win32.whl", hash = "sha256:ebccf1123e7ef66efc615a68295bf6fdba875a75d5bba10a05073202598085fc"}, - {file = "psycopg2_binary-2.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:1f6ca4a9068f5c5c57e744b4baa79f40e83e3746875cac3c45467b16326bab45"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d"}, - {file = "psycopg2_binary-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e"}, - {file = "psycopg2_binary-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-win32.whl", hash = "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32"}, - {file = "psycopg2_binary-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"}, - {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, + {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -1640,28 +1805,54 @@ pycryptodomex = [ {file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"}, ] pydantic = [ - {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, - {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, - {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, - {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, - {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, - {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, - {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, - {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, - {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, - {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, + {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, + {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, + {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, + {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, + {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, + {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, + {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, + {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, +] +pyln-bolt7 = [ + {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, + {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, +] +pyln-client = [ + {file = "pyln-client-0.12.1.tar.gz", hash = "sha256:f14fa7947b65ecde2753984452441cf41b7b25b1a0ba7beced48786fa54d2bfe"}, + {file = "pyln_client-0.12.1-py3-none-any.whl", hash = "sha256:6b500bcc49e4028d50692b962d9c9f7e9ede920d718f9b9412f04f7db0aa0e63"}, +] +pyln-proto = [ + {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"}, + {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"}, ] pyln-bolt7 = [ {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, @@ -1682,7 +1873,7 @@ pyparsing = [ pypng = [ {file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"}, ] -pyqrcode = [ +PyQRCode = [ {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] @@ -1707,10 +1898,10 @@ pytest-cov = [ {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] python-dotenv = [ - {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, - {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, + {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, + {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, ] -pyyaml = [ +PyYAML = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, @@ -1741,10 +1932,14 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -represent = [ +Represent = [ {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, ] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -1787,48 +1982,44 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] sniffio = [ - {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, - {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] -sqlalchemy = [ - {file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"}, - {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"}, - {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"}, - {file = "SQLAlchemy-1.3.23-cp27-cp27m-win32.whl", hash = "sha256:b4b0e44d586cd64b65b507fa116a3814a1a53d55dce4836d7c1a6eb2823ff8d1"}, - {file = "SQLAlchemy-1.3.23-cp27-cp27m-win_amd64.whl", hash = "sha256:6b8b8c80c7f384f06825612dd078e4a31f0185e8f1f6b8c19e188ff246334205"}, - {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9e9c25522933e569e8b53ccc644dc993cab87e922fb7e142894653880fdd419d"}, - {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a0e306e9bb76fd93b29ae3a5155298e4c1b504c7cbc620c09c20858d32d16234"}, - {file = "SQLAlchemy-1.3.23-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6c9e6cc9237de5660bcddea63f332428bb83c8e2015c26777281f7ffbd2efb84"}, - {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:94f667d86be82dd4cb17d08de0c3622e77ca865320e0b95eae6153faa7b4ecaf"}, - {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:751934967f5336a3e26fc5993ccad1e4fee982029f9317eb6153bc0bc3d2d2da"}, - {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:63677d0c08524af4c5893c18dbe42141de7178001360b3de0b86217502ed3601"}, - {file = "SQLAlchemy-1.3.23-cp35-cp35m-win32.whl", hash = "sha256:ddfb511e76d016c3a160910642d57f4587dc542ce5ee823b0d415134790eeeb9"}, - {file = "SQLAlchemy-1.3.23-cp35-cp35m-win_amd64.whl", hash = "sha256:040bdfc1d76a9074717a3f43455685f781c581f94472b010cd6c4754754e1862"}, - {file = "SQLAlchemy-1.3.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d1a85dfc5dee741bf49cb9b6b6b8d2725a268e4992507cf151cba26b17d97c37"}, - {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:639940bbe1108ac667dcffc79925db2966826c270112e9159439ab6bb14f8d80"}, - {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e8a1750b44ad6422ace82bf3466638f1aa0862dbb9689690d5f2f48cce3476c8"}, - {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e5bb3463df697279e5459a7316ad5a60b04b0107f9392e88674d0ece70e9cf70"}, - {file = "SQLAlchemy-1.3.23-cp36-cp36m-win32.whl", hash = "sha256:e273367f4076bd7b9a8dc2e771978ef2bfd6b82526e80775a7db52bff8ca01dd"}, - {file = "SQLAlchemy-1.3.23-cp36-cp36m-win_amd64.whl", hash = "sha256:ac2244e64485c3778f012951fdc869969a736cd61375fde6096d08850d8be729"}, - {file = "SQLAlchemy-1.3.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:23927c3981d1ec6b4ea71eb99d28424b874d9c696a21e5fbd9fa322718be3708"}, - {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d90010304abb4102123d10cbad2cdf2c25a9f2e66a50974199b24b468509bad5"}, - {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a8bfc1e1afe523e94974132d7230b82ca7fa2511aedde1f537ec54db0399541a"}, - {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:269990b3ab53cb035d662dcde51df0943c1417bdab707dc4a7e4114a710504b4"}, - {file = "SQLAlchemy-1.3.23-cp37-cp37m-win32.whl", hash = "sha256:fdd2ed7395df8ac2dbb10cefc44737b66c6a5cd7755c92524733d7a443e5b7e2"}, - {file = "SQLAlchemy-1.3.23-cp37-cp37m-win_amd64.whl", hash = "sha256:6a939a868fdaa4b504e8b9d4a61f21aac11e3fecc8a8214455e144939e3d2aea"}, - {file = "SQLAlchemy-1.3.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:24f9569e82a009a09ce2d263559acb3466eba2617203170e4a0af91e75b4f075"}, - {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2578dbdbe4dbb0e5126fb37ffcd9793a25dcad769a95f171a2161030bea850ff"}, - {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1fe5d8d39118c2b018c215c37b73fd6893c3e1d4895be745ca8ff6eb83333ed3"}, - {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:c7dc052432cd5d060d7437e217dd33c97025287f99a69a50e2dc1478dd610d64"}, - {file = "SQLAlchemy-1.3.23-cp38-cp38-win32.whl", hash = "sha256:ecce8c021894a77d89808222b1ff9687ad84db54d18e4bd0500ca766737faaf6"}, - {file = "SQLAlchemy-1.3.23-cp38-cp38-win_amd64.whl", hash = "sha256:37b83bf81b4b85dda273aaaed5f35ea20ad80606f672d94d2218afc565fb0173"}, - {file = "SQLAlchemy-1.3.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8be835aac18ec85351385e17b8665bd4d63083a7160a017bef3d640e8e65cadb"}, - {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6ec1044908414013ebfe363450c22f14698803ce97fbb47e53284d55c5165848"}, - {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eab063a70cca4a587c28824e18be41d8ecc4457f8f15b2933584c6c6cccd30f0"}, - {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:baeb451ee23e264de3f577fee5283c73d9bbaa8cb921d0305c0bbf700094b65b"}, - {file = "SQLAlchemy-1.3.23-cp39-cp39-win32.whl", hash = "sha256:94208867f34e60f54a33a37f1c117251be91a47e3bfdb9ab8a7847f20886ad06"}, - {file = "SQLAlchemy-1.3.23-cp39-cp39-win_amd64.whl", hash = "sha256:f4d972139d5000105fcda9539a76452039434013570d6059993120dc2a65e447"}, - {file = "SQLAlchemy-1.3.23.tar.gz", hash = "sha256:6fca33672578666f657c131552c4ef8979c1606e494f78cd5199742dfb26918b"}, +SQLAlchemy = [ + {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, ] sqlalchemy-aio = [ {file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"}, @@ -1876,13 +2067,16 @@ types-protobuf = [ {file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] uvicorn = [ - {file = "uvicorn-0.18.1-py3-none-any.whl", hash = "sha256:013c4ea0787cc2dc456ef4368e18c01982e6be57903e4d3183218e543eb889b7"}, - {file = "uvicorn-0.18.1.tar.gz", hash = "sha256:35703e6518105cfe53f16a5a9435db3e2e227d0784f1fd8fbc1214b1fdc108df"}, + {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, + {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, ] uvloop = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, @@ -1942,6 +2136,6 @@ win32-setctime = [ {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] zipp = [ - {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, - {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, ] diff --git a/pyproject.toml b/pyproject.toml index b99bb353..69d58edd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ script = "build.py" [tool.poetry.dependencies] python = "^3.10 | ^3.9 | ^3.8 | ^3.7" aiofiles = "0.8.0" -asgiref = "3.4.1" -attrs = "21.2.0" +asgiref = "^3.4.1" +attrs = "22.1.0" bech32 = "1.2.0" bitstring = "3.1.9" certifi = "2021.5.30" @@ -22,19 +22,18 @@ ecdsa = "0.17.0" embit = "0.4.9" fastapi = "0.78.0" h11 = "0.12.0" -httpcore = "0.15.0" httptools = "0.4.0" httpx = "0.23.0" -idna = "3.2" -importlib-metadata = "4.8.1" -jinja2 = "3.0.1" +idna = "3.4" +importlib-metadata = "^4.8.1" +jinja2 = "3.0.3" lnurl = "0.3.6" -markupsafe = "2.0.1" -marshmallow = "3.17.0" -outcome = "1.1.0" -psycopg2-binary = "2.9.1" +markupsafe = "2.1.1" +marshmallow = "3.18.0" +outcome = "1.2.0" +psycopg2-binary = "2.9.3" pycryptodomex = "3.14.1" -pydantic = "1.8.2" +pydantic = "1.10.2" pypng = "0.0.21" pyqrcode = "1.2.1" pyScss = "1.4.0" @@ -45,18 +44,18 @@ rfc3986 = "1.5.0" secp256k1 = "0.14.0" shortuuid = "1.0.1" six = "1.16.0" -sniffio = "1.2.0" -sqlalchemy = "1.3.23" +sniffio = "1.3.0" +sqlalchemy = "1.3.24" sqlalchemy-aio = "0.17.0" sse-starlette = "0.6.2" -typing-extensions = "3.10.0.2" -uvicorn = "0.18.1" +typing-extensions = "4.3.0" +uvicorn = "0.18.3" uvloop = "0.16.0" watchgod = "0.7" websockets = "10.0" -zipp = "3.5.0" -loguru = "0.5.3" -cffi = "1.15.0" +zipp = "^3.5.0" +loguru = "0.6.0" +cffi = "1.15.1" websocket-client = "1.3.3" grpcio = "^1.49.1" protobuf = "^4.21.6" From 70ca997c114a4607a1c411336eb061b77daf7301 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 29 Sep 2022 20:42:13 +0200 Subject: [PATCH 213/565] Revert "adjust versions" This reverts commit 4303af4f1456040f922c6283f1415052a8308ab5. --- lnbits/extensions/cashu/__init__.py | 5 +- lnbits/extensions/cashu/config.json | 8 +- poetry.lock | 919 ++++++++++------------------ pyproject.toml | 35 +- 4 files changed, 360 insertions(+), 607 deletions(-) diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index d1a1d09c..fa549ad2 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -6,12 +6,9 @@ from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart -from cashu.mint.router import router as cashu_router - db = Database("ext_cashu") -cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"]) -cashu_ext.include_router(router=cashu_router) +cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"]) def cashu_renderer(): diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json index d242a3a7..c688b22c 100644 --- a/lnbits/extensions/cashu/config.json +++ b/lnbits/extensions/cashu/config.json @@ -1,6 +1,6 @@ { - "name": "Cashu Mint", - "short_description": "Chaumian Ecash mint", - "icon": "donut_small_rounded", - "contributors": ["calle"] + "name": "Cashu Ecash", + "short_description": "Ecash mints with LN peg in/out", + "icon": "approval", + "contributors": ["shinobi", "arcbtc", "calle"] } diff --git a/poetry.lock b/poetry.lock index bf757cfe..079b2b99 100644 --- a/poetry.lock +++ b/poetry.lock @@ -26,25 +26,25 @@ trio = ["trio (>=0.16,<0.22)"] [[package]] name = "asgiref" -version = "3.5.2" +version = "3.4.1" description = "ASGI specs, helper code, and adapters" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] [[package]] -name = "asn1crypto" -version = "1.5.1" -description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" -category = "main" +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "async-timeout" @@ -59,11 +59,11 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} [[package]] name = "attrs" -version = "22.1.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -122,84 +122,24 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] -name = "cashu" -version = "0.1.11" -description = "Ecash wallet and mint with Bitcoin Lightning support" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -anyio = {version = "3.6.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""} -ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -importlib-metadata = {version = "4.12.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} -iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -psycopg2-binary = {version = "2.9.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -typing-extensions = {version = "4.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""} -uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} -win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""} -zipp = {version = "3.8.1", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} - -[package.source] -type = "file" -url = "../cashu/dist/cashu-0.1.11-py3-none-any.whl" - -[[package]] -name = "Cerberus" +name = "cerberus" version = "1.3.4" description = "Lightweight, extensible schema and data validation tool for Python dictionaries." category = "main" optional = false python-versions = ">=2.7" -[package.dependencies] -setuptools = "*" - [[package]] name = "certifi" -version = "2022.9.24" +version = "2021.5.30" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = ">=3.6" +python-versions = "*" [[package]] name = "cffi" -version = "1.15.1" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -210,7 +150,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.0.6" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -221,7 +161,7 @@ unicode-backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.4" +version = "8.0.1" description = "Composable command line interface toolkit" category = "main" optional = false @@ -231,18 +171,6 @@ python-versions = ">=3.6" colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -[[package]] -name = "coincurve" -version = "17.0.0" -description = "Cross-platform Python CFFI bindings for libsecp256k1" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -asn1crypto = "*" -cffi = ">=1.3.0" - [[package]] name = "colorama" version = "0.4.6" @@ -265,28 +193,9 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] -[[package]] -name = "cryptography" -version = "36.0.2" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] - [[package]] name = "ecdsa" -version = "0.18.0" +version = "0.17.0" description = "ECDSA cryptographic signature library (pure python)" category = "main" optional = false @@ -317,7 +226,7 @@ python-versions = "*" [[package]] name = "fastapi" -version = "0.83.0" +version = "0.78.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false @@ -399,14 +308,14 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +brotli = ["brotlicffi", "brotli"] +cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "idna" -version = "3.4" +version = "3.2" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -414,11 +323,11 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.12.0" +version = "4.8.1" description = "Read metadata from Python packages" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -433,7 +342,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -452,8 +361,8 @@ plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] -name = "Jinja2" -version = "3.0.3" +name = "jinja2" +version = "3.0.1" description = "A very fast and expressive template engine." category = "main" optional = false @@ -480,7 +389,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "loguru" -version = "0.6.0" +version = "0.5.3" description = "Python logging made (stupidly) simple" category = "main" optional = false @@ -494,16 +403,16 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] [[package]] -name = "MarkupSafe" -version = "2.1.1" +name = "markupsafe" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "marshmallow" -version = "3.18.0" +version = "3.17.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "main" optional = false @@ -527,7 +436,7 @@ optional = false python-versions = ">=3.6" [package.extras] -build = ["blurb", "twine", "wheel"] +build = ["twine", "wheel", "blurb"] docs = ["sphinx"] test = ["pytest (<5.4)", "pytest-cov"] @@ -560,11 +469,11 @@ python-versions = "*" [[package]] name = "outcome" -version = "1.2.0" +version = "1.1.0" description = "Capture the outcome of Python function calls." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.dependencies] attrs = ">=19.2.0" @@ -580,24 +489,13 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" -[[package]] -name = "pathlib2" -version = "2.3.7.post1" -description = "Object-oriented filesystem paths" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - [[package]] name = "pathspec" -version = "0.10.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "platformdirs" @@ -608,14 +506,14 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -636,7 +534,7 @@ python-versions = ">=3.7" [[package]] name = "psycopg2-binary" -version = "2.9.3" +version = "2.9.1" description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" optional = false @@ -646,7 +544,7 @@ python-versions = ">=3.6" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -668,14 +566,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydantic" -version = "1.10.2" -description = "Data validation and settings management using python type hints" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6.1" [package.dependencies] -typing-extensions = ">=4.1.0" +typing-extensions = ">=3.7.4.3" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] @@ -725,7 +623,7 @@ optional = false python-versions = ">=3.6.8" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pypng" @@ -736,7 +634,7 @@ optional = false python-versions = "*" [[package]] -name = "PyQRCode" +name = "pyqrcode" version = "1.2.1" description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." category = "main" @@ -755,8 +653,6 @@ optional = false python-versions = "*" [package.dependencies] -enum34 = "*" -pathlib2 = "*" six = "*" [[package]] @@ -769,13 +665,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pytest" -version = "7.1.3" +version = "7.1.2" description = "pytest: simple powerful testing with Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -792,7 +689,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "pytest-asyncio" version = "0.19.0" description = "Pytest support for asyncio" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -801,7 +698,7 @@ pytest = ">=6.1.0" typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pytest-cov" @@ -816,21 +713,21 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] [[package]] name = "python-dotenv" -version = "0.21.0" +version = "0.19.0" description = "Read key-value pairs from a .env file and set them as environment variables" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [package.extras] cli = ["click (>=5.0)"] [[package]] -name = "PyYAML" +name = "pyyaml" version = "5.4.1" description = "YAML parser and emitter for Python" category = "main" @@ -838,7 +735,7 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] -name = "Represent" +name = "represent" version = "1.6.0.post0" description = "Create __repr__ automatically or declaratively." category = "main" @@ -907,15 +804,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "sniffio" -version = "1.3.0" +version = "1.2.0" description = "Sniff out which async library your code is running under" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [[package]] -name = "SQLAlchemy" -version = "1.3.24" +name = "sqlalchemy" +version = "1.3.23" description = "Database Abstraction Library" category = "main" optional = false @@ -926,7 +823,7 @@ mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] mysql = ["mysqlclient"] -oracle = ["cx_oracle"] +oracle = ["cx-oracle"] postgresql = ["psycopg2"] postgresql-pg8000 = ["pg8000 (<1.16.6)"] postgresql-psycopg2binary = ["psycopg2-binary"] @@ -978,7 +875,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -1000,28 +897,15 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "4.3.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.12" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +python-versions = "*" [[package]] name = "uvicorn" -version = "0.18.3" +version = "0.18.1" description = "The lightning-fast ASGI server." category = "main" optional = false @@ -1044,9 +928,9 @@ optional = false python-versions = ">=3.7" [package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] [[package]] name = "watchgod" @@ -1090,11 +974,11 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "zipp" -version = "3.8.1" +version = "3.5.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.extras] docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] @@ -1115,24 +999,19 @@ anyio = [ {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, ] asgiref = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, ] -asn1crypto = [ - {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, - {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -base58 = [ - {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, - {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] base58 = [ {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, @@ -1170,125 +1049,72 @@ black = [ {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, ] -cashu = [ - {file = "cashu-0.1.11-py3-none-any.whl", hash = "sha256:d2c5a72648fa4487fdf694e5669dfaa8d62445d83cced33b5bc63eccbce6a00c"}, -] -Cerberus = [ +cerberus = [ {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, ] certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, + {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, ] click = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, -] -coincurve = [ - {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, - {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"}, - {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"}, - {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"}, - {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"}, - {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"}, - {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"}, - {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"}, - {file = "coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"}, - {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"}, - {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"}, - {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"}, - {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"}, - {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"}, - {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"}, - {file = "coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"}, - {file = "coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"}, - {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"}, - {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"}, - {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"}, - {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"}, - {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"}, - {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"}, - {file = "coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"}, - {file = "coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"}, - {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"}, - {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"}, - {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"}, - {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"}, - {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"}, - {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"}, - {file = "coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"}, - {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"}, - {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"}, + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, ] coincurve = [ {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, @@ -1405,8 +1231,8 @@ cryptography = [ {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] ecdsa = [ - {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, - {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, + {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, + {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"}, ] embit = [ {file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"}, @@ -1417,55 +1243,8 @@ enum34 = [ {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, ] fastapi = [ - {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"}, - {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"}, -] -grpcio = [ - {file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"}, - {file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"}, - {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"}, - {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"}, - {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"}, - {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"}, - {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"}, - {file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"}, - {file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"}, - {file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"}, - {file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"}, - {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"}, - {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"}, - {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"}, - {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"}, - {file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"}, - {file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"}, - {file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"}, - {file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"}, - {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"}, - {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"}, - {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"}, - {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"}, - {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"}, - {file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"}, - {file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"}, - {file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"}, - {file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"}, - {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"}, - {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"}, - {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"}, - {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"}, - {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"}, - {file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"}, - {file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"}, - {file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"}, - {file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"}, - {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"}, - {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"}, - {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"}, - {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"}, - {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"}, - {file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"}, - {file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"}, - {file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"}, + {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"}, + {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"}, ] grpcio = [ {file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"}, @@ -1563,12 +1342,12 @@ httpx = [ {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, ] idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -1578,63 +1357,92 @@ isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -Jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, +jinja2 = [ + {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, + {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] lnurl = [ {file = "lnurl-0.3.6-py3-none-any.whl", hash = "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92"}, {file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"}, ] loguru = [ - {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, - {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, + {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, + {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] -MarkupSafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] marshmallow = [ - {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"}, - {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"}, + {file = "marshmallow-3.17.0-py3-none-any.whl", hash = "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb"}, + {file = "marshmallow-3.17.0.tar.gz", hash = "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"}, ] mock = [ {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, @@ -1670,20 +1478,16 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] outcome = [ - {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, - {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, + {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"}, + {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pathlib2 = [ - {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, - {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, -] pathspec = [ - {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, - {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] platformdirs = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, @@ -1710,62 +1514,42 @@ protobuf = [ {file = "protobuf-4.21.8.tar.gz", hash = "sha256:427426593b55ff106c84e4a88cac855175330cb6eb7e889e85aaa7b5652b686d"}, ] psycopg2-binary = [ - {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"}, + {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661509f51531ec125e52357a489ea3806640d0ca37d9dada461ffc69ee1e7b6e"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:d92272c7c16e105788efe2cfa5d680f07e34e0c29b03c1908f8636f55d5f915a"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:736b8797b58febabb85494142c627bd182b50d2a7ec65322983e71065ad3034c"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-win32.whl", hash = "sha256:ebccf1123e7ef66efc615a68295bf6fdba875a75d5bba10a05073202598085fc"}, + {file = "psycopg2_binary-2.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:1f6ca4a9068f5c5c57e744b4baa79f40e83e3746875cac3c45467b16326bab45"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d"}, + {file = "psycopg2_binary-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e"}, + {file = "psycopg2_binary-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-win32.whl", hash = "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32"}, + {file = "psycopg2_binary-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"}, + {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -1805,54 +1589,28 @@ pycryptodomex = [ {file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"}, ] pydantic = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, -] -pyln-bolt7 = [ - {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, - {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, -] -pyln-client = [ - {file = "pyln-client-0.12.1.tar.gz", hash = "sha256:f14fa7947b65ecde2753984452441cf41b7b25b1a0ba7beced48786fa54d2bfe"}, - {file = "pyln_client-0.12.1-py3-none-any.whl", hash = "sha256:6b500bcc49e4028d50692b962d9c9f7e9ede920d718f9b9412f04f7db0aa0e63"}, -] -pyln-proto = [ - {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"}, - {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"}, + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, ] pyln-bolt7 = [ {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, @@ -1873,7 +1631,7 @@ pyparsing = [ pypng = [ {file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"}, ] -PyQRCode = [ +pyqrcode = [ {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] @@ -1886,8 +1644,8 @@ pysocks = [ {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, ] pytest = [ - {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, - {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, @@ -1898,10 +1656,10 @@ pytest-cov = [ {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] python-dotenv = [ - {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, - {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, + {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, + {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, ] -PyYAML = [ +pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, @@ -1932,14 +1690,10 @@ PyYAML = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -Represent = [ +represent = [ {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, ] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -1982,44 +1736,48 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] sniffio = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] -SQLAlchemy = [ - {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, - {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, - {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, - {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, - {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, - {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, - {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, - {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, - {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, +sqlalchemy = [ + {file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"}, + {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"}, + {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"}, + {file = "SQLAlchemy-1.3.23-cp27-cp27m-win32.whl", hash = "sha256:b4b0e44d586cd64b65b507fa116a3814a1a53d55dce4836d7c1a6eb2823ff8d1"}, + {file = "SQLAlchemy-1.3.23-cp27-cp27m-win_amd64.whl", hash = "sha256:6b8b8c80c7f384f06825612dd078e4a31f0185e8f1f6b8c19e188ff246334205"}, + {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9e9c25522933e569e8b53ccc644dc993cab87e922fb7e142894653880fdd419d"}, + {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a0e306e9bb76fd93b29ae3a5155298e4c1b504c7cbc620c09c20858d32d16234"}, + {file = "SQLAlchemy-1.3.23-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6c9e6cc9237de5660bcddea63f332428bb83c8e2015c26777281f7ffbd2efb84"}, + {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:94f667d86be82dd4cb17d08de0c3622e77ca865320e0b95eae6153faa7b4ecaf"}, + {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:751934967f5336a3e26fc5993ccad1e4fee982029f9317eb6153bc0bc3d2d2da"}, + {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:63677d0c08524af4c5893c18dbe42141de7178001360b3de0b86217502ed3601"}, + {file = "SQLAlchemy-1.3.23-cp35-cp35m-win32.whl", hash = "sha256:ddfb511e76d016c3a160910642d57f4587dc542ce5ee823b0d415134790eeeb9"}, + {file = "SQLAlchemy-1.3.23-cp35-cp35m-win_amd64.whl", hash = "sha256:040bdfc1d76a9074717a3f43455685f781c581f94472b010cd6c4754754e1862"}, + {file = "SQLAlchemy-1.3.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d1a85dfc5dee741bf49cb9b6b6b8d2725a268e4992507cf151cba26b17d97c37"}, + {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:639940bbe1108ac667dcffc79925db2966826c270112e9159439ab6bb14f8d80"}, + {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e8a1750b44ad6422ace82bf3466638f1aa0862dbb9689690d5f2f48cce3476c8"}, + {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e5bb3463df697279e5459a7316ad5a60b04b0107f9392e88674d0ece70e9cf70"}, + {file = "SQLAlchemy-1.3.23-cp36-cp36m-win32.whl", hash = "sha256:e273367f4076bd7b9a8dc2e771978ef2bfd6b82526e80775a7db52bff8ca01dd"}, + {file = "SQLAlchemy-1.3.23-cp36-cp36m-win_amd64.whl", hash = "sha256:ac2244e64485c3778f012951fdc869969a736cd61375fde6096d08850d8be729"}, + {file = "SQLAlchemy-1.3.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:23927c3981d1ec6b4ea71eb99d28424b874d9c696a21e5fbd9fa322718be3708"}, + {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d90010304abb4102123d10cbad2cdf2c25a9f2e66a50974199b24b468509bad5"}, + {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a8bfc1e1afe523e94974132d7230b82ca7fa2511aedde1f537ec54db0399541a"}, + {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:269990b3ab53cb035d662dcde51df0943c1417bdab707dc4a7e4114a710504b4"}, + {file = "SQLAlchemy-1.3.23-cp37-cp37m-win32.whl", hash = "sha256:fdd2ed7395df8ac2dbb10cefc44737b66c6a5cd7755c92524733d7a443e5b7e2"}, + {file = "SQLAlchemy-1.3.23-cp37-cp37m-win_amd64.whl", hash = "sha256:6a939a868fdaa4b504e8b9d4a61f21aac11e3fecc8a8214455e144939e3d2aea"}, + {file = "SQLAlchemy-1.3.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:24f9569e82a009a09ce2d263559acb3466eba2617203170e4a0af91e75b4f075"}, + {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2578dbdbe4dbb0e5126fb37ffcd9793a25dcad769a95f171a2161030bea850ff"}, + {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1fe5d8d39118c2b018c215c37b73fd6893c3e1d4895be745ca8ff6eb83333ed3"}, + {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:c7dc052432cd5d060d7437e217dd33c97025287f99a69a50e2dc1478dd610d64"}, + {file = "SQLAlchemy-1.3.23-cp38-cp38-win32.whl", hash = "sha256:ecce8c021894a77d89808222b1ff9687ad84db54d18e4bd0500ca766737faaf6"}, + {file = "SQLAlchemy-1.3.23-cp38-cp38-win_amd64.whl", hash = "sha256:37b83bf81b4b85dda273aaaed5f35ea20ad80606f672d94d2218afc565fb0173"}, + {file = "SQLAlchemy-1.3.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8be835aac18ec85351385e17b8665bd4d63083a7160a017bef3d640e8e65cadb"}, + {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6ec1044908414013ebfe363450c22f14698803ce97fbb47e53284d55c5165848"}, + {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eab063a70cca4a587c28824e18be41d8ecc4457f8f15b2933584c6c6cccd30f0"}, + {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:baeb451ee23e264de3f577fee5283c73d9bbaa8cb921d0305c0bbf700094b65b"}, + {file = "SQLAlchemy-1.3.23-cp39-cp39-win32.whl", hash = "sha256:94208867f34e60f54a33a37f1c117251be91a47e3bfdb9ab8a7847f20886ad06"}, + {file = "SQLAlchemy-1.3.23-cp39-cp39-win_amd64.whl", hash = "sha256:f4d972139d5000105fcda9539a76452039434013570d6059993120dc2a65e447"}, + {file = "SQLAlchemy-1.3.23.tar.gz", hash = "sha256:6fca33672578666f657c131552c4ef8979c1606e494f78cd5199742dfb26918b"}, ] sqlalchemy-aio = [ {file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"}, @@ -2067,16 +1825,13 @@ types-protobuf = [ {file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"}, ] typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] -urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] uvicorn = [ - {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, - {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, + {file = "uvicorn-0.18.1-py3-none-any.whl", hash = "sha256:013c4ea0787cc2dc456ef4368e18c01982e6be57903e4d3183218e543eb889b7"}, + {file = "uvicorn-0.18.1.tar.gz", hash = "sha256:35703e6518105cfe53f16a5a9435db3e2e227d0784f1fd8fbc1214b1fdc108df"}, ] uvloop = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, @@ -2136,6 +1891,6 @@ win32-setctime = [ {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, ] diff --git a/pyproject.toml b/pyproject.toml index 69d58edd..b99bb353 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ script = "build.py" [tool.poetry.dependencies] python = "^3.10 | ^3.9 | ^3.8 | ^3.7" aiofiles = "0.8.0" -asgiref = "^3.4.1" -attrs = "22.1.0" +asgiref = "3.4.1" +attrs = "21.2.0" bech32 = "1.2.0" bitstring = "3.1.9" certifi = "2021.5.30" @@ -22,18 +22,19 @@ ecdsa = "0.17.0" embit = "0.4.9" fastapi = "0.78.0" h11 = "0.12.0" +httpcore = "0.15.0" httptools = "0.4.0" httpx = "0.23.0" -idna = "3.4" -importlib-metadata = "^4.8.1" -jinja2 = "3.0.3" +idna = "3.2" +importlib-metadata = "4.8.1" +jinja2 = "3.0.1" lnurl = "0.3.6" -markupsafe = "2.1.1" -marshmallow = "3.18.0" -outcome = "1.2.0" -psycopg2-binary = "2.9.3" +markupsafe = "2.0.1" +marshmallow = "3.17.0" +outcome = "1.1.0" +psycopg2-binary = "2.9.1" pycryptodomex = "3.14.1" -pydantic = "1.10.2" +pydantic = "1.8.2" pypng = "0.0.21" pyqrcode = "1.2.1" pyScss = "1.4.0" @@ -44,18 +45,18 @@ rfc3986 = "1.5.0" secp256k1 = "0.14.0" shortuuid = "1.0.1" six = "1.16.0" -sniffio = "1.3.0" -sqlalchemy = "1.3.24" +sniffio = "1.2.0" +sqlalchemy = "1.3.23" sqlalchemy-aio = "0.17.0" sse-starlette = "0.6.2" -typing-extensions = "4.3.0" -uvicorn = "0.18.3" +typing-extensions = "3.10.0.2" +uvicorn = "0.18.1" uvloop = "0.16.0" watchgod = "0.7" websockets = "10.0" -zipp = "^3.5.0" -loguru = "0.6.0" -cffi = "1.15.1" +zipp = "3.5.0" +loguru = "0.5.3" +cffi = "1.15.0" websocket-client = "1.3.3" grpcio = "^1.49.1" protobuf = "^4.21.6" From 82fc156a04945b68845c0e9d2341bc7a24562726 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 30 Sep 2022 12:25:48 +0100 Subject: [PATCH 214/565] Added the cashu router --- lnbits/extensions/cashu/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index fa549ad2..ed67b134 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -6,20 +6,20 @@ from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart +from cashu.mint.router import router as cashu_router + db = Database("ext_cashu") -cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"]) - +cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"]) +cashu_ext.include_router(router=cashu_router) def cashu_renderer(): return template_renderer(["lnbits/extensions/cashu/templates"]) - from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa - def cashu_start(): loop = asyncio.get_event_loop() loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) From 485647d8a1907f41af86531df682faf56456e2a5 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 30 Sep 2022 12:26:50 +0100 Subject: [PATCH 215/565] Extension authors --- lnbits/extensions/cashu/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json index c688b22c..4f097e8b 100644 --- a/lnbits/extensions/cashu/config.json +++ b/lnbits/extensions/cashu/config.json @@ -2,5 +2,5 @@ "name": "Cashu Ecash", "short_description": "Ecash mints with LN peg in/out", "icon": "approval", - "contributors": ["shinobi", "arcbtc", "calle"] + "contributors": ["arcbtc", "calle"] } From 7df673c72df52f319bbb0b18b53c90ac539e7255 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 30 Sep 2022 14:23:03 +0100 Subject: [PATCH 216/565] started adding mint stuff --- lnbits/extensions/cashu/crud.py | 59 ++++++ lnbits/extensions/cashu/ledger.py | 282 +++++++++++++++++++++++++++ lnbits/extensions/cashu/models.py | 105 +++++++++- lnbits/extensions/cashu/views_api.py | 67 ++++++- 4 files changed, 510 insertions(+), 3 deletions(-) create mode 100644 lnbits/extensions/cashu/ledger.py diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index f50da111..e1cdaf4e 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -68,3 +68,62 @@ async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]: async def delete_cashu(cashu_id: str) -> None: await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,)) + + + +###############MINT STUFF################# + +async def store_promise( + amount: int, + B_: str, + C_: str, + db: Database, + conn: Optional[Connection] = None, +): + + await (conn or db).execute( + """ + INSERT INTO promises + (amount, B_b, C_b) + VALUES (?, ?, ?) + """, + ( + amount, + str(B_), + str(C_), + ), + ) + + +async def get_proofs_used( + db: Database, + conn: Optional[Connection] = None, +): + + rows = await (conn or db).fetchall( + """ + SELECT secret from proofs_used + """ + ) + return [row[0] for row in rows] + + +async def invalidate_proof( + proof: Proof, + db: Database, + conn: Optional[Connection] = None, +): + + # we add the proof and secret to the used list + await (conn or db).execute( + """ + INSERT INTO proofs_used + (amount, C, secret) + VALUES (?, ?, ?) + """, + ( + proof.amount, + str(proof.C), + str(proof.secret), + ), + ) \ No newline at end of file diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py new file mode 100644 index 00000000..cc5ef924 --- /dev/null +++ b/lnbits/extensions/cashu/ledger.py @@ -0,0 +1,282 @@ +import hashlib +from typing import List, Set + +from models import BlindedMessage, BlindedSignature, Invoice, Proof +from secp256k1 import PublicKey, PrivateKey + +from lnbits.core.services import check_transaction_status, create_invoice + +class Ledger: + def __init__(self, secret_key: str, db: str, MAX_ORDER: int = Query(64)): + self.proofs_used: Set[str] = set() + + self.master_key: str = secret_key + self.keys: List[PrivateKey] = self._derive_keys(self.master_key) + self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys) + self.db: Database = Database("mint", db) + + async def load_used_proofs(self): + self.proofs_used = set(await get_proofs_used(db=self.db)) + + @staticmethod + def _derive_keys(master_key: str): + """Deterministic derivation of keys for 2^n values.""" + return { + 2 + ** i: PrivateKey( + hashlib.sha256((str(master_key) + str(i)).encode("utf-8")) + .hexdigest() + .encode("utf-8")[:32], + raw=True, + ) + for i in range(MAX_ORDER) + } + + @staticmethod + def _derive_pubkeys(keys: List[PrivateKey]): + return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} + + async def _generate_promises(self, amounts: List[int], B_s: List[str]): + """Generates promises that sum to the given amount.""" + return [ + await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True)) + for (amount, B_) in zip(amounts, B_s) + ] + + async def _generate_promise(self, amount: int, B_: PublicKey): + """Generates a promise for given amount and returns a pair (amount, C').""" + secret_key = self.keys[amount] # Get the correct key + C_ = step2_bob(B_, secret_key) + await store_promise( + amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db + ) + return BlindedSignature(amount=amount, C_=C_.serialize().hex()) + + def _check_spendable(self, proof: Proof): + """Checks whether the proof was already spent.""" + return not proof.secret in self.proofs_used + + def _verify_proof(self, proof: Proof): + """Verifies that the proof of promise was issued by this ledger.""" + if not self._check_spendable(proof): + raise Exception(f"tokens already spent. Secret: {proof.secret}") + secret_key = self.keys[proof.amount] # Get the correct key to check against + C = PublicKey(bytes.fromhex(proof.C), raw=True) + return verify(secret_key, C, proof.secret) + + def _verify_outputs( + self, total: int, amount: int, output_data: List[BlindedMessage] + ): + """Verifies the expected split was correctly computed""" + fst_amt, snd_amt = total - amount, amount # we have two amounts to split to + fst_outputs = amount_split(fst_amt) + snd_outputs = amount_split(snd_amt) + expected = fst_outputs + snd_outputs + given = [o.amount for o in output_data] + return given == expected + + def _verify_no_duplicates( + self, proofs: List[Proof], output_data: List[BlindedMessage] + ): + secrets = [p.secret for p in proofs] + if len(secrets) != len(list(set(secrets))): + return False + B_s = [od.B_ for od in output_data] + if len(B_s) != len(list(set(B_s))): + return False + return True + + def _verify_split_amount(self, amount: int): + """Split amount like output amount can't be negative or too big.""" + try: + self._verify_amount(amount) + except: + # For better error message + raise Exception("invalid split amount: " + str(amount)) + + def _verify_amount(self, amount: int): + """Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" + valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER + if not valid: + raise Exception("invalid amount: " + str(amount)) + return amount + + def _verify_equation_balanced( + self, proofs: List[Proof], outs: List[BlindedMessage] + ): + """Verify that Σoutputs - Σinputs = 0.""" + sum_inputs = sum(self._verify_amount(p.amount) for p in proofs) + sum_outputs = sum(self._verify_amount(p.amount) for p in outs) + assert sum_outputs - sum_inputs == 0 + + def _get_output_split(self, amount: int): + """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" + self._verify_amount(amount) + bits_amt = bin(amount)[::-1][:-2] + rv = [] + for (pos, bit) in enumerate(bits_amt): + if bit == "1": + rv.append(2**pos) + return rv + + async def _invalidate_proofs(self, proofs: List[Proof]): + """Adds secrets of proofs to the list of knwon secrets and stores them in the db.""" + # Mark proofs as used and prepare new promises + proof_msgs = set([p.secret for p in proofs]) + self.proofs_used |= proof_msgs + # store in db + for p in proofs: + await invalidate_proof(p, db=self.db) + + # Public methods + def get_pubkeys(self): + """Returns public keys for possible amounts.""" + return {a: p.serialize().hex() for a, p in self.pub_keys.items()} + + async def request_mint(self, amount): + """Returns Lightning invoice and stores it in the db.""" + payment_request, payment_hash = payment_hash, payment_request = await create_invoice( + wallet_id=link.wallet, + amount=amount, + memo=link.description, + unhashed_description=link.description.encode("utf-8"), + extra={ + "tag": "Cashu" + }, + ) + + invoice = Invoice( + amount=amount, pr=payment_request, hash=payment_hash, issued=False + ) + if not payment_request or not payment_hash: + raise Exception(f"Could not create Lightning invoice.") + await store_lightning_invoice(invoice, db=self.db) + return payment_request, payment_hash + + async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): + """Mints a promise for coins for B_.""" + # check if lightning invoice was paid + if payment_hash and not await check_transaction_status(ayment_hash) + raise Exception("Lightning invoice not paid yet.") + + for amount in amounts: + if amount not in [2**i for i in range(MAX_ORDER)]: + raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") + + promises = [ + await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) + ] + return promises + + async def melt(self, proofs: List[Proof], amount: int, invoice: str): + """Invalidates proofs and pays a Lightning invoice.""" + # if not LIGHTNING: + total = sum([p["amount"] for p in proofs]) + # check that lightning fees are included + assert total + fee_reserve(amount * 1000) >= amount, Exception( + "provided proofs not enough for Lightning payment." + ) + + status, payment_hash = await pay_invoice( + wallet_id=link.wallet, + payment_request=invoice, + max_sat=amount, + extra={"tag": "Ecash melt"}, + ) + + if status == True: + await self._invalidate_proofs(proofs) + return status, payment_hash + + async def check_spendable(self, proofs: List[Proof]): + """Checks if all provided proofs are valid and still spendable (i.e. have not been spent).""" + return {i: self._check_spendable(p) for i, p in enumerate(proofs)} + + async def split( + self, proofs: List[Proof], amount: int, output_data: List[BlindedMessage] + ): + """Consumes proofs and prepares new promises based on the amount split.""" + self._verify_split_amount(amount) + # Verify proofs are valid + if not all([self._verify_proof(p) for p in proofs]): + return False + + total = sum([p.amount for p in proofs]) + + if not self._verify_no_duplicates(proofs, output_data): + raise Exception("duplicate proofs or promises") + if amount > total: + raise Exception("split amount is higher than the total sum") + if not self._verify_outputs(total, amount, output_data): + raise Exception("split of promises is not as expected") + + # Mark proofs as used and prepare new promises + await self._invalidate_proofs(proofs) + + outs_fst = amount_split(total - amount) + outs_snd = amount_split(amount) + B_fst = [od.B_ for od in output_data[: len(outs_fst)]] + B_snd = [od.B_ for od in output_data[len(outs_fst) :]] + prom_fst, prom_snd = await self._generate_promises( + outs_fst, B_fst + ), await self._generate_promises(outs_snd, B_snd) + self._verify_equation_balanced(proofs, prom_fst + prom_snd) + return prom_fst, prom_snd + + +#######FUNCTIONS############### +def fee_reserve(amount_msat: int) -> int: + """Function for calculating the Lightning fee reserve""" + return max( + int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0) + ) + +def amount_split(amount): + """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" + bits_amt = bin(amount)[::-1][:-2] + rv = [] + for (pos, bit) in enumerate(bits_amt): + if bit == "1": + rv.append(2**pos) + return rv + +def hash_to_point(secret_msg): + """Generates x coordinate from the message hash and checks if the point lies on the curve. + If it does not, it tries computing again a new x coordinate from the hash of the coordinate.""" + point = None + msg = secret_msg + while point is None: + _hash = hashlib.sha256(msg).hexdigest().encode("utf-8") + try: + # We construct compressed pub which has x coordinate encoded with even y + _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes + _hash[0] = 0x02 # set first byte to represent even y coord + _hash = bytes(_hash) + point = PublicKey(_hash, raw=True) + except: + msg = _hash + + return point + + +def step1_alice(secret_msg): + secret_msg = secret_msg.encode("utf-8") + Y = hash_to_point(secret_msg) + r = PrivateKey() + B_ = Y + r.pubkey + return B_, r + + +def step2_bob(B_, a): + C_ = B_.mult(a) + return C_ + + +def step3_alice(C_, r, A): + C = C_ - A.mult(r) + return C + + +def verify(a, C, secret_msg): + Y = hash_to_point(secret_msg.encode("utf-8")) + return C == Y.mult(a) diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py index 892abdf1..ba9303c5 100644 --- a/lnbits/extensions/cashu/models.py +++ b/lnbits/extensions/cashu/models.py @@ -31,5 +31,106 @@ class Pegs(BaseModel): def from_row(cls, row: Row) -> "TPoS": return cls(**dict(row)) -class PayLnurlWData(BaseModel): - lnurl: str \ No newline at end of file + +class Proof(BaseModel): + amount: int + secret: str + C: str + reserved: bool = False # whether this proof is reserved for sending + send_id: str = "" # unique ID of send attempt + time_created: str = "" + time_reserved: str = "" + + @classmethod + def from_row(cls, row: Row): + return cls( + amount=row[0], + C=row[1], + secret=row[2], + reserved=row[3] or False, + send_id=row[4] or "", + time_created=row[5] or "", + time_reserved=row[6] or "", + ) + + @classmethod + def from_dict(cls, d: dict): + assert "secret" in d, "no secret in proof" + assert "amount" in d, "no amount in proof" + return cls( + amount=d.get("amount"), + C=d.get("C"), + secret=d.get("secret"), + reserved=d.get("reserved") or False, + send_id=d.get("send_id") or "", + time_created=d.get("time_created") or "", + time_reserved=d.get("time_reserved") or "", + ) + + def to_dict(self): + return dict(amount=self.amount, secret=self.secret, C=self.C) + + def __getitem__(self, key): + return self.__getattribute__(key) + + def __setitem__(self, key, val): + self.__setattr__(key, val) + + +class Proofs(BaseModel): + """TODO: Use this model""" + + proofs: List[Proof] + + +class Invoice(BaseModel): + amount: int + pr: str + hash: str + issued: bool = False + + @classmethod + def from_row(cls, row: Row): + return cls( + amount=int(row[0]), + pr=str(row[1]), + hash=str(row[2]), + issued=bool(row[3]), + ) + + +class BlindedMessage(BaseModel): + amount: int + B_: str + + +class BlindedSignature(BaseModel): + amount: int + C_: str + + @classmethod + def from_dict(cls, d: dict): + return cls( + amount=d["amount"], + C_=d["C_"], + ) + + +class MintPayloads(BaseModel): + blinded_messages: List[BlindedMessage] = [] + + +class SplitPayload(BaseModel): + proofs: List[Proof] + amount: int + output_data: MintPayloads + + +class CheckPayload(BaseModel): + proofs: List[Proof] + + +class MeltPayload(BaseModel): + proofs: List[Proof] + amount: int + invoice: str \ No newline at end of file diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 49383945..5a1c500e 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -1,4 +1,6 @@ from http import HTTPStatus +from secp256k1 import PublicKey +from typing import Union import httpx from fastapi import Query @@ -14,8 +16,9 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import cashu_ext from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys -from .models import Cashu, Pegs, PayLnurlWData +from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload +import .ledger @cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK) async def api_cashus( @@ -168,3 +171,65 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str): logger.error(exc) return {"paid": False} return status + + +#################CASHU STUFF################### + +@cashu_ext.get("/keys") +def keys(): + """Get the public keys of the mint""" + return ledger.get_pubkeys() + + +@cashu_ext.get("/mint") +async def request_mint(amount: int = 0): + """Request minting of tokens. Server responds with a Lightning invoice.""" + payment_request, payment_hash = await ledger.request_mint(amount) + print(f"Lightning invoice: {payment_request}") + return {"pr": payment_request, "hash": payment_hash} + + +@cashu_ext.post("/mint") +async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None): + amounts = [] + B_s = [] + for payload in payloads.blinded_messages: + amounts.append(payload.amount) + B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True)) + try: + promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash) + return promises + except Exception as exc: + return {"error": str(exc)} + + +@cashu_ext.post("/melt") +async def melt(payload: MeltPayload): + + ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice) + return {"paid": ok, "preimage": preimage} + + +@cashu_ext.post("/check") +async def check_spendable(payload: CheckPayload): + return await ledger.check_spendable(payload.proofs) + + +@cashu_ext.post("/split") +async def split(payload: SplitPayload): + """ + Requetst a set of tokens with amount "total" to be split into two + newly minted sets with amount "split" and "total-split". + """ + proofs = payload.proofs + amount = payload.amount + output_data = payload.output_data.blinded_messages + try: + split_return = await ledger.split(proofs, amount, output_data) + except Exception as exc: + return {"error": str(exc)} + if not split_return: + """There was a problem with the split""" + raise Exception("could not split tokens.") + fst_promises, snd_promises = split_return + return {"fst": fst_promises, "snd": snd_promises} \ No newline at end of file From 11056f2a579f4c5b84a288688bd05ccac6a9619a Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 3 Oct 2022 10:45:13 +0100 Subject: [PATCH 217/565] Booting --- lnbits/extensions/cashu/__init__.py | 3 --- lnbits/extensions/cashu/crud.py | 15 ++++----------- lnbits/extensions/cashu/ledger.py | 19 ++++++++++--------- lnbits/extensions/cashu/models.py | 4 +++- lnbits/extensions/cashu/views_api.py | 4 ++-- 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index ed67b134..cf277664 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -6,12 +6,9 @@ from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart -from cashu.mint.router import router as cashu_router - db = Database("ext_cashu") cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"]) -cashu_ext.include_router(router=cashu_router) def cashu_renderer(): return template_renderer(["lnbits/extensions/cashu/templates"]) diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index e1cdaf4e..2892e6a4 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -3,7 +3,7 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash from . import db -from .models import Cashu, Pegs +from .models import Cashu, Pegs, Proof from embit import script from embit import ec @@ -76,9 +76,7 @@ async def delete_cashu(cashu_id: str) -> None: async def store_promise( amount: int, B_: str, - C_: str, - db: Database, - conn: Optional[Connection] = None, + C_: str ): await (conn or db).execute( @@ -95,10 +93,7 @@ async def store_promise( ) -async def get_proofs_used( - db: Database, - conn: Optional[Connection] = None, -): +async def get_proofs_used(): rows = await (conn or db).fetchall( """ @@ -109,9 +104,7 @@ async def get_proofs_used( async def invalidate_proof( - proof: Proof, - db: Database, - conn: Optional[Connection] = None, + proof: Proof ): # we add the proof and secret to the used list diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py index cc5ef924..59cc3b92 100644 --- a/lnbits/extensions/cashu/ledger.py +++ b/lnbits/extensions/cashu/ledger.py @@ -1,22 +1,23 @@ import hashlib from typing import List, Set -from models import BlindedMessage, BlindedSignature, Invoice, Proof +from .models import BlindedMessage, BlindedSignature, Invoice, Proof from secp256k1 import PublicKey, PrivateKey +from fastapi import Query + from lnbits.core.services import check_transaction_status, create_invoice class Ledger: - def __init__(self, secret_key: str, db: str, MAX_ORDER: int = Query(64)): + def __init__(self, secret_key: str, MAX_ORDER: int = Query(64)): self.proofs_used: Set[str] = set() self.master_key: str = secret_key self.keys: List[PrivateKey] = self._derive_keys(self.master_key) self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys) - self.db: Database = Database("mint", db) async def load_used_proofs(self): - self.proofs_used = set(await get_proofs_used(db=self.db)) + self.proofs_used = set(await get_proofs_used) @staticmethod def _derive_keys(master_key: str): @@ -48,7 +49,7 @@ class Ledger: secret_key = self.keys[amount] # Get the correct key C_ = step2_bob(B_, secret_key) await store_promise( - amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db + amount, B_=B_.serialize().hex(), C_=C_.serialize().hex() ) return BlindedSignature(amount=amount, C_=C_.serialize().hex()) @@ -126,7 +127,7 @@ class Ledger: self.proofs_used |= proof_msgs # store in db for p in proofs: - await invalidate_proof(p, db=self.db) + await invalidate_proof(p) # Public methods def get_pubkeys(self): @@ -150,13 +151,13 @@ class Ledger: ) if not payment_request or not payment_hash: raise Exception(f"Could not create Lightning invoice.") - await store_lightning_invoice(invoice, db=self.db) + await store_lightning_invoice(invoice) return payment_request, payment_hash async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): """Mints a promise for coins for B_.""" # check if lightning invoice was paid - if payment_hash and not await check_transaction_status(ayment_hash) + if payment_hash and not await check_transaction_status(payment_hash): raise Exception("Lightning invoice not paid yet.") for amount in amounts: @@ -224,7 +225,7 @@ class Ledger: return prom_fst, prom_snd -#######FUNCTIONS############### +##############FUNCTIONS############### def fee_reserve(amount_msat: int) -> int: """Function for calculating the Lightning fee reserve""" return max( diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py index ba9303c5..a673dfe7 100644 --- a/lnbits/extensions/cashu/models.py +++ b/lnbits/extensions/cashu/models.py @@ -1,5 +1,5 @@ from sqlite3 import Row -from typing import Optional +from typing import Optional, List from fastapi import Query from pydantic import BaseModel @@ -31,6 +31,8 @@ class Pegs(BaseModel): def from_row(cls, row: Row) -> "TPoS": return cls(**dict(row)) +class PayLnurlWData(BaseModel): + lnurl: str class Proof(BaseModel): amount: int diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 5a1c500e..5cc6e271 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -16,9 +16,9 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import cashu_ext from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys -from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload +from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload, PayLnurlWData -import .ledger +from .ledger import Ledger, fee_reserve, amount_split, hash_to_point, step1_alice, step2_bob, step3_alice, verify @cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK) async def api_cashus( From f317a7cb2d9ddca205d38047d764b642cd3d17d2 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 3 Oct 2022 14:43:02 +0100 Subject: [PATCH 218/565] Working through, getting functions working --- lnbits/extensions/cashu/crud.py | 85 ++-- lnbits/extensions/cashu/ledger.py | 426 +++++++++--------- lnbits/extensions/cashu/migrations.py | 31 +- lnbits/extensions/cashu/models.py | 9 +- lnbits/extensions/cashu/tasks.py | 1 - .../cashu/templates/cashu/index.html | 7 +- lnbits/extensions/cashu/views.py | 1 - lnbits/extensions/cashu/views_api.py | 51 ++- 8 files changed, 354 insertions(+), 257 deletions(-) diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index 2892e6a4..7a9c25c3 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -1,24 +1,37 @@ +import os + from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash from . import db -from .models import Cashu, Pegs, Proof +from .models import Cashu, Pegs, Proof, Promises from embit import script from embit import ec from embit.networks import NETWORKS +from embit import bip32 +from embit import bip39 from binascii import unhexlify, hexlify +import random + +from loguru import logger async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: cashu_id = urlsafe_short_hash() - prv = ec.PrivateKey.from_wif(urlsafe_short_hash()) - pub = prv.get_public_key() + + entropy = bytes([random.getrandbits(8) for i in range(16)]) + mnemonic = bip39.mnemonic_from_bytes(entropy) + seed = bip39.mnemonic_to_seed(mnemonic) + root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) + + bip44_xprv = root.derive("m/44h/1h/0h") + bip44_xpub = bip44_xprv.to_public() await db.execute( """ INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins, prvkey, pubkey) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( cashu_id, @@ -28,8 +41,8 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: data.fraction, data.maxsats, data.coins, - prv, - pub + bip44_xprv.to_base58(), + bip44_xpub.to_base58() ), ) @@ -39,17 +52,20 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]: - if not wif: - prv = ec.PrivateKey.from_wif(urlsafe_short_hash()) - else: - prv = ec.PrivateKey.from_wif(wif) - pub = prv.get_public_key() - await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", (hexlify(prv.serialize()), hexlify(pub.serialize()), cashu_id)) + entropy = bytes([random.getrandbits(8) for i in range(16)]) + mnemonic = bip39.mnemonic_from_bytes(entropy) + seed = bip39.mnemonic_to_seed(mnemonic) + root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) + + bip44_xprv = root.derive("m/44h/1h/0h") + bip44_xpub = bip44_xprv.to_public() + + await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", bip44_xprv.to_base58(), bip44_xpub.to_base58(), cashu_id) row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) return Cashu(**row) if row else None -async def get_cashu(cashu_id: str) -> Optional[Cashu]: +async def get_cashu(cashu_id) -> Optional[Cashu]: row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) return Cashu(**row) if row else None @@ -66,57 +82,62 @@ async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]: return [Cashu(**row) for row in rows] -async def delete_cashu(cashu_id: str) -> None: +async def delete_cashu(cashu_id) -> None: await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,)) - +########################################## ###############MINT STUFF################# +########################################## async def store_promise( amount: int, B_: str, - C_: str + C_: str, + cashu_id ): + promise_id = urlsafe_short_hash() await (conn or db).execute( """ - INSERT INTO promises - (amount, B_b, C_b) - VALUES (?, ?, ?) + INSERT INTO cashu.promises + (id, amount, B_b, C_b, cashu_id) + VALUES (?, ?, ?, ?, ?) """, ( + promise_id, amount, str(B_), str(C_), + cashu_id ), ) +async def get_promises(cashu_id) -> Optional[Cashu]: + row = await db.fetchall("SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,)) + return Promises(**row) if row else None -async def get_proofs_used(): - - rows = await (conn or db).fetchall( - """ - SELECT secret from proofs_used - """ - ) +async def get_proofs_used(cashu_id): + rows = await db.fetchall("SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,)) return [row[0] for row in rows] async def invalidate_proof( - proof: Proof + proof: Proof, + cashu_id ): - - # we add the proof and secret to the used list + invalidate_proof_id = urlsafe_short_hash() await (conn or db).execute( """ - INSERT INTO proofs_used - (amount, C, secret) - VALUES (?, ?, ?) + INSERT INTO cashu.proofs_used + (id, amount, C, secret, cashu_id) + VALUES (?, ?, ?, ?, ?) """, ( + invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), + cashu_id ), ) \ No newline at end of file diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py index 59cc3b92..404f7ee8 100644 --- a/lnbits/extensions/cashu/ledger.py +++ b/lnbits/extensions/cashu/ledger.py @@ -5,234 +5,239 @@ from .models import BlindedMessage, BlindedSignature, Invoice, Proof from secp256k1 import PublicKey, PrivateKey from fastapi import Query - +from .crud import get_cashu from lnbits.core.services import check_transaction_status, create_invoice -class Ledger: - def __init__(self, secret_key: str, MAX_ORDER: int = Query(64)): - self.proofs_used: Set[str] = set() - - self.master_key: str = secret_key - self.keys: List[PrivateKey] = self._derive_keys(self.master_key) - self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys) - - async def load_used_proofs(self): - self.proofs_used = set(await get_proofs_used) - - @staticmethod - def _derive_keys(master_key: str): - """Deterministic derivation of keys for 2^n values.""" - return { - 2 - ** i: PrivateKey( - hashlib.sha256((str(master_key) + str(i)).encode("utf-8")) - .hexdigest() - .encode("utf-8")[:32], - raw=True, - ) - for i in range(MAX_ORDER) - } - - @staticmethod - def _derive_pubkeys(keys: List[PrivateKey]): - return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} - - async def _generate_promises(self, amounts: List[int], B_s: List[str]): - """Generates promises that sum to the given amount.""" - return [ - await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True)) - for (amount, B_) in zip(amounts, B_s) - ] - - async def _generate_promise(self, amount: int, B_: PublicKey): - """Generates a promise for given amount and returns a pair (amount, C').""" - secret_key = self.keys[amount] # Get the correct key - C_ = step2_bob(B_, secret_key) - await store_promise( - amount, B_=B_.serialize().hex(), C_=C_.serialize().hex() +def _derive_keys(master_key: str, cashu_id: str = Query(None)): + """Deterministic derivation of keys for 2^n values.""" + return { + 2 + ** i: PrivateKey( + hashlib.sha256((str(master_key) + str(i)).encode("utf-8")) + .hexdigest() + .encode("utf-8")[:32], + raw=True, ) - return BlindedSignature(amount=amount, C_=C_.serialize().hex()) + for i in range(MAX_ORDER) + } - def _check_spendable(self, proof: Proof): - """Checks whether the proof was already spent.""" - return not proof.secret in self.proofs_used +def _derive_pubkeys(keys: List[PrivateKey], cashu_id: str = Query(None)): + return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} - def _verify_proof(self, proof: Proof): - """Verifies that the proof of promise was issued by this ledger.""" - if not self._check_spendable(proof): - raise Exception(f"tokens already spent. Secret: {proof.secret}") - secret_key = self.keys[proof.amount] # Get the correct key to check against - C = PublicKey(bytes.fromhex(proof.C), raw=True) - return verify(secret_key, C, proof.secret) +async def _generate_promises(amounts: List[int], B_s: List[str], cashu_id: str = Query(None)): + """Generates promises that sum to the given amount.""" + return [ + await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True)) + for (amount, B_) in zip(amounts, B_s) + ] - def _verify_outputs( - self, total: int, amount: int, output_data: List[BlindedMessage] - ): - """Verifies the expected split was correctly computed""" - fst_amt, snd_amt = total - amount, amount # we have two amounts to split to - fst_outputs = amount_split(fst_amt) - snd_outputs = amount_split(snd_amt) - expected = fst_outputs + snd_outputs - given = [o.amount for o in output_data] - return given == expected +async def _generate_promise(amount: int, B_: PublicKey, cashu_id: str = Query(None)): + """Generates a promise for given amount and returns a pair (amount, C').""" + secret_key = self.keys[amount] # Get the correct key + C_ = step2_bob(B_, secret_key) + await store_promise( + amount, B_=B_.serialize().hex(), C_=C_.serialize().hex() + ) + return BlindedSignature(amount=amount, C_=C_.serialize().hex()) - def _verify_no_duplicates( - self, proofs: List[Proof], output_data: List[BlindedMessage] - ): - secrets = [p.secret for p in proofs] - if len(secrets) != len(list(set(secrets))): - return False - B_s = [od.B_ for od in output_data] - if len(B_s) != len(list(set(B_s))): - return False - return True +def _check_spendable(proof: Proof, cashu_id: str = Query(None)): + """Checks whether the proof was already spent.""" + return not proof.secret in self.proofs_used - def _verify_split_amount(self, amount: int): - """Split amount like output amount can't be negative or too big.""" - try: - self._verify_amount(amount) - except: - # For better error message - raise Exception("invalid split amount: " + str(amount)) +def _verify_proof(proof: Proof, cashu_id: str = Query(None)): + """Verifies that the proof of promise was issued by this ledger.""" + if not self._check_spendable(proof): + raise Exception(f"tokens already spent. Secret: {proof.secret}") + secret_key = self.keys[proof.amount] # Get the correct key to check against + C = PublicKey(bytes.fromhex(proof.C), raw=True) + return verify(secret_key, C, proof.secret) - def _verify_amount(self, amount: int): - """Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" - valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER - if not valid: - raise Exception("invalid amount: " + str(amount)) - return amount +def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)): + """Verifies the expected split was correctly computed""" + fst_amt, snd_amt = total - amount, amount # we have two amounts to split to + fst_outputs = amount_split(fst_amt) + snd_outputs = amount_split(snd_amt) + expected = fst_outputs + snd_outputs + given = [o.amount for o in output_data] + return given == expected - def _verify_equation_balanced( - self, proofs: List[Proof], outs: List[BlindedMessage] - ): - """Verify that Σoutputs - Σinputs = 0.""" - sum_inputs = sum(self._verify_amount(p.amount) for p in proofs) - sum_outputs = sum(self._verify_amount(p.amount) for p in outs) - assert sum_outputs - sum_inputs == 0 +def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)): + secrets = [p.secret for p in proofs] + if len(secrets) != len(list(set(secrets))): + return False + B_s = [od.B_ for od in output_data] + if len(B_s) != len(list(set(B_s))): + return False + return True - def _get_output_split(self, amount: int): - """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" +def _verify_split_amount(amount: int, cashu_id: str = Query(None)): + """Split amount like output amount can't be negative or too big.""" + try: self._verify_amount(amount) - bits_amt = bin(amount)[::-1][:-2] - rv = [] - for (pos, bit) in enumerate(bits_amt): - if bit == "1": - rv.append(2**pos) - return rv + except: + # For better error message + raise Exception("invalid split amount: " + str(amount)) - async def _invalidate_proofs(self, proofs: List[Proof]): - """Adds secrets of proofs to the list of knwon secrets and stores them in the db.""" - # Mark proofs as used and prepare new promises - proof_msgs = set([p.secret for p in proofs]) - self.proofs_used |= proof_msgs - # store in db - for p in proofs: - await invalidate_proof(p) +def _verify_amount(amount: int, cashu_id: str = Query(None)): + """Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" + valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER + if not valid: + raise Exception("invalid amount: " + str(amount)) + return amount - # Public methods - def get_pubkeys(self): - """Returns public keys for possible amounts.""" - return {a: p.serialize().hex() for a, p in self.pub_keys.items()} +def _verify_equation_balanced(proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)): + """Verify that Σoutputs - Σinputs = 0.""" + sum_inputs = sum(self._verify_amount(p.amount) for p in proofs) + sum_outputs = sum(self._verify_amount(p.amount) for p in outs) + assert sum_outputs - sum_inputs == 0 - async def request_mint(self, amount): - """Returns Lightning invoice and stores it in the db.""" - payment_request, payment_hash = payment_hash, payment_request = await create_invoice( - wallet_id=link.wallet, - amount=amount, - memo=link.description, - unhashed_description=link.description.encode("utf-8"), - extra={ - "tag": "Cashu" - }, - ) +def _get_output_split(amount: int, cashu_id: str): + """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" + self._verify_amount(amount) + bits_amt = bin(amount)[::-1][:-2] + rv = [] + for (pos, bit) in enumerate(bits_amt): + if bit == "1": + rv.append(2**pos) + return rv - invoice = Invoice( - amount=amount, pr=payment_request, hash=payment_hash, issued=False - ) - if not payment_request or not payment_hash: - raise Exception(f"Could not create Lightning invoice.") - await store_lightning_invoice(invoice) - return payment_request, payment_hash +async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)): + """Adds secrets of proofs to the list of knwon secrets and stores them in the db.""" + # Mark proofs as used and prepare new promises + proof_msgs = set([p.secret for p in proofs]) + self.proofs_used |= proof_msgs + # store in db + for p in proofs: + await invalidate_proof(p) - async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): - """Mints a promise for coins for B_.""" - # check if lightning invoice was paid - if payment_hash and not await check_transaction_status(payment_hash): +def get_pubkeys(cashu_id: str = Query(None)): + """Returns public keys for possible amounts.""" + return {a: p.serialize().hex() for a, p in self.pub_keys.items()} + +async def request_mint(amount, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + + """Returns Lightning invoice and stores it in the db.""" + payment_hash, payment_request = await create_invoice( + wallet_id=cashu.wallet, + amount=amount, + memo=cashu.name, + unhashed_description=cashu.name.encode("utf-8"), + extra={ + "tag": "Cashu" + }, + ) + + invoice = Invoice( + amount=amount, pr=payment_request, hash=payment_hash, issued=False + ) + if not payment_request or not payment_hash: + raise Exception(f"Could not create Lightning invoice.") + return payment_request, payment_hash + +async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Query(None), cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + + """Mints a promise for coins for B_.""" + # check if lightning invoice was paid + if payment_hash: + if not await check_transaction_status(wallet_id=cashu.wallet, payment_hash=payment_hash): raise Exception("Lightning invoice not paid yet.") - for amount in amounts: - if amount not in [2**i for i in range(MAX_ORDER)]: - raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") + for amount in amounts: + if amount not in [2**i for i in range(MAX_ORDER)]: + raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") - promises = [ - await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) - ] - return promises + promises = [ + await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) + ] + return promises - async def melt(self, proofs: List[Proof], amount: int, invoice: str): - """Invalidates proofs and pays a Lightning invoice.""" - # if not LIGHTNING: - total = sum([p["amount"] for p in proofs]) - # check that lightning fees are included - assert total + fee_reserve(amount * 1000) >= amount, Exception( - "provided proofs not enough for Lightning payment." - ) +async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + + """Invalidates proofs and pays a Lightning invoice.""" + # if not LIGHTNING: + total = sum([p["amount"] for p in proofs]) + # check that lightning fees are included + assert total + fee_reserve(amount * 1000) >= amount, Exception( + "provided proofs not enough for Lightning payment." + ) - status, payment_hash = await pay_invoice( - wallet_id=link.wallet, - payment_request=invoice, - max_sat=amount, - extra={"tag": "Ecash melt"}, - ) + status, payment_hash = await pay_invoice( + wallet_id=link.wallet, + payment_request=invoice, + max_sat=amount, + extra={"tag": "Ecash melt"}, + ) - if status == True: - await self._invalidate_proofs(proofs) - return status, payment_hash - - async def check_spendable(self, proofs: List[Proof]): - """Checks if all provided proofs are valid and still spendable (i.e. have not been spent).""" - return {i: self._check_spendable(p) for i, p in enumerate(proofs)} - - async def split( - self, proofs: List[Proof], amount: int, output_data: List[BlindedMessage] - ): - """Consumes proofs and prepares new promises based on the amount split.""" - self._verify_split_amount(amount) - # Verify proofs are valid - if not all([self._verify_proof(p) for p in proofs]): - return False - - total = sum([p.amount for p in proofs]) - - if not self._verify_no_duplicates(proofs, output_data): - raise Exception("duplicate proofs or promises") - if amount > total: - raise Exception("split amount is higher than the total sum") - if not self._verify_outputs(total, amount, output_data): - raise Exception("split of promises is not as expected") - - # Mark proofs as used and prepare new promises + if status == True: await self._invalidate_proofs(proofs) + return status, payment_hash - outs_fst = amount_split(total - amount) - outs_snd = amount_split(amount) - B_fst = [od.B_ for od in output_data[: len(outs_fst)]] - B_snd = [od.B_ for od in output_data[len(outs_fst) :]] - prom_fst, prom_snd = await self._generate_promises( - outs_fst, B_fst - ), await self._generate_promises(outs_snd, B_snd) - self._verify_equation_balanced(proofs, prom_fst + prom_snd) - return prom_fst, prom_snd +async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + + """Checks if all provided proofs are valid and still spendable (i.e. have not been spent).""" + return {i: self._check_spendable(p) for i, p in enumerate(proofs)} + +async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + + """Consumes proofs and prepares new promises based on the amount split.""" + self._verify_split_amount(amount) + # Verify proofs are valid + if not all([self._verify_proof(p) for p in proofs]): + return False + + total = sum([p.amount for p in proofs]) + + if not self._verify_no_duplicates(proofs, output_data): + raise Exception("duplicate proofs or promises") + if amount > total: + raise Exception("split amount is higher than the total sum") + if not self._verify_outputs(total, amount, output_data): + raise Exception("split of promises is not as expected") + + # Mark proofs as used and prepare new promises + await self._invalidate_proofs(proofs) + + outs_fst = amount_split(total - amount) + outs_snd = amount_split(amount) + B_fst = [od.B_ for od in output_data[: len(outs_fst)]] + B_snd = [od.B_ for od in output_data[len(outs_fst) :]] + prom_fst, prom_snd = await self._generate_promises( + outs_fst, B_fst + ), await self._generate_promises(outs_snd, B_snd) + self._verify_equation_balanced(proofs, prom_fst + prom_snd) + return prom_fst, prom_snd -##############FUNCTIONS############### -def fee_reserve(amount_msat: int) -> int: +async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + """Function for calculating the Lightning fee reserve""" return max( int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0) ) -def amount_split(amount): +async def amount_split(amount, cashu_id: str): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" bits_amt = bin(amount)[::-1][:-2] rv = [] @@ -241,7 +246,11 @@ def amount_split(amount): rv.append(2**pos) return rv -def hash_to_point(secret_msg): +async def hash_to_point(secret_msg, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + """Generates x coordinate from the message hash and checks if the point lies on the curve. If it does not, it tries computing again a new x coordinate from the hash of the coordinate.""" point = None @@ -260,24 +269,39 @@ def hash_to_point(secret_msg): return point -def step1_alice(secret_msg): +async def step1_alice(secret_msg, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + secret_msg = secret_msg.encode("utf-8") Y = hash_to_point(secret_msg) r = PrivateKey() B_ = Y + r.pubkey return B_, r +async def step2_bob(B_, a, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") -def step2_bob(B_, a): C_ = B_.mult(a) return C_ -def step3_alice(C_, r, A): +async def step3_alice(C_, r, A, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + C = C_ - A.mult(r) return C -def verify(a, C, secret_msg): +async def verify(a, C, secret_msg, cashu_id: str = Query(None)): + cashu = await get_cashu(cashu_id) + if not cashu: + raise Exception(f"Could not find Cashu") + Y = hash_to_point(secret_msg.encode("utf-8")) return C == Y.mult(a) diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py index 53420062..f7d8f4f0 100644 --- a/lnbits/extensions/cashu/migrations.py +++ b/lnbits/extensions/cashu/migrations.py @@ -8,7 +8,7 @@ async def m001_initial(db): id TEXT PRIMARY KEY, wallet TEXT NOT NULL, name TEXT NOT NULL, - tickershort TEXT NOT NULL, + tickershort TEXT DEFAULT 'sats', fraction BOOL, maxsats INT, coins INT, @@ -32,3 +32,32 @@ async def m001_initial(db): """ ) + """ + Initial cashus table. + """ + await db.execute( + """ + CREATE TABLE cashu.promises ( + id TEXT PRIMARY KEY, + amount INT, + B_b TEXT NOT NULL, + C_b TEXT NOT NULL, + cashu_id TEXT NOT NULL + ); + """ + ) + + """ + Initial cashus table. + """ + await db.execute( + """ + CREATE TABLE cashu.proofs_used ( + id TEXT PRIMARY KEY, + amount INT, + C TEXT NOT NULL, + secret TEXT NOT NULL, + cashu_id TEXT NOT NULL + ); + """ + ) \ No newline at end of file diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py index a673dfe7..094966ff 100644 --- a/lnbits/extensions/cashu/models.py +++ b/lnbits/extensions/cashu/models.py @@ -9,7 +9,7 @@ class Cashu(BaseModel): id: str = Query(None) name: str = Query(None) wallet: str = Query(None) - tickershort: str + tickershort: str = Query(None) fraction: bool = Query(None) maxsats: int = Query(0) coins: int = Query(0) @@ -34,6 +34,13 @@ class Pegs(BaseModel): class PayLnurlWData(BaseModel): lnurl: str +class Promises(BaseModel): + id: str + amount: int + B_b: str + C_b: str + cashu_id: str + class Proof(BaseModel): amount: int secret: str diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py index fe00a591..5fbdde8e 100644 --- a/lnbits/extensions/cashu/tasks.py +++ b/lnbits/extensions/cashu/tasks.py @@ -9,7 +9,6 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener from .crud import get_cashu - async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() register_invoice_listener(invoice_queue) diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html index 17b2a919..3cd57d45 100644 --- a/lnbits/extensions/cashu/templates/cashu/index.html +++ b/lnbits/extensions/cashu/templates/cashu/index.html @@ -80,13 +80,10 @@ -
-
@@ -96,6 +93,8 @@
+
Create Mint + :disable="formDialog.data.wallet == null || formDialog.data.name == null" type="submit">Create Mint Cancel
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py index 4ac1f1ce..655ed028 100644 --- a/lnbits/extensions/cashu/views.py +++ b/lnbits/extensions/cashu/views.py @@ -22,7 +22,6 @@ async def index(request: Request, user: User = Depends(check_user_exists)): "cashu/index.html", {"request": request, "user": user.dict()} ) - @cashu_ext.get("/wallet") async def cashu(request: Request): return cashu_renderer().TemplateResponse("cashu/wallet.html",{"request": request}) diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 5cc6e271..391aeda1 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -15,10 +15,25 @@ from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import cashu_ext -from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys -from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload, PayLnurlWData +from .ledger import get_pubkeys, request_mint, mint -from .ledger import Ledger, fee_reserve, amount_split, hash_to_point, step1_alice, step2_bob, step3_alice, verify +from .crud import ( + create_cashu, + delete_cashu, + get_cashu, + get_cashus, + update_cashu_keys +) + +from .models import ( + Cashu, + Pegs, + CheckPayload, + MeltPayload, + MintPayloads, + SplitPayload, + PayLnurlWData +) @cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK) async def api_cashus( @@ -173,50 +188,54 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str): return status -#################CASHU STUFF################### +######################################## +#################MINT################### +######################################## @cashu_ext.get("/keys") -def keys(): +def keys(cashu_id: str): """Get the public keys of the mint""" - return ledger.get_pubkeys() + return get_pubkeys(cashu_id) @cashu_ext.get("/mint") -async def request_mint(amount: int = 0): +async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)): """Request minting of tokens. Server responds with a Lightning invoice.""" - payment_request, payment_hash = await ledger.request_mint(amount) + payment_request, payment_hash = await request_mint(amount, cashu_id) print(f"Lightning invoice: {payment_request}") return {"pr": payment_request, "hash": payment_hash} @cashu_ext.post("/mint") -async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None): +async def mint_coins(payloads: MintPayloads, payment_hash: Union[str, None] = None, cashu_id: str = Query(None)): amounts = [] B_s = [] for payload in payloads.blinded_messages: amounts.append(payload.amount) B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True)) + promises = await mint(B_s, amounts, payment_hash, cashu_id) + logger.debug(promises) try: - promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash) + promises = await mint(B_s, amounts, payment_hash, cashu_id) return promises except Exception as exc: return {"error": str(exc)} @cashu_ext.post("/melt") -async def melt(payload: MeltPayload): +async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)): - ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice) + ok, preimage = await melt(payload.proofs, payload.amount, payload.invoice, cashu_id) return {"paid": ok, "preimage": preimage} @cashu_ext.post("/check") -async def check_spendable(payload: CheckPayload): - return await ledger.check_spendable(payload.proofs) +async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)): + return await check_spendable(payload.proofs, cashu_id) @cashu_ext.post("/split") -async def split(payload: SplitPayload): +async def spli_coinst(payload: SplitPayload, cashu_id: str = Query(None)): """ Requetst a set of tokens with amount "total" to be split into two newly minted sets with amount "split" and "total-split". @@ -225,7 +244,7 @@ async def split(payload: SplitPayload): amount = payload.amount output_data = payload.output_data.blinded_messages try: - split_return = await ledger.split(proofs, amount, output_data) + split_return = await split(proofs, amount, output_data) except Exception as exc: return {"error": str(exc)} if not split_return: From b7cd0f4d453c878012333938e52436ba69f06a29 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 7 Oct 2022 10:09:16 +0300 Subject: [PATCH 219/565] feat: generate keys --- lnbits/extensions/cashu/core/secp.py | 52 ++++++++++++++++++++++++++ lnbits/extensions/cashu/mint.py | 12 ++++++ lnbits/extensions/cashu/mint_helper.py | 22 +++++++++++ lnbits/extensions/cashu/views_api.py | 23 ++++++++++-- 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 lnbits/extensions/cashu/core/secp.py create mode 100644 lnbits/extensions/cashu/mint.py create mode 100644 lnbits/extensions/cashu/mint_helper.py diff --git a/lnbits/extensions/cashu/core/secp.py b/lnbits/extensions/cashu/core/secp.py new file mode 100644 index 00000000..33416434 --- /dev/null +++ b/lnbits/extensions/cashu/core/secp.py @@ -0,0 +1,52 @@ +from secp256k1 import PrivateKey, PublicKey + + +# We extend the public key to define some operations on points +# Picked from https://github.com/WTRMQDev/secp256k1-zkp-py/blob/master/secp256k1_zkp/__init__.py +class PublicKeyExt(PublicKey): + def __add__(self, pubkey2): + if isinstance(pubkey2, PublicKey): + new_pub = PublicKey() + new_pub.combine([self.public_key, pubkey2.public_key]) + return new_pub + else: + raise TypeError("Cant add pubkey and %s" % pubkey2.__class__) + + def __neg__(self): + serialized = self.serialize() + first_byte, remainder = serialized[:1], serialized[1:] + # flip odd/even byte + first_byte = {b"\x03": b"\x02", b"\x02": b"\x03"}[first_byte] + return PublicKey(first_byte + remainder, raw=True) + + def __sub__(self, pubkey2): + if isinstance(pubkey2, PublicKey): + return self + (-pubkey2) + else: + raise TypeError("Can't add pubkey and %s" % pubkey2.__class__) + + def mult(self, privkey): + if isinstance(privkey, PrivateKey): + return self.tweak_mul(privkey.private_key) + else: + raise TypeError("Can't multiply with non privatekey") + + def __eq__(self, pubkey2): + if isinstance(pubkey2, PublicKey): + seq1 = self.to_data() + seq2 = pubkey2.to_data() + return seq1 == seq2 + else: + raise TypeError("Can't compare pubkey and %s" % pubkey2.__class__) + + def to_data(self): + return [self.public_key.data[i] for i in range(64)] + + +# Horrible monkeypatching +PublicKey.__add__ = PublicKeyExt.__add__ +PublicKey.__neg__ = PublicKeyExt.__neg__ +PublicKey.__sub__ = PublicKeyExt.__sub__ +PublicKey.mult = PublicKeyExt.mult +PublicKey.__eq__ = PublicKeyExt.__eq__ +PublicKey.to_data = PublicKeyExt.to_data diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py new file mode 100644 index 00000000..703bf426 --- /dev/null +++ b/lnbits/extensions/cashu/mint.py @@ -0,0 +1,12 @@ + +from .crud import get_cashu +from .mint_helper import derive_keys, derive_pubkeys + + +def get_pubkeys(xpriv: str): + """Returns public keys for possible amounts.""" + + keys = derive_keys(xpriv) + pub_keys = derive_pubkeys(keys) + + return {a: p.serialize().hex() for a, p in pub_keys.items()} \ No newline at end of file diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py new file mode 100644 index 00000000..30e66b03 --- /dev/null +++ b/lnbits/extensions/cashu/mint_helper.py @@ -0,0 +1,22 @@ +import hashlib +from typing import List, Set +from .core.secp import PrivateKey, PublicKey + +# todo: extract const +MAX_ORDER = 64 + +def derive_keys(master_key: str): + """Deterministic derivation of keys for 2^n values.""" + return { + 2 + ** i: PrivateKey( + hashlib.sha256((str(master_key) + str(i)).encode("utf-8")) + .hexdigest() + .encode("utf-8")[:32], + raw=True, + ) + for i in range(MAX_ORDER) + } + +def derive_pubkeys(keys: List[PrivateKey]): + return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} \ No newline at end of file diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 391aeda1..65323715 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -15,7 +15,8 @@ from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import cashu_ext -from .ledger import get_pubkeys, request_mint, mint +from .ledger import request_mint, mint +from .mint import get_pubkeys from .crud import ( create_cashu, @@ -35,6 +36,11 @@ from .models import ( PayLnurlWData ) +######################################## +#################MINT CRUD############## +######################################## + +# todo: use /mints @cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK) async def api_cashus( all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) @@ -83,6 +89,9 @@ async def api_cashu_delete( raise HTTPException(status_code=HTTPStatus.NO_CONTENT) +######################################## +#################????################### +######################################## @cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED) async def api_cashu_create_invoice( amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None @@ -192,10 +201,16 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str): #################MINT################### ######################################## -@cashu_ext.get("/keys") -def keys(cashu_id: str): +@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK) +async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)): """Get the public keys of the mint""" - return get_pubkeys(cashu_id) + print('############################') + mint = await get_cashu(cashu_id) + if mint is None: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist." + ) + return get_pubkeys(mint.prvkey) @cashu_ext.get("/mint") From 7720525eb82ffd9190f8f46133a63de7baebb774 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 7 Oct 2022 10:23:42 +0300 Subject: [PATCH 220/565] feat: generate invoice for amount --- lnbits/extensions/cashu/mint.py | 15 +++++++++++++-- lnbits/extensions/cashu/views_api.py | 22 +++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py index 703bf426..70b6895e 100644 --- a/lnbits/extensions/cashu/mint.py +++ b/lnbits/extensions/cashu/mint.py @@ -1,5 +1,5 @@ -from .crud import get_cashu +from .models import Cashu from .mint_helper import derive_keys, derive_pubkeys @@ -9,4 +9,15 @@ def get_pubkeys(xpriv: str): keys = derive_keys(xpriv) pub_keys = derive_pubkeys(keys) - return {a: p.serialize().hex() for a, p in pub_keys.items()} \ No newline at end of file + return {a: p.serialize().hex() for a, p in pub_keys.items()} + +async def request_mint(mint: Cashu, amount): + """Returns Lightning invoice and stores it in the db.""" + payment_request, checking_id = await self._request_lightning_invoice(amount) + invoice = Invoice( + amount=amount, pr=payment_request, hash=checking_id, issued=False + ) + if not payment_request or not checking_id: + raise Exception(f"Could not create Lightning invoice.") + await store_lightning_invoice(invoice, db=self.db) + return payment_request, checking_id \ No newline at end of file diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 65323715..a4f8a2d8 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -204,7 +204,6 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str): @cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK) async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)): """Get the public keys of the mint""" - print('############################') mint = await get_cashu(cashu_id) if mint is None: raise HTTPException( @@ -213,10 +212,27 @@ async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(ge return get_pubkeys(mint.prvkey) -@cashu_ext.get("/mint") +@cashu_ext.get("/api/v1/mint/{cashu_id}") async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)): """Request minting of tokens. Server responds with a Lightning invoice.""" - payment_request, payment_hash = await request_mint(amount, cashu_id) + print('############################') + cashu = await get_cashu(cashu_id) + if cashu is None: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist." + ) + + try: + payment_hash, payment_request = await create_invoice( + wallet_id=cashu.wallet, + amount=amount, + memo=f"{cashu.name}", + extra={"tag": "cashu"}, + ) + except Exception as e: + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) + + print(f"Lightning invoice: {payment_request}") return {"pr": payment_request, "hash": payment_hash} From 2c54c240ba316e8ffa4457e7cdc56faf072b2f75 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 7 Oct 2022 11:08:46 +0300 Subject: [PATCH 221/565] feat: store generated invoices per mint --- lnbits/extensions/cashu/core/base.py | 167 ++++++++++++++++++++++++++ lnbits/extensions/cashu/crud.py | 52 +++++++- lnbits/extensions/cashu/migrations.py | 15 +++ lnbits/extensions/cashu/mint.py | 29 +++-- lnbits/extensions/cashu/views_api.py | 42 ++++--- 5 files changed, 276 insertions(+), 29 deletions(-) create mode 100644 lnbits/extensions/cashu/core/base.py diff --git a/lnbits/extensions/cashu/core/base.py b/lnbits/extensions/cashu/core/base.py new file mode 100644 index 00000000..0aa02442 --- /dev/null +++ b/lnbits/extensions/cashu/core/base.py @@ -0,0 +1,167 @@ +from sqlite3 import Row +from typing import List, Union + +from pydantic import BaseModel + + +class CashuError(BaseModel): + code = "000" + error = "CashuError" + + +class P2SHScript(BaseModel): + script: str + signature: str + address: Union[str, None] = None + + @classmethod + def from_row(cls, row: Row): + return cls( + address=row[0], + script=row[1], + signature=row[2], + used=row[3], + ) + + +class Proof(BaseModel): + amount: int + secret: str = "" + C: str + script: Union[P2SHScript, None] = None + reserved: bool = False # whether this proof is reserved for sending + send_id: str = "" # unique ID of send attempt + time_created: str = "" + time_reserved: str = "" + + @classmethod + def from_row(cls, row: Row): + return cls( + amount=row[0], + C=row[1], + secret=row[2], + reserved=row[3] or False, + send_id=row[4] or "", + time_created=row[5] or "", + time_reserved=row[6] or "", + ) + + @classmethod + def from_dict(cls, d: dict): + assert "amount" in d, "no amount in proof" + return cls( + amount=d.get("amount"), + C=d.get("C"), + secret=d.get("secret") or "", + reserved=d.get("reserved") or False, + send_id=d.get("send_id") or "", + time_created=d.get("time_created") or "", + time_reserved=d.get("time_reserved") or "", + ) + + def to_dict(self): + return dict(amount=self.amount, secret=self.secret, C=self.C) + + def to_dict_no_secret(self): + return dict(amount=self.amount, C=self.C) + + def __getitem__(self, key): + return self.__getattribute__(key) + + def __setitem__(self, key, val): + self.__setattr__(key, val) + + +class Proofs(BaseModel): + """TODO: Use this model""" + + proofs: List[Proof] + + +class Invoice(BaseModel): + amount: int + pr: str + hash: str + issued: bool = False + + @classmethod + def from_row(cls, row: Row): + return cls( + amount=int(row[0]), + pr=str(row[1]), + hash=str(row[2]), + issued=bool(row[3]), + ) + + +class BlindedMessage(BaseModel): + amount: int + B_: str + + +class BlindedSignature(BaseModel): + amount: int + C_: str + + @classmethod + def from_dict(cls, d: dict): + return cls( + amount=d["amount"], + C_=d["C_"], + ) + + +class MintRequest(BaseModel): + blinded_messages: List[BlindedMessage] = [] + + +class GetMintResponse(BaseModel): + pr: str + hash: str + + +class GetMeltResponse(BaseModel): + paid: Union[bool, None] + preimage: Union[str, None] + + +class SplitRequest(BaseModel): + proofs: List[Proof] + amount: int + output_data: Union[ + MintRequest, None + ] = None # backwards compatibility with clients < v0.2.2 + outputs: Union[MintRequest, None] = None + + def __init__(self, **data): + super().__init__(**data) + self.backwards_compatibility_v021() + + def backwards_compatibility_v021(self): + # before v0.2.2: output_data, after: outputs + if self.output_data: + self.outputs = self.output_data + self.output_data = None + + +class PostSplitResponse(BaseModel): + fst: List[BlindedSignature] + snd: List[BlindedSignature] + + +class CheckRequest(BaseModel): + proofs: List[Proof] + + +class CheckFeesRequest(BaseModel): + pr: str + + +class CheckFeesResponse(BaseModel): + fee: Union[int, None] + + +class MeltRequest(BaseModel): + proofs: List[Proof] + amount: int = None # deprecated + invoice: str diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index 7a9c25c3..c991a8ec 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -1,6 +1,7 @@ import os from typing import List, Optional, Union +from .core.base import Invoice from lnbits.helpers import urlsafe_short_hash @@ -98,7 +99,7 @@ async def store_promise( ): promise_id = urlsafe_short_hash() - await (conn or db).execute( + await db.execute( """ INSERT INTO cashu.promises (id, amount, B_b, C_b, cashu_id) @@ -140,4 +141,51 @@ async def invalidate_proof( str(proof.secret), cashu_id ), - ) \ No newline at end of file + ) + + + + + + +######################################## +############ MINT INVOICES ############# +######################################## + + +async def store_lightning_invoice(cashu_id: str, invoice: Invoice): + await db.execute( + """ + INSERT INTO cashu.invoices + (cashu_id, amount, pr, hash, issued) + VALUES (?, ?, ?, ?, ?) + """, + ( + cashu_id, + invoice.amount, + invoice.pr, + invoice.hash, + invoice.issued, + ), + ) + +async def get_lightning_invoice(cashu_id: str, hash: str): + row = await db.fetchone( + """ + SELECT * from invoices + WHERE cashu_id =? AND hash = ? + """, + (cashu_id, hash,), + ) + return Invoice.from_row(row) + + +async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool): + await db.execute( + "UPDATE invoices SET issued = ? WHERE cashu_id = ? AND hash = ?", + ( + issued, + cashu_id, + hash, + ), + ) diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py index f7d8f4f0..3f1df660 100644 --- a/lnbits/extensions/cashu/migrations.py +++ b/lnbits/extensions/cashu/migrations.py @@ -60,4 +60,19 @@ async def m001_initial(db): cashu_id TEXT NOT NULL ); """ + ) + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS cashu.invoices ( + cashu_id TEXT NOT NULL, + amount INTEGER NOT NULL, + pr TEXT NOT NULL, + hash TEXT NOT NULL, + issued BOOL NOT NULL, + + UNIQUE (hash) + + ); + """ ) \ No newline at end of file diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py index 70b6895e..fca096ed 100644 --- a/lnbits/extensions/cashu/mint.py +++ b/lnbits/extensions/cashu/mint.py @@ -11,13 +11,22 @@ def get_pubkeys(xpriv: str): return {a: p.serialize().hex() for a, p in pub_keys.items()} -async def request_mint(mint: Cashu, amount): - """Returns Lightning invoice and stores it in the db.""" - payment_request, checking_id = await self._request_lightning_invoice(amount) - invoice = Invoice( - amount=amount, pr=payment_request, hash=checking_id, issued=False - ) - if not payment_request or not checking_id: - raise Exception(f"Could not create Lightning invoice.") - await store_lightning_invoice(invoice, db=self.db) - return payment_request, checking_id \ No newline at end of file +# async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): +# """Mints a promise for coins for B_.""" +# # check if lightning invoice was paid +# if LIGHTNING: +# try: +# paid = await self._check_lightning_invoice(payment_hash) +# except: +# raise Exception("could not check invoice.") +# if not paid: +# raise Exception("Lightning invoice not paid yet.") + +# for amount in amounts: +# if amount not in [2**i for i in range(MAX_ORDER)]: +# raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") + +# promises = [ +# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) +# ] +# return promises \ No newline at end of file diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index a4f8a2d8..c0bc59f3 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -13,6 +13,7 @@ from lnbits.core.crud import get_user from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key +from .core.base import CashuError from . import cashu_ext from .ledger import request_mint, mint @@ -22,12 +23,13 @@ from .crud import ( create_cashu, delete_cashu, get_cashu, - get_cashus, - update_cashu_keys + get_cashus, + store_lightning_invoice, ) from .models import ( - Cashu, + Cashu, + Invoice, Pegs, CheckPayload, MeltPayload, @@ -215,7 +217,7 @@ async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(ge @cashu_ext.get("/api/v1/mint/{cashu_id}") async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)): """Request minting of tokens. Server responds with a Lightning invoice.""" - print('############################') + print('############################ amount', amount) cashu = await get_cashu(cashu_id) if cashu is None: raise HTTPException( @@ -229,28 +231,34 @@ async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)): memo=f"{cashu.name}", extra={"tag": "cashu"}, ) + invoice = Invoice( + amount=amount, pr=payment_request, hash=payment_hash, issued=False + ) + await store_lightning_invoice(cashu_id, invoice) except Exception as e: - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) + logger.error(e) + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id)) - - print(f"Lightning invoice: {payment_request}") return {"pr": payment_request, "hash": payment_hash} @cashu_ext.post("/mint") async def mint_coins(payloads: MintPayloads, payment_hash: Union[str, None] = None, cashu_id: str = Query(None)): + """ + Requests the minting of tokens belonging to a paid payment request. + + Call this endpoint after `GET /mint`. + """ amounts = [] B_s = [] - for payload in payloads.blinded_messages: - amounts.append(payload.amount) - B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True)) - promises = await mint(B_s, amounts, payment_hash, cashu_id) - logger.debug(promises) - try: - promises = await mint(B_s, amounts, payment_hash, cashu_id) - return promises - except Exception as exc: - return {"error": str(exc)} + # for payload in payloads.blinded_messages: + # amounts.append(payload.amount) + # B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True)) + # try: + # promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash) + # return promises + # except Exception as exc: + # return CashuError(error=str(exc)) @cashu_ext.post("/melt") From 104aca33ff875bf676661f89255454cb3e4d898c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 7 Oct 2022 11:17:02 +0300 Subject: [PATCH 222/565] chore: format --- lnbits/extensions/cashu/__init__.py | 3 + lnbits/extensions/cashu/crud.py | 84 +- lnbits/extensions/cashu/ledger.py | 100 +- lnbits/extensions/cashu/migrations.py | 2 +- lnbits/extensions/cashu/mint.py | 8 +- lnbits/extensions/cashu/mint_helper.py | 5 +- lnbits/extensions/cashu/models.py | 9 +- lnbits/extensions/cashu/tasks.py | 1 + .../cashu/templates/cashu/_api_docs.html | 3 +- .../cashu/templates/cashu/_cashu.html | 7 +- .../cashu/templates/cashu/index.html | 160 ++- .../cashu/templates/cashu/mint.html | 13 +- .../cashu/templates/cashu/wallet.html | 1104 ++++++++++------- lnbits/extensions/cashu/views.py | 9 +- lnbits/extensions/cashu/views_api.py | 94 +- 15 files changed, 992 insertions(+), 610 deletions(-) diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index cf277664..bd7d5513 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -10,13 +10,16 @@ db = Database("ext_cashu") cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"]) + def cashu_renderer(): return template_renderer(["lnbits/extensions/cashu/templates"]) + from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa + def cashu_start(): loop = asyncio.get_event_loop() loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index c991a8ec..448614ac 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -1,22 +1,18 @@ import os - +import random +from binascii import hexlify, unhexlify from typing import List, Optional, Union -from .core.base import Invoice + +from embit import bip32, bip39, ec, script +from embit.networks import NETWORKS +from loguru import logger from lnbits.helpers import urlsafe_short_hash from . import db -from .models import Cashu, Pegs, Proof, Promises +from .core.base import Invoice +from .models import Cashu, Pegs, Promises, Proof -from embit import script -from embit import ec -from embit.networks import NETWORKS -from embit import bip32 -from embit import bip39 -from binascii import unhexlify, hexlify -import random - -from loguru import logger async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: cashu_id = urlsafe_short_hash() @@ -25,7 +21,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: mnemonic = bip39.mnemonic_from_bytes(entropy) seed = bip39.mnemonic_to_seed(mnemonic) root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) - + bip44_xprv = root.derive("m/44h/1h/0h") bip44_xpub = bip44_xprv.to_public() @@ -43,7 +39,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu: data.maxsats, data.coins, bip44_xprv.to_base58(), - bip44_xpub.to_base58() + bip44_xpub.to_base58(), ), ) @@ -57,11 +53,16 @@ async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]: mnemonic = bip39.mnemonic_from_bytes(entropy) seed = bip39.mnemonic_to_seed(mnemonic) root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) - + bip44_xprv = root.derive("m/44h/1h/0h") bip44_xpub = bip44_xprv.to_public() - await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", bip44_xprv.to_base58(), bip44_xpub.to_base58(), cashu_id) + await db.execute( + "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", + bip44_xprv.to_base58(), + bip44_xpub.to_base58(), + cashu_id, + ) row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) return Cashu(**row) if row else None @@ -91,12 +92,8 @@ async def delete_cashu(cashu_id) -> None: ###############MINT STUFF################# ########################################## -async def store_promise( - amount: int, - B_: str, - C_: str, - cashu_id -): + +async def store_promise(amount: int, B_: str, C_: str, cashu_id): promise_id = urlsafe_short_hash() await db.execute( @@ -105,28 +102,25 @@ async def store_promise( (id, amount, B_b, C_b, cashu_id) VALUES (?, ?, ?, ?, ?) """, - ( - promise_id, - amount, - str(B_), - str(C_), - cashu_id - ), + (promise_id, amount, str(B_), str(C_), cashu_id), ) + async def get_promises(cashu_id) -> Optional[Cashu]: - row = await db.fetchall("SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,)) + row = await db.fetchall( + "SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,) + ) return Promises(**row) if row else None + async def get_proofs_used(cashu_id): - rows = await db.fetchall("SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,)) + rows = await db.fetchall( + "SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,) + ) return [row[0] for row in rows] -async def invalidate_proof( - proof: Proof, - cashu_id -): +async def invalidate_proof(proof: Proof, cashu_id): invalidate_proof_id = urlsafe_short_hash() await (conn or db).execute( """ @@ -134,20 +128,10 @@ async def invalidate_proof( (id, amount, C, secret, cashu_id) VALUES (?, ?, ?, ?, ?) """, - ( - invalidate_proof_id, - proof.amount, - str(proof.C), - str(proof.secret), - cashu_id - ), + (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id), ) - - - - ######################################## ############ MINT INVOICES ############# ######################################## @@ -169,18 +153,22 @@ async def store_lightning_invoice(cashu_id: str, invoice: Invoice): ), ) + async def get_lightning_invoice(cashu_id: str, hash: str): row = await db.fetchone( """ SELECT * from invoices WHERE cashu_id =? AND hash = ? """, - (cashu_id, hash,), + ( + cashu_id, + hash, + ), ) return Invoice.from_row(row) -async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool): +async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool): await db.execute( "UPDATE invoices SET issued = ? WHERE cashu_id = ? AND hash = ?", ( diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py index 404f7ee8..a28dc97a 100644 --- a/lnbits/extensions/cashu/ledger.py +++ b/lnbits/extensions/cashu/ledger.py @@ -1,13 +1,15 @@ import hashlib from typing import List, Set -from .models import BlindedMessage, BlindedSignature, Invoice, Proof -from secp256k1 import PublicKey, PrivateKey - from fastapi import Query -from .crud import get_cashu +from secp256k1 import PrivateKey, PublicKey + from lnbits.core.services import check_transaction_status, create_invoice +from .crud import get_cashu +from .models import BlindedMessage, BlindedSignature, Invoice, Proof + + def _derive_keys(master_key: str, cashu_id: str = Query(None)): """Deterministic derivation of keys for 2^n values.""" return { @@ -21,29 +23,34 @@ def _derive_keys(master_key: str, cashu_id: str = Query(None)): for i in range(MAX_ORDER) } + def _derive_pubkeys(keys: List[PrivateKey], cashu_id: str = Query(None)): return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} -async def _generate_promises(amounts: List[int], B_s: List[str], cashu_id: str = Query(None)): + +async def _generate_promises( + amounts: List[int], B_s: List[str], cashu_id: str = Query(None) +): """Generates promises that sum to the given amount.""" return [ await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True)) for (amount, B_) in zip(amounts, B_s) ] + async def _generate_promise(amount: int, B_: PublicKey, cashu_id: str = Query(None)): """Generates a promise for given amount and returns a pair (amount, C').""" secret_key = self.keys[amount] # Get the correct key C_ = step2_bob(B_, secret_key) - await store_promise( - amount, B_=B_.serialize().hex(), C_=C_.serialize().hex() - ) + await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex()) return BlindedSignature(amount=amount, C_=C_.serialize().hex()) + def _check_spendable(proof: Proof, cashu_id: str = Query(None)): """Checks whether the proof was already spent.""" return not proof.secret in self.proofs_used + def _verify_proof(proof: Proof, cashu_id: str = Query(None)): """Verifies that the proof of promise was issued by this ledger.""" if not self._check_spendable(proof): @@ -52,7 +59,13 @@ def _verify_proof(proof: Proof, cashu_id: str = Query(None)): C = PublicKey(bytes.fromhex(proof.C), raw=True) return verify(secret_key, C, proof.secret) -def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)): + +def _verify_outputs( + total: int, + amount: int, + output_data: List[BlindedMessage], + cashu_id: str = Query(None), +): """Verifies the expected split was correctly computed""" fst_amt, snd_amt = total - amount, amount # we have two amounts to split to fst_outputs = amount_split(fst_amt) @@ -61,7 +74,10 @@ def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage], given = [o.amount for o in output_data] return given == expected -def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)): + +def _verify_no_duplicates( + proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None) +): secrets = [p.secret for p in proofs] if len(secrets) != len(list(set(secrets))): return False @@ -70,6 +86,7 @@ def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage] return False return True + def _verify_split_amount(amount: int, cashu_id: str = Query(None)): """Split amount like output amount can't be negative or too big.""" try: @@ -78,6 +95,7 @@ def _verify_split_amount(amount: int, cashu_id: str = Query(None)): # For better error message raise Exception("invalid split amount: " + str(amount)) + def _verify_amount(amount: int, cashu_id: str = Query(None)): """Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER @@ -85,12 +103,16 @@ def _verify_amount(amount: int, cashu_id: str = Query(None)): raise Exception("invalid amount: " + str(amount)) return amount -def _verify_equation_balanced(proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)): + +def _verify_equation_balanced( + proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None) +): """Verify that Σoutputs - Σinputs = 0.""" sum_inputs = sum(self._verify_amount(p.amount) for p in proofs) sum_outputs = sum(self._verify_amount(p.amount) for p in outs) assert sum_outputs - sum_inputs == 0 + def _get_output_split(amount: int, cashu_id: str): """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" self._verify_amount(amount) @@ -101,6 +123,7 @@ def _get_output_split(amount: int, cashu_id: str): rv.append(2**pos) return rv + async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)): """Adds secrets of proofs to the list of knwon secrets and stores them in the db.""" # Mark proofs as used and prepare new promises @@ -110,10 +133,12 @@ async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)): for p in proofs: await invalidate_proof(p) + def get_pubkeys(cashu_id: str = Query(None)): """Returns public keys for possible amounts.""" return {a: p.serialize().hex() for a, p in self.pub_keys.items()} + async def request_mint(amount, cashu_id: str = Query(None)): cashu = await get_cashu(cashu_id) if not cashu: @@ -125,9 +150,7 @@ async def request_mint(amount, cashu_id: str = Query(None)): amount=amount, memo=cashu.name, unhashed_description=cashu.name.encode("utf-8"), - extra={ - "tag": "Cashu" - }, + extra={"tag": "Cashu"}, ) invoice = Invoice( @@ -137,15 +160,23 @@ async def request_mint(amount, cashu_id: str = Query(None)): raise Exception(f"Could not create Lightning invoice.") return payment_request, payment_hash -async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Query(None), cashu_id: str = Query(None)): + +async def mint( + B_s: List[PublicKey], + amounts: List[int], + payment_hash: str = Query(None), + cashu_id: str = Query(None), +): cashu = await get_cashu(cashu_id) if not cashu: raise Exception(f"Could not find Cashu") """Mints a promise for coins for B_.""" # check if lightning invoice was paid - if payment_hash: - if not await check_transaction_status(wallet_id=cashu.wallet, payment_hash=payment_hash): + if payment_hash: + if not await check_transaction_status( + wallet_id=cashu.wallet, payment_hash=payment_hash + ): raise Exception("Lightning invoice not paid yet.") for amount in amounts: @@ -157,11 +188,14 @@ async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Que ] return promises -async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)): + +async def melt( + proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None) +): cashu = await get_cashu(cashu_id) if not cashu: - raise Exception(f"Could not find Cashu") - + raise Exception(f"Could not find Cashu") + """Invalidates proofs and pays a Lightning invoice.""" # if not LIGHTNING: total = sum([p["amount"] for p in proofs]) @@ -181,6 +215,7 @@ async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Q await self._invalidate_proofs(proofs) return status, payment_hash + async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)): cashu = await get_cashu(cashu_id) if not cashu: @@ -189,7 +224,13 @@ async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)): """Checks if all provided proofs are valid and still spendable (i.e. have not been spent).""" return {i: self._check_spendable(p) for i, p in enumerate(proofs)} -async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)): + +async def split( + proofs: List[Proof], + amount: int, + output_data: List[BlindedMessage], + cashu_id: str = Query(None), +): cashu = await get_cashu(cashu_id) if not cashu: raise Exception(f"Could not find Cashu") @@ -226,18 +267,19 @@ async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessa async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)): cashu = await get_cashu(cashu_id) if not cashu: - raise Exception(f"Could not find Cashu") - + raise Exception(f"Could not find Cashu") + """Function for calculating the Lightning fee reserve""" return max( int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0) ) + async def amount_split(amount, cashu_id: str): cashu = await get_cashu(cashu_id) if not cashu: - raise Exception(f"Could not find Cashu") - + raise Exception(f"Could not find Cashu") + """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" bits_amt = bin(amount)[::-1][:-2] rv = [] @@ -246,11 +288,12 @@ async def amount_split(amount, cashu_id: str): rv.append(2**pos) return rv + async def hash_to_point(secret_msg, cashu_id: str = Query(None)): cashu = await get_cashu(cashu_id) if not cashu: - raise Exception(f"Could not find Cashu") - + raise Exception(f"Could not find Cashu") + """Generates x coordinate from the message hash and checks if the point lies on the curve. If it does not, it tries computing again a new x coordinate from the hash of the coordinate.""" point = None @@ -280,10 +323,11 @@ async def step1_alice(secret_msg, cashu_id: str = Query(None)): B_ = Y + r.pubkey return B_, r + async def step2_bob(B_, a, cashu_id: str = Query(None)): cashu = await get_cashu(cashu_id) if not cashu: - raise Exception(f"Could not find Cashu") + raise Exception(f"Could not find Cashu") C_ = B_.mult(a) return C_ diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py index 3f1df660..cb6b24f9 100644 --- a/lnbits/extensions/cashu/migrations.py +++ b/lnbits/extensions/cashu/migrations.py @@ -75,4 +75,4 @@ async def m001_initial(db): ); """ - ) \ No newline at end of file + ) diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py index fca096ed..c2568313 100644 --- a/lnbits/extensions/cashu/mint.py +++ b/lnbits/extensions/cashu/mint.py @@ -1,16 +1,16 @@ - -from .models import Cashu from .mint_helper import derive_keys, derive_pubkeys +from .models import Cashu def get_pubkeys(xpriv: str): """Returns public keys for possible amounts.""" - + keys = derive_keys(xpriv) pub_keys = derive_pubkeys(keys) return {a: p.serialize().hex() for a, p in pub_keys.items()} + # async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None): # """Mints a promise for coins for B_.""" # # check if lightning invoice was paid @@ -29,4 +29,4 @@ def get_pubkeys(xpriv: str): # promises = [ # await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts) # ] -# return promises \ No newline at end of file +# return promises diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py index 30e66b03..1cf631b4 100644 --- a/lnbits/extensions/cashu/mint_helper.py +++ b/lnbits/extensions/cashu/mint_helper.py @@ -1,10 +1,12 @@ import hashlib from typing import List, Set + from .core.secp import PrivateKey, PublicKey # todo: extract const MAX_ORDER = 64 + def derive_keys(master_key: str): """Deterministic derivation of keys for 2^n values.""" return { @@ -18,5 +20,6 @@ def derive_keys(master_key: str): for i in range(MAX_ORDER) } + def derive_pubkeys(keys: List[PrivateKey]): - return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} \ No newline at end of file + return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py index 094966ff..570387a2 100644 --- a/lnbits/extensions/cashu/models.py +++ b/lnbits/extensions/cashu/models.py @@ -1,5 +1,5 @@ from sqlite3 import Row -from typing import Optional, List +from typing import List, Optional from fastapi import Query from pydantic import BaseModel @@ -20,20 +20,22 @@ class Cashu(BaseModel): def from_row(cls, row: Row) -> "TPoS": return cls(**dict(row)) + class Pegs(BaseModel): id: str wallet: str inout: str amount: str - @classmethod def from_row(cls, row: Row) -> "TPoS": return cls(**dict(row)) + class PayLnurlWData(BaseModel): lnurl: str + class Promises(BaseModel): id: str amount: int @@ -41,6 +43,7 @@ class Promises(BaseModel): C_b: str cashu_id: str + class Proof(BaseModel): amount: int secret: str @@ -142,4 +145,4 @@ class CheckPayload(BaseModel): class MeltPayload(BaseModel): proofs: List[Proof] amount: int - invoice: str \ No newline at end of file + invoice: str diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py index 5fbdde8e..fe00a591 100644 --- a/lnbits/extensions/cashu/tasks.py +++ b/lnbits/extensions/cashu/tasks.py @@ -9,6 +9,7 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener from .crud import get_cashu + async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() register_invoice_listener(invoice_queue) diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html index 7378eb08..3476d41a 100644 --- a/lnbits/extensions/cashu/templates/cashu/_api_docs.html +++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html @@ -71,7 +71,8 @@
Curl example
curl -X DELETE {{ request.base_url - }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: <admin_key>" + }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: + <admin_key>" diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html index 3c2a38f5..f5af738f 100644 --- a/lnbits/extensions/cashu/templates/cashu/_cashu.html +++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html @@ -2,13 +2,12 @@

- Make Ecash mints with peg in/out to a wallet, that can create and manage ecash. + Make Ecash mints with peg in/out to a wallet, that can create and manage + ecash.

Created by - Calle.Calle.
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html index 3cd57d45..37dc360e 100644 --- a/lnbits/extensions/cashu/templates/cashu/index.html +++ b/lnbits/extensions/cashu/templates/cashu/index.html @@ -4,7 +4,9 @@
- New Mint + New Mint @@ -18,8 +20,14 @@ Export to CSV
- + {% raw %}