watchonly done

This commit is contained in:
Tiago vasconcelos 2021-10-14 22:30:47 +01:00
parent ec4117a5f4
commit 6935589dad
7 changed files with 119 additions and 98 deletions

View File

@ -1,13 +1,25 @@
from quart import Blueprint
import asyncio
from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
db = Database("ext_watchonly")
watchonly_ext: Blueprint = Blueprint(
"watchonly", __name__, static_folder="static", template_folder="templates"
watchonly_ext: APIRouter = APIRouter(
prefix="/watchonly",
tags=["watchonly"]
)
def watchonly_renderer():
return template_renderer(
[
"lnbits/extensions/watchonly/templates",
]
)
from .views_api import * # noqa
from .views import * # noqa

View File

@ -74,8 +74,9 @@ def parse_key(masterpub: str):
return desc, network
async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets:
async def create_watch_wallet(user: str, masterpub: str, title: str) -> Wallets:
# check the masterpub is fine, it will raise an exception if not
print("PARSE", parse_key(masterpub))
parse_key(masterpub)
wallet_id = urlsafe_short_hash()
await db.execute(
@ -131,19 +132,20 @@ async def delete_watch_wallet(wallet_id: str) -> None:
async def get_derive_address(wallet_id: str, num: int):
wallet = await get_watch_wallet(wallet_id)
key = wallet[2]
key = wallet.masterpub
desc, network = parse_key(key)
return desc.derive(num).address(network=network)
async def get_fresh_address(wallet_id: str) -> Optional[Addresses]:
wallet = await get_watch_wallet(wallet_id)
if not wallet:
return None
address = await get_derive_address(wallet_id, wallet[4] + 1)
address = await get_derive_address(wallet_id, wallet.address_no + 1)
await update_watch_wallet(wallet_id=wallet_id, address_no=wallet[4] + 1)
await update_watch_wallet(wallet_id=wallet_id, address_no=wallet.address_no + 1)
masterpub_id = urlsafe_short_hash()
await db.execute(
"""
@ -181,7 +183,7 @@ async def get_addresses(wallet_id: str) -> List[Addresses]:
async def create_mempool(user: str) -> Optional[Mempool]:
await db.execute(
"""
INSERT INTO watchonly.mempool ("user",endpoint)
INSERT INTO watchonly.mempool ("user",endpoint)
VALUES (?, ?)
""",
(user, "https://mempool.space"),

View File

@ -1,8 +1,12 @@
from sqlite3 import Row
from typing import NamedTuple
from fastapi.param_functions import Query
from pydantic import BaseModel
class CreateWallet(BaseModel):
masterpub: str = Query("")
title: str = Query("")
class Wallets(NamedTuple):
class Wallets(BaseModel):
id: str
user: str
masterpub: str
@ -15,7 +19,7 @@ class Wallets(NamedTuple):
return cls(**dict(row))
class Mempool(NamedTuple):
class Mempool(BaseModel):
user: str
endpoint: str
@ -24,7 +28,7 @@ class Mempool(NamedTuple):
return cls(**dict(row))
class Addresses(NamedTuple):
class Addresses(BaseModel):
id: str
address: str
wallet: str

View File

@ -38,7 +38,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/wallet -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
@ -67,7 +67,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/wallet/&lt;wallet_id&gt;
-H "X-Api-Key: {{ g.user.wallets[0].inkey }}"
-H "X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
@ -91,7 +91,7 @@
<code
>curl -X POST {{ request.url_root }}api/v1/wallet -d '{"title":
&lt;string&gt;, "masterpub": &lt;string&gt;}' -H "Content-type:
application/json" -H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
application/json" -H "X-Api-Key: {{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
@ -117,7 +117,7 @@
<code
>curl -X DELETE {{ request.url_root
}}api/v1/wallet/&lt;wallet_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].adminkey }}"
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
@ -143,7 +143,7 @@
<code
>curl -X GET {{ request.url_root
}}api/v1/addresses/&lt;wallet_id&gt; -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
@ -174,7 +174,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/address/&lt;wallet_id&gt;
-H "X-Api-Key: {{ g.user.wallets[0].inkey }}"
-H "X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
@ -203,7 +203,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/mempool -H "X-Api-Key: {{
g.user.wallets[0].adminkey }}"
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
@ -235,7 +235,7 @@
<code
>curl -X PUT {{ request.url_root }}api/v1/mempool -d '{"endpoint":
&lt;string&gt;}' -H "Content-type: application/json" -H "X-Api-Key:
{{ g.user.wallets[0].adminkey }}"
{{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>

View File

@ -287,7 +287,6 @@
{% endraw %}
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<script type="text/javascript" src="https://mempool.space/mempool.js"></script>
<style></style>
<script>

View File

@ -1,22 +1,32 @@
from quart import g, abort, render_template
from http import HTTPStatus
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from starlette.requests import Request
from fastapi.params import Depends
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import watchonly_ext
from . import watchonly_ext, watchonly_renderer
# from .crud import get_payment
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
@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.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return watchonly_renderer().TemplateResponse("watchonly/index.html", {"request": request,"user": user.dict()})
@watchonly_ext.route("/<charge_id>")
async def display(charge_id):
link = get_payment(charge_id) or abort(
HTTPStatus.NOT_FOUND, "Charge link does not exist."
)
return await render_template("watchonly/display.html", link=link)
# @watchonly_ext.get("/{charge_id}", response_class=HTMLResponse)
# async def display(request: Request, charge_id):
# link = get_payment(charge_id)
# if not link:
# raise HTTPException(
# status_code=HTTPStatus.NOT_FOUND,
# detail="Charge link does not exist."
# )
#
# return watchonly_renderer().TemplateResponse("watchonly/display.html", {"request": request,"link": link.dict()})

View File

@ -1,11 +1,15 @@
import hashlib
from quart import g, jsonify, url_for, request
from http import HTTPStatus
import httpx
import json
from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.decorators import WalletTypeInfo, get_key_type
from fastapi import Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException
from .models import CreateWallet
from lnbits.extensions.watchonly import watchonly_ext
from .crud import (
@ -24,86 +28,83 @@ from .crud import (
###################WALLETS#############################
@watchonly_ext.route("/api/v1/wallet", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_wallets_retrieve():
@watchonly_ext.get("/api/v1/wallet")
async def api_wallets_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
try:
return (
jsonify(
[wallet._asdict() for wallet in await get_watch_wallets(g.wallet.user)]
),
HTTPStatus.OK,
)
return [wallet.dict() for wallet in await get_watch_wallets(wallet.wallet.user)]
except:
return ""
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_wallet_retrieve(wallet_id):
wallet = await get_watch_wallet(wallet_id)
@watchonly_ext.get("/api/v1/wallet/{wallet_id}")
async def api_wallet_retrieve(wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)):
w_wallet = await get_watch_wallet(wallet_id)
if not wallet:
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
if not w_wallet:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Wallet does not exist."
)
return jsonify(wallet._asdict()), HTTPStatus.OK
return w_wallet.dict()
@watchonly_ext.route("/api/v1/wallet", methods=["POST"])
@api_check_wallet_key("admin")
@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):
@watchonly_ext.post("/api/v1/wallet")
async def api_wallet_create_or_update(data: CreateWallet, wallet_id=None, w: WalletTypeInfo = Depends(get_key_type)):
try:
wallet = await create_watch_wallet(
user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"]
user=w.wallet.user, masterpub=data.masterpub, title=data.title
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST
mempool = await get_mempool(g.wallet.user)
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(e)
)
mempool = await get_mempool(w.wallet.user)
if not mempool:
create_mempool(user=g.wallet.user)
return jsonify(wallet._asdict()), HTTPStatus.CREATED
create_mempool(user=w.wallet.user)
return wallet.dict()
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["DELETE"])
@api_check_wallet_key("admin")
async def api_wallet_delete(wallet_id):
@watchonly_ext.delete("/api/v1/wallet/{wallet_id}")
async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
wallet = await get_watch_wallet(wallet_id)
if not wallet:
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Wallet does not exist."
)
await delete_watch_wallet(wallet_id)
return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
#############################ADDRESSES##########################
@watchonly_ext.route("/api/v1/address/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_fresh_address(wallet_id):
@watchonly_ext.get("/api/v1/address/{wallet_id}")
async def api_fresh_address(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
await get_fresh_address(wallet_id)
addresses = await get_addresses(wallet_id)
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
return [address.dict() for address in addresses]
@watchonly_ext.route("/api/v1/addresses/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_get_addresses(wallet_id):
@watchonly_ext.get("/api/v1/addresses/{wallet_id}")
async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
wallet = await get_watch_wallet(wallet_id)
if not wallet:
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Wallet does not exist."
)
addresses = await get_addresses(wallet_id)
@ -111,28 +112,21 @@ async def api_get_addresses(wallet_id):
await get_fresh_address(wallet_id)
addresses = await get_addresses(wallet_id)
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
return [address.dict() for address in addresses]
#############################MEMPOOL##########################
@watchonly_ext.route("/api/v1/mempool", methods=["PUT"])
@api_check_wallet_key("admin")
@api_validate_post_request(
schema={
"endpoint": {"type": "string", "empty": False, "required": True},
}
)
async def api_update_mempool():
mempool = await update_mempool(user=g.wallet.user, **g.data)
return jsonify(mempool._asdict()), HTTPStatus.OK
@watchonly_ext.put("/api/v1/mempool")
async def api_update_mempool(endpoint: str = Query(...), w: WalletTypeInfo = Depends(get_key_type)):
mempool = await update_mempool(endpoint, user=w.wallet.user)
return mempool.dict()
@watchonly_ext.route("/api/v1/mempool", methods=["GET"])
@api_check_wallet_key("admin")
async def api_get_mempool():
mempool = await get_mempool(g.wallet.user)
@watchonly_ext.get("/api/v1/mempool")
async def api_get_mempool(w: WalletTypeInfo = Depends(get_key_type)):
mempool = await get_mempool(w.wallet.user)
if not mempool:
mempool = await create_mempool(user=g.wallet.user)
return jsonify(mempool._asdict()), HTTPStatus.OK
mempool = await create_mempool(user=w.wallet.user)
return mempool.dict()