From a838706090e2d6b2fbc381e8bce39eb3f58c1323 Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 1 Dec 2020 19:54:16 +0000 Subject: [PATCH 01/45] updated watchonly --- lnbits/extensions/watchonly/README.md | 4 + lnbits/extensions/watchonly/__init__.py | 8 + lnbits/extensions/watchonly/config.json | 8 + lnbits/extensions/watchonly/crud.py | 186 +++++ lnbits/extensions/watchonly/migrations.py | 48 ++ lnbits/extensions/watchonly/models.py | 44 ++ .../templates/watchonly/_api_docs.html | 141 ++++ .../templates/watchonly/display.html | 54 ++ .../watchonly/templates/watchonly/index.html | 710 ++++++++++++++++++ lnbits/extensions/watchonly/views.py | 21 + lnbits/extensions/watchonly/views_api.py | 194 +++++ requirements.txt | 1 + 12 files changed, 1419 insertions(+) create mode 100644 lnbits/extensions/watchonly/README.md create mode 100644 lnbits/extensions/watchonly/__init__.py create mode 100644 lnbits/extensions/watchonly/config.json create mode 100644 lnbits/extensions/watchonly/crud.py create mode 100644 lnbits/extensions/watchonly/migrations.py create mode 100644 lnbits/extensions/watchonly/models.py create mode 100644 lnbits/extensions/watchonly/templates/watchonly/_api_docs.html create mode 100644 lnbits/extensions/watchonly/templates/watchonly/display.html create mode 100644 lnbits/extensions/watchonly/templates/watchonly/index.html create mode 100644 lnbits/extensions/watchonly/views.py create mode 100644 lnbits/extensions/watchonly/views_api.py diff --git a/lnbits/extensions/watchonly/README.md b/lnbits/extensions/watchonly/README.md new file mode 100644 index 00000000..515be9aa --- /dev/null +++ b/lnbits/extensions/watchonly/README.md @@ -0,0 +1,4 @@ +# Watch Only wallet + +Monitor an extended public key and generate deterministic fresh public keys with this simple watch only wallet. Invoice payments can also be generated, both through a publically shareable page and API. + diff --git a/lnbits/extensions/watchonly/__init__.py b/lnbits/extensions/watchonly/__init__.py new file mode 100644 index 00000000..34c849a8 --- /dev/null +++ b/lnbits/extensions/watchonly/__init__.py @@ -0,0 +1,8 @@ +from quart import Blueprint + + +watchonly_ext: Blueprint = Blueprint("watchonly", __name__, static_folder="static", template_folder="templates") + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/watchonly/config.json b/lnbits/extensions/watchonly/config.json new file mode 100644 index 00000000..48c19ef0 --- /dev/null +++ b/lnbits/extensions/watchonly/config.json @@ -0,0 +1,8 @@ +{ + "name": "Watch Only", + "short_description": "Onchain watch only wallets", + "icon": "visibility", + "contributors": [ + "arcbtc" + ] +} diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py new file mode 100644 index 00000000..1a1fec21 --- /dev/null +++ b/lnbits/extensions/watchonly/crud.py @@ -0,0 +1,186 @@ +from typing import List, Optional, Union + +from lnbits.db import open_ext_db + +from .models import Wallets, Payments, Addresses, Mempool +from lnbits.helpers import urlsafe_short_hash + +from embit import bip32 +from embit import ec +from embit.networks import NETWORKS +from embit import base58 +from embit.util import hashlib +import io +from embit.util import secp256k1 +from embit import hashes +from binascii import hexlify +from quart import jsonify +from embit import script +from embit import ec +from embit.networks import NETWORKS +from binascii import unhexlify, hexlify, a2b_base64, b2a_base64 + +########################ADDRESSES####################### + +def get_derive_address(wallet_id: str, num: int): + + wallet = get_watch_wallet(wallet_id) + k = bip32.HDKey.from_base58(str(wallet[2])) + child = k.derive([0, num]) + address = script.p2wpkh(child).address() + + return address + +def get_fresh_address(wallet_id: str) -> Addresses: + wallet = get_watch_wallet(wallet_id) + + address = get_derive_address(wallet_id, wallet[4] + 1) + + update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1) + with open_ext_db("watchonly") as db: + db.execute( + """ + INSERT INTO addresses ( + address, + wallet, + amount + ) + VALUES (?, ?, ?) + """, + (address, wallet_id, 0), + ) + + return get_address(address) + + +def get_address(address: str) -> Addresses: + with open_ext_db("watchonly") as db: + row = db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,)) + return Addresses.from_row(row) if row else None + + +def get_addresses(wallet_id: str) -> List[Addresses]: + with open_ext_db("watchonly") as db: + rows = db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,)) + return [Addresses(**row) for row in rows] + + +##########################WALLETS#################### + +def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets: + wallet_id = urlsafe_short_hash() + with open_ext_db("watchonly") as db: + db.execute( + """ + INSERT INTO wallets ( + id, + user, + masterpub, + title, + address_no, + amount + ) + VALUES (?, ?, ?, ?, ?, ?) + """, + (wallet_id, user, masterpub, title, 0, 0), + ) + # weallet_id = db.cursor.lastrowid + address = get_fresh_address(wallet_id) + return get_watch_wallet(wallet_id) + + +def get_watch_wallet(wallet_id: str) -> Wallets: + with open_ext_db("watchonly") as db: + row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) + return Wallets.from_row(row) if row else None + +def get_watch_wallets(user: str) -> List[Wallets]: + with open_ext_db("watchonly") as db: + rows = db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,)) + return [Wallets(**row) for row in rows] + +def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[Wallets]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + + with open_ext_db("watchonly") as db: + db.execute(f"UPDATE wallets SET {q} WHERE id = ?", (*kwargs.values(), wallet_id)) + row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) + return Wallets.from_row(row) if row else None + + +def delete_watch_wallet(wallet_id: str) -> None: + with open_ext_db("watchonly") as db: + db.execute("DELETE FROM wallets WHERE id = ?", (wallet_id,)) + + +###############PAYMENTS########################## + +def create_payment(*, user: str, ex_key: str, description: str, amount: int) -> Payments: + + address = get_fresh_address(ex_key) + payment_id = urlsafe_short_hash() + with open_ext_db("watchonly") as db: + db.execute( + """ + INSERT INTO payments ( + payment_id, + user, + ex_key, + address, + amount + ) + VALUES (?, ?, ?, ?, ?) + """, + (payment_id, user, ex_key, address, amount), + ) + payment_id = db.cursor.lastrowid + return get_payment(payment_id) + + +def get_payment(payment_id: str) -> Payments: + with open_ext_db("watchonly") as db: + row = db.fetchone("SELECT * FROM payments WHERE id = ?", (payment_id,)) + return Payments.from_row(row) if row else None + + +def get_payments(user: str) -> List[Payments]: + with open_ext_db("watchonly") as db: + rows = db.fetchall("SELECT * FROM payments WHERE user IN ?", (user,)) + return [Payments.from_row(row) for row in rows] + + +def delete_payment(payment_id: str) -> None: + with open_ext_db("watchonly") as db: + db.execute("DELETE FROM payments WHERE id = ?", (payment_id,)) + + +######################MEMPOOL####################### + +def create_mempool(user: str) -> Mempool: + with open_ext_db("watchonly") as db: + db.execute( + """ + INSERT INTO mempool ( + user, + endpoint + ) + VALUES (?, ?) + """, + (user, 'https://mempool.space'), + ) + row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) + return Mempool.from_row(row) if row else None + +def update_mempool(user: str, **kwargs) -> Optional[Mempool]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + + with open_ext_db("watchonly") as db: + db.execute(f"UPDATE mempool SET {q} WHERE user = ?", (*kwargs.values(), user)) + row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) + return Mempool.from_row(row) if row else None + + +def get_mempool(user: str) -> Mempool: + with open_ext_db("watchonly") as db: + row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) + return Mempool.from_row(row) if row else None diff --git a/lnbits/extensions/watchonly/migrations.py b/lnbits/extensions/watchonly/migrations.py new file mode 100644 index 00000000..fee05cdd --- /dev/null +++ b/lnbits/extensions/watchonly/migrations.py @@ -0,0 +1,48 @@ +def m001_initial(db): + """ + Initial wallet table. + """ + db.execute( + """ + CREATE TABLE IF NOT EXISTS wallets ( + id TEXT NOT NULL PRIMARY KEY, + user TEXT, + masterpub TEXT NOT NULL, + title TEXT NOT NULL, + address_no INTEGER NOT NULL DEFAULT 0, + amount INTEGER NOT NULL + ); + """ + ) + + db.execute( + """ + CREATE TABLE IF NOT EXISTS addresses ( + address TEXT NOT NULL PRIMARY KEY, + wallet TEXT NOT NULL, + amount INTEGER NOT NULL + ); + """ + ) + + db.execute( + """ + CREATE TABLE IF NOT EXISTS payments ( + id TEXT NOT NULL PRIMARY KEY, + user TEXT, + masterpub TEXT NOT NULL, + address TEXT NOT NULL, + time_to_pay INTEGER NOT NULL, + amount INTEGER NOT NULL, + time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) + ); + """ + ) + db.execute( + """ + CREATE TABLE IF NOT EXISTS mempool ( + user TEXT NOT NULL, + endpoint TEXT NOT NULL + ); + """ + ) \ No newline at end of file diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py new file mode 100644 index 00000000..fc3a726e --- /dev/null +++ b/lnbits/extensions/watchonly/models.py @@ -0,0 +1,44 @@ +from sqlite3 import Row +from typing import NamedTuple + +class Wallets(NamedTuple): + id: str + user: str + masterpub: str + title: str + address_no: int + amount: int + + @classmethod + def from_row(cls, row: Row) -> "Wallets": + return cls(**dict(row)) + +class Payments(NamedTuple): + id: str + user: str + ex_key: str + address: str + time_to_pay: str + amount: int + time: int + + @classmethod + def from_row(cls, row: Row) -> "Payments": + return cls(**dict(row)) + +class Addresses(NamedTuple): + address: str + wallet: str + amount: int + + @classmethod + def from_row(cls, row: Row) -> "Addresses": + return cls(**dict(row)) + +class Mempool(NamedTuple): + user: str + endpoint: str + + @classmethod + def from_row(cls, row: Row) -> "Mempool": + return cls(**dict(row)) \ No newline at end of file diff --git a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html new file mode 100644 index 00000000..9b83e05a --- /dev/null +++ b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html @@ -0,0 +1,141 @@ + + +

