From 4494f473a66bd3c876e3f64fbe7b10defded9c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 17 Feb 2023 18:03:31 +0100 Subject: [PATCH] remove lndhub (#1523) --- lnbits/extensions/lndhub/README.md | 6 - lnbits/extensions/lndhub/__init__.py | 27 -- lnbits/extensions/lndhub/config.json | 6 - lnbits/extensions/lndhub/decorators.py | 42 ---- lnbits/extensions/lndhub/migrations.py | 2 - .../extensions/lndhub/static/image/lndhub.png | Bin 32115 -> 0 bytes .../templates/lndhub/_instructions.html | 38 --- .../lndhub/templates/lndhub/_lndhub.html | 20 -- .../lndhub/templates/lndhub/index.html | 94 ------- lnbits/extensions/lndhub/utils.py | 19 -- lnbits/extensions/lndhub/views.py | 13 - lnbits/extensions/lndhub/views_api.py | 230 ------------------ 12 files changed, 497 deletions(-) delete mode 100644 lnbits/extensions/lndhub/README.md delete mode 100644 lnbits/extensions/lndhub/__init__.py delete mode 100644 lnbits/extensions/lndhub/config.json delete mode 100644 lnbits/extensions/lndhub/decorators.py delete mode 100644 lnbits/extensions/lndhub/migrations.py delete mode 100644 lnbits/extensions/lndhub/static/image/lndhub.png delete mode 100644 lnbits/extensions/lndhub/templates/lndhub/_instructions.html delete mode 100644 lnbits/extensions/lndhub/templates/lndhub/_lndhub.html delete mode 100644 lnbits/extensions/lndhub/templates/lndhub/index.html delete mode 100644 lnbits/extensions/lndhub/utils.py delete mode 100644 lnbits/extensions/lndhub/views.py delete mode 100644 lnbits/extensions/lndhub/views_api.py diff --git a/lnbits/extensions/lndhub/README.md b/lnbits/extensions/lndhub/README.md deleted file mode 100644 index ddd2020ad..000000000 --- a/lnbits/extensions/lndhub/README.md +++ /dev/null @@ -1,6 +0,0 @@ -

lndhub Extension

-

*connect to your lnbits wallet from BlueWallet or Zeus*

- -Lndhub has nothing to do with lnd, it is just the name of the HTTP/JSON protocol https://bluewallet.io/ uses to talk to their Lightning custodian server at https://lndhub.io/. - -Despite not having been planned to this, Lndhub because somewhat a standard for custodian wallet communication when https://t.me/lntxbot and https://zeusln.app/ implemented the same interface. And with this extension LNbits joins the same club. diff --git a/lnbits/extensions/lndhub/__init__.py b/lnbits/extensions/lndhub/__init__.py deleted file mode 100644 index 344e91c66..000000000 --- a/lnbits/extensions/lndhub/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -from fastapi import APIRouter -from starlette.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer - -db = Database("ext_lndhub") - -lndhub_ext: APIRouter = APIRouter(prefix="/lndhub", tags=["lndhub"]) - -lndhub_static_files = [ - { - "path": "/lndhub/static", - "app": StaticFiles(directory="lnbits/extensions/lndhub/static"), - "name": "lndhub_static", - } -] - - -def lndhub_renderer(): - return template_renderer(["lnbits/extensions/lndhub/templates"]) - - -from .decorators import * # noqa: F401,F403 -from .utils import * # noqa: F401,F403 -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/lndhub/config.json b/lnbits/extensions/lndhub/config.json deleted file mode 100644 index 30a2ce593..000000000 --- a/lnbits/extensions/lndhub/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "LndHub", - "short_description": "Access lnbits from BlueWallet or Zeus", - "tile": "/lndhub/static/image/lndhub.png", - "contributors": ["fiatjaf"] -} diff --git a/lnbits/extensions/lndhub/decorators.py b/lnbits/extensions/lndhub/decorators.py deleted file mode 100644 index 48118087c..000000000 --- a/lnbits/extensions/lndhub/decorators.py +++ /dev/null @@ -1,42 +0,0 @@ -from base64 import b64decode - -from fastapi import Request, status -from fastapi.param_functions import Security -from fastapi.security.api_key import APIKeyHeader -from starlette.exceptions import HTTPException - -from lnbits.decorators import WalletTypeInfo, get_key_type - -api_key_header_auth = APIKeyHeader( - name="AUTHORIZATION", - auto_error=False, - description="Admin or Invoice key for LNDHub API's", -) - - -async def check_wallet( - r: Request, api_key_header_auth: str = Security(api_key_header_auth) -) -> WalletTypeInfo: - if not api_key_header_auth: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid auth key" - ) - - t = api_key_header_auth.split(" ")[1] - _, token = b64decode(t).decode().split(":") - - return await get_key_type(r, api_key_header=token) - - -async def require_admin_key( - r: Request, api_key_header_auth: str = Security(api_key_header_auth) -): - wallet = await check_wallet(r, api_key_header_auth) - if wallet.wallet_type != 0: - # If wallet type is not admin then return the unauthorized status - # This also covers when the user passes an invalid key type - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Admin key required." - ) - else: - return wallet diff --git a/lnbits/extensions/lndhub/migrations.py b/lnbits/extensions/lndhub/migrations.py deleted file mode 100644 index d6ea5fdea..000000000 --- a/lnbits/extensions/lndhub/migrations.py +++ /dev/null @@ -1,2 +0,0 @@ -async def migrate(): - pass diff --git a/lnbits/extensions/lndhub/static/image/lndhub.png b/lnbits/extensions/lndhub/static/image/lndhub.png deleted file mode 100644 index f5e95a6e76861406d71ef01fc6d39a480d32ae81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32115 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%0Dy`1O*I{oN9K2k&fV$8 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 - - - To access an LNbits wallet from a mobile phone, -
    -
  1. - Install either - Zeus or - BlueWallet; -
  2. -
  3. - Go to Add a wallet / Import wallet on BlueWallet or - Settings / Add a new node on Zeus. -
  4. -
  5. Select the desired wallet on this page;
  6. -
  7. Scan one of the two QR codes from the mobile wallet.
  8. -
-
    -
  • - Invoice URLs mean the mobile wallet will only have the - authorization to read your payments and invoices and generate new - invoices. -
  • -
  • - Admin URLs mean the mobile wallet will be able to pay - invoices.. -
  • -
-
- -
- diff --git a/lnbits/extensions/lndhub/templates/lndhub/_lndhub.html b/lnbits/extensions/lndhub/templates/lndhub/_lndhub.html deleted file mode 100644 index 73097dbf0..000000000 --- a/lnbits/extensions/lndhub/templates/lndhub/_lndhub.html +++ /dev/null @@ -1,20 +0,0 @@ - - - -

- LndHub is a protocol invented by - BlueWallet - that allows mobile wallets to query payments and balances, generate - invoices and make payments from accounts that exist on a server. The - protocol is a collection of HTTP endpoints exposed through the internet. -

-

- For a wallet that supports it, reading a QR code that contains the URL - along with secret access credentials should enable access. Currently it - is supported by - Zeus and - BlueWallet. -

-
-
-
diff --git a/lnbits/extensions/lndhub/templates/lndhub/index.html b/lnbits/extensions/lndhub/templates/lndhub/index.html deleted file mode 100644 index fc666da9f..000000000 --- a/lnbits/extensions/lndhub/templates/lndhub/index.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} {% raw %} -
-
-
- - - -
- Copy LndHub {{type}} URL -
-
-
-
- - - - - - - -
- - {% endraw %} - -
- - -
- {{SITE_TITLE}} LndHub extension -
-
- - - - {% include "lndhub/_instructions.html" %} - - {% include "lndhub/_lndhub.html" %} - - -
-
-
- -{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/lndhub/utils.py b/lnbits/extensions/lndhub/utils.py deleted file mode 100644 index 008650801..000000000 --- a/lnbits/extensions/lndhub/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -from lnbits.bolt11 import Invoice - - -def to_buffer(payment_hash: str): - return {"type": "Buffer", "data": [b for b in bytes.fromhex(payment_hash)]} - - -def decoded_as_lndhub(invoice: Invoice): - return { - "destination": invoice.payee, - "payment_hash": invoice.payment_hash, - "num_satoshis": invoice.amount_msat / 1000, - "timestamp": str(invoice.date), - "expiry": str(invoice.expiry), - "description": invoice.description, - "fallback_addr": "", - "cltv_expiry": invoice.min_final_cltv_expiry, - "route_hints": "", - } diff --git a/lnbits/extensions/lndhub/views.py b/lnbits/extensions/lndhub/views.py deleted file mode 100644 index b216f8b16..000000000 --- a/lnbits/extensions/lndhub/views.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import Depends, Request - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import lndhub_ext, lndhub_renderer - - -@lndhub_ext.get("/") -async def lndhub_index(request: Request, user: User = Depends(check_user_exists)): - return lndhub_renderer().TemplateResponse( - "lndhub/index.html", {"request": request, "user": user.dict()} - ) diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py deleted file mode 100644 index 059604f28..000000000 --- a/lnbits/extensions/lndhub/views_api.py +++ /dev/null @@ -1,230 +0,0 @@ -import time -from base64 import urlsafe_b64encode -from http import HTTPStatus - -from fastapi import Depends, Query -from pydantic import BaseModel -from starlette.exceptions import HTTPException - -from lnbits import bolt11 -from lnbits.core.crud import get_payments -from lnbits.core.services import create_invoice, pay_invoice -from lnbits.decorators import WalletTypeInfo -from lnbits.settings import get_wallet_class, settings - -from . import lndhub_ext -from .decorators import check_wallet, require_admin_key -from .utils import decoded_as_lndhub, to_buffer - - -@lndhub_ext.get("/ext/getinfo") -async def lndhub_getinfo(): - return {"alias": settings.lnbits_site_title} - - -class AuthData(BaseModel): - login: str = Query(None) - password: str = Query(None) - refresh_token: str = Query(None) - - -@lndhub_ext.post("/ext/auth") -async def lndhub_auth(data: AuthData): - token = ( - data.refresh_token - if data.refresh_token - else urlsafe_b64encode((data.login + ":" + data.password).encode()).decode( - "ascii" - ) - ) - return {"refresh_token": token, "access_token": token} - - -class AddInvoice(BaseModel): - amt: str = Query(...) - memo: str = Query(...) - preimage: str = Query(None) - - -@lndhub_ext.post("/ext/addinvoice") -async def lndhub_addinvoice( - data: AddInvoice, wallet: WalletTypeInfo = Depends(check_wallet) -): - try: - _, pr = await create_invoice( - wallet_id=wallet.wallet.id, - amount=int(data.amt), - memo=data.memo or settings.lnbits_site_title, - extra={"tag": "lndhub"}, - ) - except: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Failed to create invoice" - ) - invoice = bolt11.decode(pr) - return { - "pay_req": pr, - "payment_request": pr, - "add_index": "500", - "r_hash": to_buffer(invoice.payment_hash), - "hash": invoice.payment_hash, - } - - -class CreateInvoice(BaseModel): - invoice: str = Query(...) - - -@lndhub_ext.post("/ext/payinvoice") -async def lndhub_payinvoice( - r_invoice: CreateInvoice, wallet: WalletTypeInfo = Depends(require_admin_key) -): - try: - await pay_invoice( - wallet_id=wallet.wallet.id, - payment_request=r_invoice.invoice, - extra={"tag": "lndhub"}, - ) - except: - raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Payment failed") - - invoice: bolt11.Invoice = bolt11.decode(r_invoice.invoice) - - return { - "payment_error": "", - "payment_preimage": "0" * 64, - "route": {}, - "payment_hash": invoice.payment_hash, - "decoded": decoded_as_lndhub(invoice), - "fee_msat": 0, - "type": "paid_invoice", - "fee": 0, - "value": invoice.amount_msat / 1000, - "timestamp": int(time.time()), - "memo": invoice.description, - } - - -@lndhub_ext.get("/ext/balance") -async def lndhub_balance( - wallet: WalletTypeInfo = Depends(check_wallet), -): - return {"BTC": {"AvailableBalance": wallet.wallet.balance}} - - -@lndhub_ext.get("/ext/gettxs") -async def lndhub_gettxs( - wallet: WalletTypeInfo = Depends(check_wallet), - limit: int = Query(20, ge=1, le=20), - offset: int = Query(0, ge=0), -): - for payment in await get_payments( - wallet_id=wallet.wallet.id, - complete=False, - pending=True, - outgoing=True, - incoming=False, - limit=limit, - offset=offset, - exclude_uncheckable=True, - ): - await payment.check_status() - - return [ - { - "payment_preimage": payment.preimage, - "payment_hash": payment.payment_hash, - "fee_msat": payment.fee * 1000, - "type": "paid_invoice", - "fee": payment.fee, - "value": int(payment.amount / 1000), - "timestamp": payment.time, - "memo": payment.memo if not payment.pending else "Payment in transition", - } - for payment in reversed( - ( - await get_payments( - wallet_id=wallet.wallet.id, - pending=True, - complete=True, - outgoing=True, - incoming=False, - limit=limit, - offset=offset, - ) - ) - ) - ] - - -@lndhub_ext.get("/ext/getuserinvoices") -async def lndhub_getuserinvoices( - wallet: WalletTypeInfo = Depends(check_wallet), - limit: int = Query(20, ge=1, le=20), - offset: int = Query(0, ge=0), -): - WALLET = get_wallet_class() - for invoice in await get_payments( - wallet_id=wallet.wallet.id, - complete=False, - pending=True, - outgoing=False, - incoming=True, - limit=limit, - offset=offset, - exclude_uncheckable=True, - ): - await invoice.set_pending( - (await WALLET.get_invoice_status(invoice.checking_id)).pending - ) - - return [ - { - "r_hash": to_buffer(invoice.payment_hash), - "payment_request": invoice.bolt11, - "add_index": "500", - "description": invoice.memo, - "payment_hash": invoice.payment_hash, - "ispaid": not invoice.pending, - "amt": int(invoice.amount / 1000), - "expire_time": int(time.time() + 1800), - "timestamp": invoice.time, - "type": "user_invoice", - } - for invoice in reversed( - ( - await get_payments( - wallet_id=wallet.wallet.id, - pending=True, - complete=True, - incoming=True, - outgoing=False, - limit=limit, - offset=offset, - ) - ) - ) - ] - - -@lndhub_ext.get("/ext/getbtc") -async def lndhub_getbtc(wallet: WalletTypeInfo = Depends(check_wallet)): - "load an address for incoming onchain btc" - return [] - - -@lndhub_ext.get("/ext/getpending") -async def lndhub_getpending(wallet: WalletTypeInfo = Depends(check_wallet)): - "pending onchain transactions" - return [] - - -@lndhub_ext.get("/ext/decodeinvoice") -async def lndhub_decodeinvoice(invoice: str = Query(None)): - inv = bolt11.decode(invoice) - return decoded_as_lndhub(inv) - - -@lndhub_ext.get("/ext/checkrouteinvoice") -async def lndhub_checkrouteinvoice(): - "not implemented on canonical lndhub"