watchonly done
This commit is contained in:
parent
ec4117a5f4
commit
6935589dad
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/<wallet_id>
|
||||
-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":
|
||||
<string>, "masterpub": <string>}' -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/<wallet_id> -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/<wallet_id> -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/<wallet_id>
|
||||
-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":
|
||||
<string>}' -H "Content-type: application/json" -H "X-Api-Key:
|
||||
{{ g.user.wallets[0].adminkey }}"
|
||||
{{ user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()})
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user