The WatchOnly extension uses https://mempool.block for blockchain data.
+ + Created by, Ben Arc +

+
+ + + + + + + + GET /pay/api/v1/links +
Headers
+ {"X-Api-Key": <invoice_key>}
+
Body (application/json)
+
+ Returns 200 OK (application/json) +
+ [<pay_link_object>, ...] +
Curl example
+ curl -X GET {{ request.url_root }}pay/api/v1/links -H "X-Api-Key: {{ + g.user.wallets[0].inkey }}" + +
+
+
+ + + + GET + /pay/api/v1/links/<pay_id> +
Headers
+ {"X-Api-Key": <invoice_key>}
+
Body (application/json)
+
+ Returns 201 CREATED (application/json) +
+ {"lnurl": <string>} +
Curl example
+ curl -X GET {{ request.url_root }}pay/api/v1/links/<pay_id> -H + "X-Api-Key: {{ g.user.wallets[0].inkey }}" + +
+
+
+ + + + POST /pay/api/v1/links +
Headers
+ {"X-Api-Key": <admin_key>}
+
Body (application/json)
+ {"description": <string> "amount": <integer>} +
+ Returns 201 CREATED (application/json) +
+ {"lnurl": <string>} +
Curl example
+ curl -X POST {{ request.url_root }}pay/api/v1/links -d + '{"description": <string>, "amount": <integer>}' -H + "Content-type: application/json" -H "X-Api-Key: {{ + g.user.wallets[0].adminkey }}" + +
+
+
+ + + + PUT + /pay/api/v1/links/<pay_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Body (application/json)
+ {"description": <string>, "amount": <integer>} +
+ Returns 200 OK (application/json) +
+ {"lnurl": <string>} +
Curl example
+ curl -X PUT {{ request.url_root }}pay/api/v1/links/<pay_id> -d + '{"description": <string>, "amount": <integer>}' -H + "Content-type: application/json" -H "X-Api-Key: {{ + g.user.wallets[0].adminkey }}" + +
+
+
+ + + + DELETE + /pay/api/v1/links/<pay_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Returns 204 NO CONTENT
+ +
Curl example
+ curl -X DELETE {{ request.url_root }}pay/api/v1/links/<pay_id> + -H "X-Api-Key: {{ g.user.wallets[0].adminkey }}" + +
+
+
+
diff --git a/lnbits/extensions/watchonly/templates/watchonly/display.html b/lnbits/extensions/watchonly/templates/watchonly/display.html new file mode 100644 index 00000000..11af36ac --- /dev/null +++ b/lnbits/extensions/watchonly/templates/watchonly/display.html @@ -0,0 +1,54 @@ +{% extends "public.html" %} {% block page %} +
+
+ + + +
+ Copy LNURL +
+
+
+
+
+ + +
+ LNbits LNURL-pay link +
+

+ Use a LNURL compatible bitcoin wallet to claim the sats. +

+
+ + + + {% include "lnurlp/_lnurl.html" %} + + +
+
+
+{% endblock %} {% block scripts %} + + +{% endblock %} diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html new file mode 100644 index 00000000..3db624d3 --- /dev/null +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -0,0 +1,710 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + +{% raw %} + New wallet + +
+ + Point to another Mempool + + {{ this.mempool.endpoint }} + + + +
set + cancel +
+ +
+
+ +
+
+
+ + + +
+
+
Wallets
+
+
+ + + + +
+
+ + + + +
+
+ + + + + +
+
+
Paylinks
+
+
+ + + +{% endraw %} +
+
+ + + {% raw %} + + + {% endraw %} + + + + + +
+
+ + + + +
+ + + +
+ + +
+ LNbits WatchOnly Extension +
+
+ + + + {% include "watchonly/_api_docs.html" %} + + +
+
+ + + + + + + + +
+ Update Watch-only Wallet + Create Watch-only Wallet + Cancel +
+
+
+
+ + + + + + + + + + + + +
+ Update Paylink + Create Paylink + Cancel +
+
+
+
+ + + + + {% raw %} +
Addresses
+
+

Current: + {{ Addresses.data[0].address }} + + +

+ + + +

+

+ Table of addresses and amount will go here... + +

+ {% endraw %} +
+ Get fresh address + Close +
+
+
+ + +
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + + + +{% endblock %} diff --git a/lnbits/extensions/watchonly/views.py b/lnbits/extensions/watchonly/views.py new file mode 100644 index 00000000..4e5416ba --- /dev/null +++ b/lnbits/extensions/watchonly/views.py @@ -0,0 +1,21 @@ +from quart import g, abort, render_template +from http import HTTPStatus + +from lnbits.decorators import check_user_exists, validate_uuids + +from lnbits.extensions.watchonly import watchonly_ext +from .crud import get_payment + + +@watchonly_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + return await render_template("watchonly/index.html", user=g.user) + + +@watchonly_ext.route("/") +async def display(payment_id): + link = get_payment(payment_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.") + + return await render_template("watchonly/display.html", link=link) \ No newline at end of file diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py new file mode 100644 index 00000000..ceddf35f --- /dev/null +++ b/lnbits/extensions/watchonly/views_api.py @@ -0,0 +1,194 @@ +import hashlib +from quart import g, jsonify, request, url_for +from http import HTTPStatus + +from lnbits.core.crud import get_user +from lnbits.decorators import api_check_wallet_key, api_validate_post_request + +from lnbits.extensions.watchonly import watchonly_ext +from .crud import ( + create_watch_wallet, + get_watch_wallet, + get_watch_wallets, + update_watch_wallet, + delete_watch_wallet, + create_payment, + get_payment, + get_payments, + delete_payment, + create_mempool, + update_mempool, + get_mempool, + get_addresses, + get_fresh_address, + get_address +) + +###################WALLETS############################# + +@watchonly_ext.route("/api/v1/wallet", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_wallets_retrieve(): + + try: + return ( + jsonify([wallet._asdict() for wallet in get_watch_wallets(g.wallet.user)]), HTTPStatus.OK + ) + except: + return ( + jsonify({"message": "Cant fetch."}), + HTTPStatus.UPGRADE_REQUIRED, + ) + +@watchonly_ext.route("/api/v1/wallet/", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_wallet_retrieve(wallet_id): + wallet = get_watch_wallet(wallet_id) + + if not wallet: + return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND + + return jsonify({wallet}), HTTPStatus.OK + + +@watchonly_ext.route("/api/v1/wallet", methods=["POST"]) +@watchonly_ext.route("/api/v1/wallet/", methods=["PUT"]) +@api_check_wallet_key("invoice") +@api_validate_post_request( + schema={ + "masterpub": {"type": "string", "empty": False, "required": True}, + "title": {"type": "string", "empty": False, "required": True}, + } +) +async def api_wallet_create_or_update(wallet_id=None): + print("g.data") + if not wallet_id: + wallet = create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"]) + mempool = get_mempool(g.wallet.user) + if not mempool: + create_mempool(user=g.wallet.user) + return jsonify(wallet._asdict()), HTTPStatus.CREATED + + else: + wallet = update_watch_wallet(wallet_id=wallet_id, **g.data) + return jsonify(wallet._asdict()), HTTPStatus.OK + + +@watchonly_ext.route("/api/v1/wallet/", methods=["DELETE"]) +@api_check_wallet_key("invoice") +async def api_wallet_delete(wallet_id): + wallet = get_watch_wallet(wallet_id) + + if not wallet: + return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND + + delete_watch_wallet(wallet_id) + + return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT + + +#############################ADDRESSES########################## + +@watchonly_ext.route("/api/v1/address/", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_fresh_address(wallet_id): + address = get_fresh_address(wallet_id) + + if not address: + return jsonify({"message": "something went wrong"}), HTTPStatus.NOT_FOUND + + return jsonify({address}), HTTPStatus.OK + + +@watchonly_ext.route("/api/v1/addresses/", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_get_addresses(wallet_id): + addresses = get_addresses(wallet_id) + print(addresses) + if not addresses: + return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND + + return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK + +#############################PAYEMENTS########################## + +@watchonly_ext.route("/api/v1/payment", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_payments_retrieve(): + + try: + return ( + jsonify(get_payments(g.wallet.user)), + HTTPStatus.OK, + ) + except: + return ( + jsonify({"message": "Cant fetch."}), + HTTPStatus.UPGRADE_REQUIRED, + ) + +@watchonly_ext.route("/api/v1/payment/", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_payment_retrieve(payment_id): + payment = get_payment(payment_id) + + if not payment: + return jsonify({"message": "payment does not exist"}), HTTPStatus.NOT_FOUND + + return jsonify({payment}), HTTPStatus.OK + + +@watchonly_ext.route("/api/v1/payment", methods=["POST"]) +@watchonly_ext.route("/api/v1/payment/", methods=["PUT"]) +@api_check_wallet_key("invoice") +@api_validate_post_request( + schema={ + "ex_key": {"type": "string", "empty": False, "required": True}, + "pub_key": {"type": "string", "empty": False, "required": True}, + "time_to_pay": {"type": "integer", "min": 1, "required": True}, + "amount": {"type": "integer", "min": 1, "required": True}, + } +) +async def api_payment_create_or_update(payment_id=None): + + if not payment_id: + payment = create_payment(g.wallet.user, g.data.ex_key, g.data.pub_key, g.data.amount) + return jsonify(get_payment(payment)), HTTPStatus.CREATED + + else: + payment = update_payment(payment_id, g.data) + return jsonify({payment}), HTTPStatus.OK + + +@watchonly_ext.route("/api/v1/payment/", methods=["DELETE"]) +@api_check_wallet_key("invoice") +async def api_payment_delete(payment_id): + payment = get_watch_wallet(payment_id) + + if not payment: + return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND + + delete_watch_wallet(payment_id) + + return "", HTTPStatus.NO_CONTENT + +#############################MEMPOOL########################## + +@watchonly_ext.route("/api/v1/mempool", methods=["PUT"]) +@api_check_wallet_key("invoice") +@api_validate_post_request( + schema={ + "endpoint": {"type": "string", "empty": False, "required": True}, + } +) +async def api_update_mempool(): + mempool = update_mempool(user=g.wallet.user, **g.data) + return jsonify(mempool._asdict()), HTTPStatus.OK + +@watchonly_ext.route("/api/v1/mempool", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_get_mempool(): + mempool = get_mempool(g.wallet.user) + if not mempool: + mempool = create_mempool(user=g.wallet.user) + return jsonify(mempool._asdict()), HTTPStatus.OK \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 18250051..19d57cb1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,3 +46,4 @@ trio==0.16.0 typing-extensions==3.7.4.3 werkzeug==1.0.1 wsproto==1.0.0 +embit==0.1.2 \ No newline at end of file From 93417e5bdf5a3df35139c99bc686f93a29560ed5 Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 1 Dec 2020 20:29:21 +0000 Subject: [PATCH 02/45] added awaits --- .env.example | 10 +++++----- lnbits/extensions/watchonly/__init__.py | 3 +++ lnbits/extensions/watchonly/crud.py | 5 +++-- lnbits/extensions/watchonly/migrations.py | 10 +++++----- lnbits/extensions/watchonly/views.py | 2 +- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index 9a47cd7e..7ae3b388 100644 --- a/.env.example +++ b/.env.example @@ -8,14 +8,14 @@ PORT=5000 LNBITS_SITE_TITLE=LNbits LNBITS_ALLOWED_USERS="" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" -LNBITS_DATA_FOLDER="." +LNBITS_DATA_FOLDER="/home/benarc/projects/lnbits/lnbits/data" LNBITS_DISABLED_EXTENSIONS="amilk" LNBITS_FORCE_HTTPS=true LNBITS_SERVICE_FEE="0.0" # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), # LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet -LNBITS_BACKEND_WALLET_CLASS=VoidWallet +LNBITS_BACKEND_WALLET_CLASS=LNPayWallet # VoidWallet is just a fallback that works without any actual Lightning capabilities, # just so you can see the UI before dealing with this file. @@ -44,8 +44,8 @@ LND_REST_MACAROON="HEXSTRING" # LNPayWallet LNPAY_API_ENDPOINT=https://lnpay.co/v1/ -LNPAY_API_KEY=LNPAY_API_KEY -LNPAY_WALLET_KEY=LNPAY_ADMIN_KEY +LNPAY_API_KEY=sak_bbDwhLym3se3IAQFNzTHhQeTWP1enGMQ +LNPAY_WALLET_KEY=waka_XyunFwrtyFrqMkv3UquVCqce # LntxbotWallet LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/ @@ -53,4 +53,4 @@ LNTXBOT_KEY=LNTXBOT_ADMIN_KEY # OpenNodeWallet OPENNODE_API_ENDPOINT=https://api.opennode.com/ -OPENNODE_KEY=OPENNODE_ADMIN_KEY +OPENNODE_KEY=8261b1d8-3748-4d33-8a93-ae1b98f1a0f7 diff --git a/lnbits/extensions/watchonly/__init__.py b/lnbits/extensions/watchonly/__init__.py index 34c849a8..19f2a5af 100644 --- a/lnbits/extensions/watchonly/__init__.py +++ b/lnbits/extensions/watchonly/__init__.py @@ -1,4 +1,7 @@ from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_watchonly") watchonly_ext: Blueprint = Blueprint("watchonly", __name__, static_folder="static", template_folder="templates") diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index 1a1fec21..7cfbb436 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -1,8 +1,9 @@ from typing import List, Optional, Union -from lnbits.db import open_ext_db - +#from lnbits.db import open_ext_db +from . import db from .models import Wallets, Payments, Addresses, Mempool + from lnbits.helpers import urlsafe_short_hash from embit import bip32 diff --git a/lnbits/extensions/watchonly/migrations.py b/lnbits/extensions/watchonly/migrations.py index fee05cdd..7c0c4e6d 100644 --- a/lnbits/extensions/watchonly/migrations.py +++ b/lnbits/extensions/watchonly/migrations.py @@ -1,8 +1,8 @@ -def m001_initial(db): +async def m001_initial(db): """ Initial wallet table. """ - db.execute( + await db.execute( """ CREATE TABLE IF NOT EXISTS wallets ( id TEXT NOT NULL PRIMARY KEY, @@ -15,7 +15,7 @@ def m001_initial(db): """ ) - db.execute( + await db.execute( """ CREATE TABLE IF NOT EXISTS addresses ( address TEXT NOT NULL PRIMARY KEY, @@ -25,7 +25,7 @@ def m001_initial(db): """ ) - db.execute( + await db.execute( """ CREATE TABLE IF NOT EXISTS payments ( id TEXT NOT NULL PRIMARY KEY, @@ -38,7 +38,7 @@ def m001_initial(db): ); """ ) - db.execute( + await db.execute( """ CREATE TABLE IF NOT EXISTS mempool ( user TEXT NOT NULL, diff --git a/lnbits/extensions/watchonly/views.py b/lnbits/extensions/watchonly/views.py index 4e5416ba..ceaf6822 100644 --- a/lnbits/extensions/watchonly/views.py +++ b/lnbits/extensions/watchonly/views.py @@ -3,7 +3,7 @@ from http import HTTPStatus from lnbits.decorators import check_user_exists, validate_uuids -from lnbits.extensions.watchonly import watchonly_ext +from . import watchonly_ext from .crud import get_payment From 913f2a37c1c231d47f4aba69e056d3bec7fb4b3d Mon Sep 17 00:00:00 2001 From: benarc Date: Tue, 1 Dec 2020 21:39:33 +0000 Subject: [PATCH 03/45] Awaits seem to be working in watchonly --- lnbits/extensions/watchonly/crud.py | 185 +++++++++++------------ lnbits/extensions/watchonly/views_api.py | 32 ++-- 2 files changed, 100 insertions(+), 117 deletions(-) diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index 7cfbb436..e46a17b2 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -23,165 +23,150 @@ from binascii import unhexlify, hexlify, a2b_base64, b2a_base64 ########################ADDRESSES####################### -def get_derive_address(wallet_id: str, num: int): +async def get_derive_address(wallet_id: str, num: int): - wallet = get_watch_wallet(wallet_id) + wallet = await get_watch_wallet(wallet_id) k = bip32.HDKey.from_base58(str(wallet[2])) child = k.derive([0, num]) address = script.p2wpkh(child).address() return address -def get_fresh_address(wallet_id: str) -> Addresses: - wallet = get_watch_wallet(wallet_id) +async def get_fresh_address(wallet_id: str) -> Addresses: + wallet = await get_watch_wallet(wallet_id) - address = get_derive_address(wallet_id, wallet[4] + 1) + address = await get_derive_address(wallet_id, wallet[4] + 1) - update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1) - with open_ext_db("watchonly") as db: - db.execute( - """ - INSERT INTO addresses ( - address, - wallet, - amount - ) - VALUES (?, ?, ?) - """, - (address, wallet_id, 0), + await update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1) + await db.execute( + """ + INSERT INTO addresses ( + address, + wallet, + amount ) + VALUES (?, ?, ?) + """, + (address, wallet_id, 0), + ) - return get_address(address) + return await get_address(address) -def get_address(address: str) -> Addresses: - with open_ext_db("watchonly") as db: - row = db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,)) +async def get_address(address: str) -> Addresses: + row = await db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,)) return Addresses.from_row(row) if row else None -def get_addresses(wallet_id: str) -> List[Addresses]: - with open_ext_db("watchonly") as db: - rows = db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,)) +async def get_addresses(wallet_id: str) -> List[Addresses]: + rows = await db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,)) return [Addresses(**row) for row in rows] ##########################WALLETS#################### -def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets: +async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets: wallet_id = urlsafe_short_hash() - with open_ext_db("watchonly") as db: - db.execute( - """ - INSERT INTO wallets ( - id, - user, - masterpub, - title, - address_no, - amount - ) - VALUES (?, ?, ?, ?, ?, ?) - """, - (wallet_id, user, masterpub, title, 0, 0), + await db.execute( + """ + INSERT INTO wallets ( + id, + user, + masterpub, + title, + address_no, + amount ) + VALUES (?, ?, ?, ?, ?, ?) + """, + (wallet_id, user, masterpub, title, 0, 0), + ) # weallet_id = db.cursor.lastrowid - address = get_fresh_address(wallet_id) - return get_watch_wallet(wallet_id) + address = await get_fresh_address(wallet_id) + return await get_watch_wallet(wallet_id) -def get_watch_wallet(wallet_id: str) -> Wallets: - with open_ext_db("watchonly") as db: - row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) +async def get_watch_wallet(wallet_id: str) -> Wallets: + row = await db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) return Wallets.from_row(row) if row else None -def get_watch_wallets(user: str) -> List[Wallets]: - with open_ext_db("watchonly") as db: - rows = db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,)) +async def get_watch_wallets(user: str) -> List[Wallets]: + rows = await db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,)) return [Wallets(**row) for row in rows] -def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[Wallets]: +async def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[Wallets]: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - with open_ext_db("watchonly") as db: - db.execute(f"UPDATE wallets SET {q} WHERE id = ?", (*kwargs.values(), wallet_id)) - row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) + await db.execute(f"UPDATE wallets SET {q} WHERE id = ?", (*kwargs.values(), wallet_id)) + row = await db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) return Wallets.from_row(row) if row else None -def delete_watch_wallet(wallet_id: str) -> None: - with open_ext_db("watchonly") as db: - db.execute("DELETE FROM wallets WHERE id = ?", (wallet_id,)) +async def delete_watch_wallet(wallet_id: str) -> None: + await db.execute("DELETE FROM wallets WHERE id = ?", (wallet_id,)) ###############PAYMENTS########################## -def create_payment(*, user: str, ex_key: str, description: str, amount: int) -> Payments: +async def create_payment(*, user: str, ex_key: str, description: str, amount: int) -> Payments: - address = get_fresh_address(ex_key) + address = await get_fresh_address(ex_key) payment_id = urlsafe_short_hash() - with open_ext_db("watchonly") as db: - db.execute( - """ - INSERT INTO payments ( - payment_id, - user, - ex_key, - address, - amount - ) - VALUES (?, ?, ?, ?, ?) - """, - (payment_id, user, ex_key, address, amount), + await db.execute( + """ + INSERT INTO payments ( + payment_id, + user, + ex_key, + address, + amount ) - payment_id = db.cursor.lastrowid - return get_payment(payment_id) + VALUES (?, ?, ?, ?, ?) + """, + (payment_id, user, ex_key, address, amount), + ) + payment_id = db.cursor.lastrowid + return await get_payment(payment_id) -def get_payment(payment_id: str) -> Payments: - with open_ext_db("watchonly") as db: - row = db.fetchone("SELECT * FROM payments WHERE id = ?", (payment_id,)) +async def get_payment(payment_id: str) -> Payments: + row = await db.fetchone("SELECT * FROM payments WHERE id = ?", (payment_id,)) return Payments.from_row(row) if row else None -def get_payments(user: str) -> List[Payments]: - with open_ext_db("watchonly") as db: - rows = db.fetchall("SELECT * FROM payments WHERE user IN ?", (user,)) +async def get_payments(user: str) -> List[Payments]: + rows = await db.fetchall("SELECT * FROM payments WHERE user IN ?", (user,)) return [Payments.from_row(row) for row in rows] -def delete_payment(payment_id: str) -> None: - with open_ext_db("watchonly") as db: - db.execute("DELETE FROM payments WHERE id = ?", (payment_id,)) +async def delete_payment(payment_id: str) -> None: + await db.execute("DELETE FROM payments WHERE id = ?", (payment_id,)) ######################MEMPOOL####################### -def create_mempool(user: str) -> Mempool: - with open_ext_db("watchonly") as db: - db.execute( - """ - INSERT INTO mempool ( - user, - endpoint - ) - VALUES (?, ?) - """, - (user, 'https://mempool.space'), - ) - row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) +async def create_mempool(user: str) -> Mempool: + await db.execute( + """ + INSERT INTO mempool ( + user, + endpoint + ) + VALUES (?, ?) + """, + (user, 'https://mempool.space'), + ) + row = await db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) return Mempool.from_row(row) if row else None -def update_mempool(user: str, **kwargs) -> Optional[Mempool]: +async def update_mempool(user: str, **kwargs) -> Optional[Mempool]: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - with open_ext_db("watchonly") as db: - db.execute(f"UPDATE mempool SET {q} WHERE user = ?", (*kwargs.values(), user)) - row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) + await db.execute(f"UPDATE mempool SET {q} WHERE user = ?", (*kwargs.values(), user)) + row = await db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) return Mempool.from_row(row) if row else None -def get_mempool(user: str) -> Mempool: - with open_ext_db("watchonly") as db: - row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) +async def get_mempool(user: str) -> Mempool: + row = await db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) return Mempool.from_row(row) if row else None diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index ceddf35f..77a4f079 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -43,7 +43,7 @@ async def api_wallets_retrieve(): @watchonly_ext.route("/api/v1/wallet/", methods=["GET"]) @api_check_wallet_key("invoice") async def api_wallet_retrieve(wallet_id): - wallet = get_watch_wallet(wallet_id) + wallet = await get_watch_wallet(wallet_id) if not wallet: return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND @@ -63,26 +63,25 @@ async def api_wallet_retrieve(wallet_id): async def api_wallet_create_or_update(wallet_id=None): print("g.data") if not wallet_id: - wallet = create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"]) - mempool = get_mempool(g.wallet.user) + wallet = await create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"]) + mempool = await get_mempool(g.wallet.user) if not mempool: create_mempool(user=g.wallet.user) return jsonify(wallet._asdict()), HTTPStatus.CREATED - else: - wallet = update_watch_wallet(wallet_id=wallet_id, **g.data) + wallet = await update_watch_wallet(wallet_id=wallet_id, **g.data) return jsonify(wallet._asdict()), HTTPStatus.OK @watchonly_ext.route("/api/v1/wallet/", methods=["DELETE"]) @api_check_wallet_key("invoice") async def api_wallet_delete(wallet_id): - wallet = get_watch_wallet(wallet_id) + wallet = await get_watch_wallet(wallet_id) if not wallet: return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND - delete_watch_wallet(wallet_id) + await delete_watch_wallet(wallet_id) return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT @@ -92,7 +91,7 @@ async def api_wallet_delete(wallet_id): @watchonly_ext.route("/api/v1/address/", methods=["GET"]) @api_check_wallet_key("invoice") async def api_fresh_address(wallet_id): - address = get_fresh_address(wallet_id) + address = await get_fresh_address(wallet_id) if not address: return jsonify({"message": "something went wrong"}), HTTPStatus.NOT_FOUND @@ -103,8 +102,7 @@ async def api_fresh_address(wallet_id): @watchonly_ext.route("/api/v1/addresses/", methods=["GET"]) @api_check_wallet_key("invoice") async def api_get_addresses(wallet_id): - addresses = get_addresses(wallet_id) - print(addresses) + addresses = await get_addresses(wallet_id) if not addresses: return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND @@ -152,23 +150,23 @@ async def api_payment_retrieve(payment_id): async def api_payment_create_or_update(payment_id=None): if not payment_id: - payment = create_payment(g.wallet.user, g.data.ex_key, g.data.pub_key, g.data.amount) + payment = await create_payment(g.wallet.user, g.data.ex_key, g.data.pub_key, g.data.amount) return jsonify(get_payment(payment)), HTTPStatus.CREATED else: - payment = update_payment(payment_id, g.data) + payment = await update_payment(payment_id, g.data) return jsonify({payment}), HTTPStatus.OK @watchonly_ext.route("/api/v1/payment/", methods=["DELETE"]) @api_check_wallet_key("invoice") async def api_payment_delete(payment_id): - payment = get_watch_wallet(payment_id) + payment = await get_watch_wallet(payment_id) if not payment: return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND - delete_watch_wallet(payment_id) + await delete_watch_wallet(payment_id) return "", HTTPStatus.NO_CONTENT @@ -182,13 +180,13 @@ async def api_payment_delete(payment_id): } ) async def api_update_mempool(): - mempool = update_mempool(user=g.wallet.user, **g.data) + mempool = await update_mempool(user=g.wallet.user, **g.data) return jsonify(mempool._asdict()), HTTPStatus.OK @watchonly_ext.route("/api/v1/mempool", methods=["GET"]) @api_check_wallet_key("invoice") async def api_get_mempool(): - mempool = get_mempool(g.wallet.user) + mempool = await get_mempool(g.wallet.user) if not mempool: - mempool = create_mempool(user=g.wallet.user) + mempool = await create_mempool(user=g.wallet.user) return jsonify(mempool._asdict()), HTTPStatus.OK \ No newline at end of file From 48cf23346c4bfe7d8ee2d303e9d2b2f5bc23c5b1 Mon Sep 17 00:00:00 2001 From: benarc Date: Wed, 2 Dec 2020 22:47:33 +0000 Subject: [PATCH 04/45] Added get fresh address button --- .../watchonly/templates/watchonly/index.html | 150 +++++++++++++++--- lnbits/extensions/watchonly/views_api.py | 27 ++-- 2 files changed, 143 insertions(+), 34 deletions(-) diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 3db624d3..c798e7f7 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -82,13 +82,14 @@ + @@ -298,14 +299,14 @@ - + - + @@ -313,7 +314,7 @@ @@ -321,14 +322,14 @@
Create Paylink @@ -379,16 +380,33 @@ >

-

- Table of addresses and amount will go here... + + + + + {{ data.address }} + + + + + +

- {% endraw %} +
Get fresh address @@ -396,7 +414,7 @@
- +{% endraw %}
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -431,6 +449,7 @@ filter: '', checker: null, walletLinks: [], + current: {}, Addresses: { show: false, data: null @@ -513,7 +532,7 @@ show: false, data: {} }, - formDialogPayLink: { + formDialogPayment: { show: false, data: {} }, @@ -543,6 +562,24 @@ .catch(function (error) { LNbits.utils.notifyApiError(error) }) + }, + getFreshAddress: function (walletID) { + var self = this + + LNbits.api + .request( + 'GET', + '/watchonly/api/v1/address/' + walletID, + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.Addresses.show = false + getAddresses(walletID) + + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) }, addressRedirect: function (address){ window.location.href = this.mempool.endpoint + "/address/" + address; @@ -610,6 +647,7 @@ openQrCodeDialog: function (linkId) { var getAddresses = this.getAddresses getAddresses(linkId) + this.current = linkId this.Addresses.show = true }, openUpdateDialog: function (linkId) { @@ -621,20 +659,86 @@ var wallet = this.g.user.wallets[0] var data = _.omit(this.formDialog.data, 'wallet') - data.wait_time = - data.wait_time * - { - seconds: 1, - minutes: 60, - hours: 3600 - }[this.formDialog.secondMultiplier] - if (data.id) { this.updateWalletLink(wallet, data) } else { this.createWalletLink(wallet, data) } }, + sendFormDataPayLink: function () { + var wallet = this.g.user.wallets[0] + var data = _.omit(this.formDialogPayLink.data, 'wallet') + + data.wait_time = + data.wait_time * + { + seconds: 1, + minutes: 60, + hours: 3600 + }[this.formDialogPayLink.secondMultiplier] + + if (data.id) { + this.updatePayLink(wallet, data) + } else { + this.createPayLink(wallet, data) + } + }, + updatePayment: function (wallet, data) { + var self = this + + LNbits.api + .request( + 'PUT', + '/watchonly/api/v1/payment/' + data.id, + wallet.inkey, data) + .then(function (response) { + self.payment = _.reject(self.payment, function (obj) { + return obj.id === data.id + }) + self.payment.push(mapWalletLink(response.data)) + self.formDialogPayLink.show = false + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + createPayment: function (wallet, data) { + var self = this + + LNbits.api + .request('POST', '/watchonly/api/v1/payment', wallet.inkey, data) + .then(function (response) { + self.payment.push(mapWalletLink(response.data)) + self.formDialogPayLink.show = false + console.log(response.data[1][1]) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + deletePayment: function (linkId) { + var self = this + var link = _.findWhere(this.payment, {id: linkId}) + console.log(self.g.user.wallets[0].adminkey) + LNbits.utils + .confirmDialog('Are you sure you want to delete this pay link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/watchonly/api/v1/payment/' + linkId, + self.g.user.wallets[0].inkey + ) + .then(function (response) { + self.payment = _.reject(self.payment, function (obj) { + return obj.id === linkId + })}) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + updateWalletLink: function (wallet, data) { var self = this diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 77a4f079..f06c1166 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -32,13 +32,10 @@ async def api_wallets_retrieve(): try: return ( - jsonify([wallet._asdict() for wallet in get_watch_wallets(g.wallet.user)]), HTTPStatus.OK + jsonify([wallet._asdict() for wallet in await get_watch_wallets(g.wallet.user)]), HTTPStatus.OK ) except: - return ( - jsonify({"message": "Cant fetch."}), - HTTPStatus.UPGRADE_REQUIRED, - ) + return "" @watchonly_ext.route("/api/v1/wallet/", methods=["GET"]) @api_check_wallet_key("invoice") @@ -91,21 +88,29 @@ async def api_wallet_delete(wallet_id): @watchonly_ext.route("/api/v1/address/", methods=["GET"]) @api_check_wallet_key("invoice") async def api_fresh_address(wallet_id): - address = await get_fresh_address(wallet_id) + await get_fresh_address(wallet_id) - if not address: - return jsonify({"message": "something went wrong"}), HTTPStatus.NOT_FOUND + addresses = await get_addresses(wallet_id) - return jsonify({address}), HTTPStatus.OK + return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK @watchonly_ext.route("/api/v1/addresses/", methods=["GET"]) @api_check_wallet_key("invoice") async def api_get_addresses(wallet_id): - addresses = await get_addresses(wallet_id) - if not addresses: + print(wallet_id) + + wallet = await get_watch_wallet(wallet_id) + + if not wallet: return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND + addresses = await get_addresses(wallet_id) + + if not addresses: + await get_fresh_address(wallet_id) + addresses = await get_addresses(wallet_id) + return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK #############################PAYEMENTS########################## From c62678b3aaf259cf1def73e90b9d04ccd87dfb09 Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 3 Dec 2020 17:26:11 +0000 Subject: [PATCH 05/45] generated fresh addresses nicely --- Pipfile | 1 + lnbits/extensions/watchonly/crud.py | 18 +-- lnbits/extensions/watchonly/migrations.py | 3 +- lnbits/extensions/watchonly/models.py | 3 +- .../templates/watchonly/_api_docs.html | 2 +- .../watchonly/templates/watchonly/index.html | 109 ++++++++++++------ lnbits/extensions/watchonly/views_api.py | 18 +-- 7 files changed, 97 insertions(+), 57 deletions(-) diff --git a/Pipfile b/Pipfile index 6d125eeb..6909f2f2 100644 --- a/Pipfile +++ b/Pipfile @@ -24,6 +24,7 @@ quart-trio = "*" trio = "==0.16.0" hypercorn = {extras = ["trio"], version = "*"} sqlalchemy-aio = "*" +embit = "*" [dev-packages] black = "==20.8b1" diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index e46a17b2..af2d6807 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -57,7 +57,6 @@ async def get_address(address: str) -> Addresses: row = await db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,)) return Addresses.from_row(row) if row else None - async def get_addresses(wallet_id: str) -> List[Addresses]: rows = await db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,)) return [Addresses(**row) for row in rows] @@ -90,6 +89,7 @@ async def get_watch_wallet(wallet_id: str) -> Wallets: row = await db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) return Wallets.from_row(row) if row else None + async def get_watch_wallets(user: str) -> List[Wallets]: rows = await db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,)) return [Wallets(**row) for row in rows] @@ -108,24 +108,25 @@ async def delete_watch_wallet(wallet_id: str) -> None: ###############PAYMENTS########################## -async def create_payment(*, user: str, ex_key: str, description: str, amount: int) -> Payments: +async def create_payment(*, walletid: str, user: str, title: str, time: str, amount: int) -> Payments: - address = await get_fresh_address(ex_key) + address = await get_fresh_address(walletid) payment_id = urlsafe_short_hash() await db.execute( """ INSERT INTO payments ( - payment_id, + id, user, - ex_key, + title, + wallet, address, + time_to_pay, amount ) - VALUES (?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?) """, - (payment_id, user, ex_key, address, amount), + (payment_id, user, title, walletid, address.address, time, amount), ) - payment_id = db.cursor.lastrowid return await get_payment(payment_id) @@ -136,6 +137,7 @@ async def get_payment(payment_id: str) -> Payments: async def get_payments(user: str) -> List[Payments]: rows = await db.fetchall("SELECT * FROM payments WHERE user IN ?", (user,)) + print(rows[0]) return [Payments.from_row(row) for row in rows] diff --git a/lnbits/extensions/watchonly/migrations.py b/lnbits/extensions/watchonly/migrations.py index 7c0c4e6d..3ef13a0d 100644 --- a/lnbits/extensions/watchonly/migrations.py +++ b/lnbits/extensions/watchonly/migrations.py @@ -30,7 +30,8 @@ async def m001_initial(db): CREATE TABLE IF NOT EXISTS payments ( id TEXT NOT NULL PRIMARY KEY, user TEXT, - masterpub TEXT NOT NULL, + title TEXT, + wallet TEXT NOT NULL, address TEXT NOT NULL, time_to_pay INTEGER NOT NULL, amount INTEGER NOT NULL, diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index fc3a726e..d415cec6 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -16,7 +16,8 @@ class Wallets(NamedTuple): class Payments(NamedTuple): id: str user: str - ex_key: str + wallet: str + title: str address: str time_to_pay: str amount: int diff --git a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html index 9b83e05a..fd5dd4ac 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html +++ b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html @@ -1,6 +1,6 @@ -

The WatchOnly extension uses https://mempool.block for blockchain data.
+

The WatchOnly extension uses https://mempool.space for blockchain data.
Created by, Ben Arc diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index c798e7f7..19407efb 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -89,7 +89,7 @@ size="xs" icon="toll" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" - @click="formDialogPayment.show = true" + @click="formDialogPayment.show = true, formDialogPayment.data.walletid = props.row.id" > @@ -157,20 +157,20 @@ -{% endraw %} + - {% raw %} +