From cbf067bba22fcd5d7b44b7060d9f05e21d7193a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=82=BFen?= Date: Thu, 19 May 2022 23:05:34 -0400 Subject: [PATCH 001/116] Fix GET transactions endpoint in usermanager docs --- .../usermanager/templates/usermanager/_api_docs.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html index 9d2901f6..34b3c39b 100644 --- a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html +++ b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html @@ -97,7 +97,7 @@ GET - /usermanager/api/v1/wallets<wallet_id>
Headers
{"X-Api-Key": <string>} @@ -109,7 +109,7 @@
Curl example
curl -X GET {{ request.base_url - }}usermanager/api/v1/wallets<wallet_id> -H "X-Api-Key: {{ + }}usermanager/api/v1/transactions/<wallet_id> -H "X-Api-Key: {{ user.wallets[0].inkey }}"
From b4a6b5a1d9e2ff9275cc068ce403ceeb5180bac1 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Jun 2022 13:25:37 +0200 Subject: [PATCH 002/116] serviced fix doc (#645) --- docs/guide/installation.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 2806a4f5..7184d46b 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -78,17 +78,23 @@ Systemd is great for taking care of your LNbits instance. It will start it on bo [Unit] Description=LNbits -#Wants=lnd.service # you can uncomment these lines if you know what you're doing -#After=lnd.service # it will make sure that lnbits starts after lnd (replace with your own backend service) +# you can uncomment these lines if you know what you're doing +# it will make sure that lnbits starts after lnd (replace with your own backend service) +#Wants=lnd.service +#After=lnd.service [Service] -WorkingDirectory=/home/bitcoin/lnbits # replace with the absolute path of your lnbits installation -ExecStart=/home/bitcoin/lnbits/venv/bin/uvicorn lnbits.__main__:app --port 5000 # same here -User=bitcoin # replace with the user that you're running lnbits on +# replace with the absolute path of your lnbits installation +WorkingDirectory=/home/bitcoin/lnbits +# same here +ExecStart=/home/bitcoin/lnbits/venv/bin/uvicorn lnbits.__main__:app --port 5000 +# replace with the user that you're running lnbits on +User=bitcoin Restart=always TimeoutSec=120 RestartSec=30 -Environment=PYTHONUNBUFFERED=1 # this makes sure that you receive logs in real time +# this makes sure that you receive logs in real time +Environment=PYTHONUNBUFFERED=1 [Install] WantedBy=multi-user.target From 7c4ce9bf961aa5f26ca8b9f2ab6ca85d7bee5388 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Wed, 1 Jun 2022 12:53:47 +0100 Subject: [PATCH 003/116] Extension: add example extension (#644) * add example ext files * remove from extension list --- lnbits/extensions/example/README.md | 11 ++++ lnbits/extensions/example/__init__.py | 19 ++++++ lnbits/extensions/example/example.config.json | 6 ++ lnbits/extensions/example/migrations.py | 10 ++++ lnbits/extensions/example/models.py | 6 ++ .../example/templates/example/index.html | 59 +++++++++++++++++++ lnbits/extensions/example/views.py | 18 ++++++ lnbits/extensions/example/views_api.py | 34 +++++++++++ 8 files changed, 163 insertions(+) create mode 100644 lnbits/extensions/example/README.md create mode 100644 lnbits/extensions/example/__init__.py create mode 100644 lnbits/extensions/example/example.config.json create mode 100644 lnbits/extensions/example/migrations.py create mode 100644 lnbits/extensions/example/models.py create mode 100644 lnbits/extensions/example/templates/example/index.html create mode 100644 lnbits/extensions/example/views.py create mode 100644 lnbits/extensions/example/views_api.py diff --git a/lnbits/extensions/example/README.md b/lnbits/extensions/example/README.md new file mode 100644 index 00000000..27729459 --- /dev/null +++ b/lnbits/extensions/example/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/example/__init__.py b/lnbits/extensions/example/__init__.py new file mode 100644 index 00000000..29a0f0b6 --- /dev/null +++ b/lnbits/extensions/example/__init__.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter + +from lnbits.db import Database +from lnbits.helpers import template_renderer + +db = Database("ext_example") + +example_ext: APIRouter = APIRouter( + prefix="/example", + tags=["example"] +) + + +def example_renderer(): + return template_renderer(["lnbits/extensions/example/templates"]) + + +from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/example/example.config.json b/lnbits/extensions/example/example.config.json new file mode 100644 index 00000000..b8eec193 --- /dev/null +++ b/lnbits/extensions/example/example.config.json @@ -0,0 +1,6 @@ +{ + "name": "Build your own!!", + "short_description": "Join us, make an extension", + "icon": "info", + "contributors": ["github_username"] +} diff --git a/lnbits/extensions/example/migrations.py b/lnbits/extensions/example/migrations.py new file mode 100644 index 00000000..99d7c362 --- /dev/null +++ b/lnbits/extensions/example/migrations.py @@ -0,0 +1,10 @@ +# async def m001_initial(db): +# await db.execute( +# f""" +# CREATE TABLE example.example ( +# id TEXT PRIMARY KEY, +# wallet TEXT NOT NULL, +# time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} +# ); +# """ +# ) diff --git a/lnbits/extensions/example/models.py b/lnbits/extensions/example/models.py new file mode 100644 index 00000000..0347a06f --- /dev/null +++ b/lnbits/extensions/example/models.py @@ -0,0 +1,6 @@ +# from pydantic import BaseModel + +# class Example(BaseModel): +# id: str +# wallet: str + diff --git a/lnbits/extensions/example/templates/example/index.html b/lnbits/extensions/example/templates/example/index.html new file mode 100644 index 00000000..d732ef37 --- /dev/null +++ b/lnbits/extensions/example/templates/example/index.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + + +
+ Frameworks used by {{SITE_TITLE}} +
+ + + {% raw %} + + + {{ tool.name }} + {{ tool.language }} + + {% endraw %} + + + +

+ A magical "g" is always available, with info about the user, wallets and + extensions: +

+ {% raw %}{{ g }}{% endraw %} +
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/example/views.py b/lnbits/extensions/example/views.py new file mode 100644 index 00000000..252b4726 --- /dev/null +++ b/lnbits/extensions/example/views.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI, 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 . import example_ext, example_renderer + +templates = Jinja2Templates(directory="templates") + + +@example_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): + return example_renderer().TemplateResponse( + "example/index.html", {"request": request, "user": user.dict()} + ) diff --git a/lnbits/extensions/example/views_api.py b/lnbits/extensions/example/views_api.py new file mode 100644 index 00000000..f144fe76 --- /dev/null +++ b/lnbits/extensions/example/views_api.py @@ -0,0 +1,34 @@ +# views_api.py is for you API endpoints that could be hit by another service + +# add your dependencies here + +# import httpx +# (use httpx just like requests, except instead of response.ok there's only the +# response.is_error that is its inverse) + +from . import example_ext + +# add your endpoints here + +@example_ext.get("/api/v1/tools") +async def api_example(): + """Try to add descriptions for others.""" + tools = [ + { + "name": "fastAPI", + "url": "https://fastapi.tiangolo.com/", + "language": "Python", + }, + { + "name": "Vue.js", + "url": "https://vuejs.org/", + "language": "JavaScript", + }, + { + "name": "Quasar Framework", + "url": "https://quasar.dev/", + "language": "JavaScript", + }, + ] + + return tools From f8400512f7ecaf1b8109e3c1655013451f859fb9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Jun 2022 14:53:05 +0200 Subject: [PATCH 004/116] black formating --- conv.py | 1 + lnbits/bolt11.py | 12 ++--- lnbits/core/views/api.py | 6 ++- lnbits/core/views/generic.py | 4 +- lnbits/decorators.py | 24 +++++++--- lnbits/extensions/discordbot/models.py | 2 + lnbits/extensions/discordbot/views_api.py | 4 +- lnbits/extensions/example/__init__.py | 5 +-- lnbits/extensions/example/models.py | 1 - lnbits/extensions/example/views_api.py | 1 + lnbits/extensions/jukebox/crud.py | 2 +- lnbits/extensions/jukebox/views_api.py | 3 +- lnbits/extensions/livestream/views.py | 1 + lnbits/extensions/offlineshop/helpers.py | 4 +- lnbits/extensions/paywall/views_api.py | 7 +-- lnbits/extensions/streamalerts/views_api.py | 2 +- lnbits/extensions/withdraw/lnurl.py | 20 ++++++--- lnbits/extensions/withdraw/views.py | 3 +- lnbits/extensions/withdraw/views_api.py | 6 +-- lnbits/helpers.py | 4 +- lnbits/settings.py | 4 +- lnbits/wallets/eclair.py | 50 ++++++++++----------- lnbits/wallets/macaroon/__init__.py | 2 +- lnbits/wallets/macaroon/macaroon.py | 16 ++++--- tests/conftest.py | 3 +- tests/core/views/test_generic.py | 1 + tests/extensions/bleskomat/conftest.py | 15 +++++-- tests/helpers.py | 5 ++- 28 files changed, 127 insertions(+), 81 deletions(-) diff --git a/conv.py b/conv.py index 159c7dc0..aa66a998 100644 --- a/conv.py +++ b/conv.py @@ -1,6 +1,7 @@ import psycopg2 import sqlite3 import os + # Python script to migrate an LNbits SQLite DB to Postgres # All credits to @Fritz446 for the awesome work diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 74f73963..e5221984 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -165,7 +165,7 @@ def lnencode(addr, privkey): if addr.amount: amount = Decimal(str(addr.amount)) # We can only send down to millisatoshi. - if amount * 10 ** 12 % 10: + if amount * 10**12 % 10: raise ValueError( "Cannot encode {}: too many decimal places".format(addr.amount) ) @@ -270,7 +270,7 @@ class LnAddr(object): def shorten_amount(amount): """Given an amount in bitcoin, shorten it""" # Convert to pico initially - amount = int(amount * 10 ** 12) + amount = int(amount * 10**12) units = ["p", "n", "u", "m", ""] for unit in units: if amount % 1000 == 0: @@ -289,7 +289,7 @@ def _unshorten_amount(amount: str) -> int: # * `u` (micro): multiply by 0.000001 # * `n` (nano): multiply by 0.000000001 # * `p` (pico): multiply by 0.000000000001 - units = {"p": 10 ** 12, "n": 10 ** 9, "u": 10 ** 6, "m": 10 ** 3} + units = {"p": 10**12, "n": 10**9, "u": 10**6, "m": 10**3} unit = str(amount)[-1] # BOLT #11: @@ -348,9 +348,9 @@ def _trim_to_bytes(barr): def _readable_scid(short_channel_id: int) -> str: return "{blockheight}x{transactionindex}x{outputindex}".format( - blockheight=((short_channel_id >> 40) & 0xffffff), - transactionindex=((short_channel_id >> 16) & 0xffffff), - outputindex=(short_channel_id & 0xffff), + blockheight=((short_channel_id >> 40) & 0xFFFFFF), + transactionindex=((short_channel_id >> 16) & 0xFFFFFF), + outputindex=(short_channel_id & 0xFFFF), ) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 3be3a460..3b2f8b3a 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -391,7 +391,11 @@ async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)): return {"paid": False} if wallet and wallet.id == payment.wallet_id: - return {"paid": not payment.pending, "preimage": payment.preimage, "details": payment} + return { + "paid": not payment.pending, + "preimage": payment.preimage, + "details": payment, + } return {"paid": not payment.pending, "preimage": payment.preimage} diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 16a2fbac..d9687e16 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -226,7 +226,9 @@ async def lnurl_balance_notify(request: Request, service: str): redeem_lnurl_withdraw(bc.wallet, bc.url) -@core_html_routes.get("/lnurlwallet", response_class=RedirectResponse, name="core.lnurlwallet") +@core_html_routes.get( + "/lnurlwallet", response_class=RedirectResponse, name="core.lnurlwallet" +) async def lnurlwallet(request: Request): async with db.connect() as conn: account = await create_account(conn=conn) diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 76cb8a54..d6f73f40 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -13,7 +13,11 @@ from starlette.requests import Request from lnbits.core.crud import get_user, get_wallet_for_key from lnbits.core.models import User, Wallet from lnbits.requestvars import g -from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS, LNBITS_ADMIN_EXTENSIONS +from lnbits.settings import ( + LNBITS_ALLOWED_USERS, + LNBITS_ADMIN_USERS, + LNBITS_ADMIN_EXTENSIONS, +) class KeyChecker(SecurityBase): @@ -122,7 +126,7 @@ async def get_key_type( # 0: admin # 1: invoice # 2: invalid - pathname = r['path'].split('/')[1] + pathname = r["path"].split("/")[1] if not api_key_header and not api_key_query: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) @@ -133,8 +137,12 @@ async def get_key_type( checker = WalletAdminKeyChecker(api_key=token) await checker.__call__(r) wallet = WalletTypeInfo(0, checker.wallet) - if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS): - raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized.") + if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and ( + LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS + ): + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) return wallet except HTTPException as e: if e.status_code == HTTPStatus.BAD_REQUEST: @@ -148,8 +156,12 @@ async def get_key_type( checker = WalletInvoiceKeyChecker(api_key=token) await checker.__call__(r) wallet = WalletTypeInfo(1, checker.wallet) - if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS): - raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized.") + if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and ( + LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS + ): + raise HTTPException( + status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." + ) return wallet except HTTPException as e: if e.status_code == HTTPStatus.BAD_REQUEST: diff --git a/lnbits/extensions/discordbot/models.py b/lnbits/extensions/discordbot/models.py index 4be367f8..985eb096 100644 --- a/lnbits/extensions/discordbot/models.py +++ b/lnbits/extensions/discordbot/models.py @@ -11,6 +11,7 @@ class CreateUserData(BaseModel): admin_id: str = Query(...) discord_id: str = Query("") + class CreateUserWallet(BaseModel): user_id: str = Query(...) wallet_name: str = Query(...) @@ -23,6 +24,7 @@ class Users(BaseModel): admin: str discord_id: str + class Wallets(BaseModel): id: str admin: str diff --git a/lnbits/extensions/discordbot/views_api.py b/lnbits/extensions/discordbot/views_api.py index 64d1df1a..6f213a89 100644 --- a/lnbits/extensions/discordbot/views_api.py +++ b/lnbits/extensions/discordbot/views_api.py @@ -109,9 +109,7 @@ async def api_discordbot_wallet_transactions( async def api_discordbot_users_wallets( user_id, wallet: WalletTypeInfo = Depends(get_key_type) ): - return [ - s_wallet.dict() for s_wallet in await get_discordbot_users_wallets(user_id) - ] + return [s_wallet.dict() for s_wallet in await get_discordbot_users_wallets(user_id)] @discordbot_ext.delete("/api/v1/wallets/{wallet_id}") diff --git a/lnbits/extensions/example/__init__.py b/lnbits/extensions/example/__init__.py index 29a0f0b6..96cc6428 100644 --- a/lnbits/extensions/example/__init__.py +++ b/lnbits/extensions/example/__init__.py @@ -5,10 +5,7 @@ from lnbits.helpers import template_renderer db = Database("ext_example") -example_ext: APIRouter = APIRouter( - prefix="/example", - tags=["example"] -) +example_ext: APIRouter = APIRouter(prefix="/example", tags=["example"]) def example_renderer(): diff --git a/lnbits/extensions/example/models.py b/lnbits/extensions/example/models.py index 0347a06f..bfeb7517 100644 --- a/lnbits/extensions/example/models.py +++ b/lnbits/extensions/example/models.py @@ -3,4 +3,3 @@ # class Example(BaseModel): # id: str # wallet: str - diff --git a/lnbits/extensions/example/views_api.py b/lnbits/extensions/example/views_api.py index f144fe76..5b702717 100644 --- a/lnbits/extensions/example/views_api.py +++ b/lnbits/extensions/example/views_api.py @@ -10,6 +10,7 @@ from . import example_ext # add your endpoints here + @example_ext.get("/api/v1/tools") async def api_example(): """Try to add descriptions for others.""" diff --git a/lnbits/extensions/jukebox/crud.py b/lnbits/extensions/jukebox/crud.py index caaac7e5..d160daee 100644 --- a/lnbits/extensions/jukebox/crud.py +++ b/lnbits/extensions/jukebox/crud.py @@ -41,7 +41,7 @@ async def update_jukebox( q = ", ".join([f"{field[0]} = ?" for field in data]) items = [f"{field[1]}" for field in data] items.append(juke_id) - q = q.replace("user", '"user"', 1) # hack to make user be "user"! + q = q.replace("user", '"user"', 1) # hack to make user be "user"! await db.execute(f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (items)) row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) return Jukebox(**row) if row else None diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index 3ba8cbf2..1f3723a7 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -455,5 +455,6 @@ async def api_get_jukebox_currently( ) except: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong, or no song is playing yet" + status_code=HTTPStatus.NOT_FOUND, + detail="Something went wrong, or no song is playing yet", ) diff --git a/lnbits/extensions/livestream/views.py b/lnbits/extensions/livestream/views.py index 4f1bd1c5..ef035431 100644 --- a/lnbits/extensions/livestream/views.py +++ b/lnbits/extensions/livestream/views.py @@ -1,4 +1,5 @@ from http import HTTPStatus + # from mmap import MAP_DENYWRITE from fastapi.param_functions import Depends diff --git a/lnbits/extensions/offlineshop/helpers.py b/lnbits/extensions/offlineshop/helpers.py index db2c19cc..6b56cf55 100644 --- a/lnbits/extensions/offlineshop/helpers.py +++ b/lnbits/extensions/offlineshop/helpers.py @@ -8,8 +8,8 @@ def hotp(key, counter, digits=6, digest="sha1"): key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8)) counter = struct.pack(">Q", counter) mac = hmac.new(key, counter, digest).digest() - offset = mac[-1] & 0x0f - binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7fffffff + offset = mac[-1] & 0x0F + binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7FFFFFFF return str(binary)[-digits:].zfill(digits) diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py index 3d1c2575..8052c63b 100644 --- a/lnbits/extensions/paywall/views_api.py +++ b/lnbits/extensions/paywall/views_api.py @@ -54,8 +54,7 @@ async def api_paywall_delete( @paywall_ext.post("/api/v1/paywalls/invoice/{paywall_id}") async def api_paywall_create_invoice( - data: CreatePaywallInvoice, - paywall_id: str = Query(None) + data: CreatePaywallInvoice, paywall_id: str = Query(None) ): paywall = await get_paywall(paywall_id) if data.amount < paywall.amount: @@ -78,7 +77,9 @@ async def api_paywall_create_invoice( @paywall_ext.post("/api/v1/paywalls/check_invoice/{paywall_id}") -async def api_paywal_check_invoice(data: CheckPaywallInvoice, paywall_id: str = Query(None)): +async def api_paywal_check_invoice( + data: CheckPaywallInvoice, paywall_id: str = Query(None) +): paywall = await get_paywall(paywall_id) payment_hash = data.payment_hash if not paywall: diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index 0a678d8b..bb2998ee 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -123,7 +123,7 @@ async def api_create_donation(data: CreateDonation, request: Request): completelinktext="Back to Stream!", webhook=webhook_base + "/streamalerts/api/v1/postdonation", description=description, - **charge_details + **charge_details, ) charge = await create_charge(user=charge_details["user"], data=create_charge_data) await create_donation( diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 801fa62f..859d8aaa 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -30,7 +30,9 @@ async def api_lnurl_response(request: Request, unique_hash): ) if link.is_spent: - raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent.") + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent." + ) url = request.url_for("withdraw.api_lnurl_callback", unique_hash=link.unique_hash) withdrawResponse = { "tag": "withdrawRequest", @@ -48,7 +50,11 @@ async def api_lnurl_response(request: Request, unique_hash): @withdraw_ext.get("/api/v1/lnurl/cb/{unique_hash}", name="withdraw.api_lnurl_callback") async def api_lnurl_callback( - unique_hash, request: Request, k1: str = Query(...), pr: str = Query(...), id_unique_hash=None + unique_hash, + request: Request, + k1: str = Query(...), + pr: str = Query(...), + id_unique_hash=None, ): link = await get_withdraw_link_by_hash(unique_hash) now = int(datetime.now().timestamp()) @@ -58,7 +64,9 @@ async def api_lnurl_callback( ) if link.is_spent: - raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent.") + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent." + ) if link.k1 != k1: raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Bad request.") @@ -81,7 +89,7 @@ async def api_lnurl_callback( if id_unique_hash == shortuuid.uuid(name=tohash): found = True useslist.pop(ind) - usescsv = ','.join(useslist) + usescsv = ",".join(useslist) if not found: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found." @@ -134,7 +142,9 @@ async def api_lnurl_multi_response(request: Request, unique_hash, id_unique_hash ) if link.is_spent: - raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent.") + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent." + ) useslist = link.usescsv.split(",") found = False diff --git a/lnbits/extensions/withdraw/views.py b/lnbits/extensions/withdraw/views.py index 81aeef98..1f059a4b 100644 --- a/lnbits/extensions/withdraw/views.py +++ b/lnbits/extensions/withdraw/views.py @@ -103,6 +103,7 @@ async def print_qr(request: Request, link_id): "withdraw/print_qr.html", {"request": request, "link": linked, "unique": True} ) + @withdraw_ext.get("/csv/{link_id}", response_class=HTMLResponse) async def print_qr(request: Request, link_id): link = await get_withdraw_link(link_id) @@ -135,4 +136,4 @@ async def print_qr(request: Request, link_id): return withdraw_renderer().TemplateResponse( "withdraw/csv.html", {"request": request, "link": linked, "unique": True} - ) \ No newline at end of file + ) diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py index c48a26b7..027b2925 100644 --- a/lnbits/extensions/withdraw/views_api.py +++ b/lnbits/extensions/withdraw/views_api.py @@ -72,10 +72,8 @@ async def api_link_create_or_update( wallet: WalletTypeInfo = Depends(require_admin_key), ): if data.uses > 250: - raise HTTPException( - detail="250 uses max.", status_code=HTTPStatus.BAD_REQUEST - ) - + raise HTTPException(detail="250 uses max.", status_code=HTTPStatus.BAD_REQUEST) + if data.min_withdrawable < 1: raise HTTPException( detail="Min must be more than 1.", status_code=HTTPStatus.BAD_REQUEST diff --git a/lnbits/helpers.py b/lnbits/helpers.py index cb6f8ee7..4ccfccea 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -26,7 +26,9 @@ class Extension(NamedTuple): class ExtensionManager: def __init__(self): self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS - self._admin_only: List[str] = [x.strip(' ') for x in settings.LNBITS_ADMIN_EXTENSIONS] + self._admin_only: List[str] = [ + x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS + ] self._extension_folders: List[str] = [ x[1] for x in os.walk(os.path.join(settings.LNBITS_PATH, "extensions")) ][0] diff --git a/lnbits/settings.py b/lnbits/settings.py index 9ccd9e4e..e3eb57e3 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -29,7 +29,9 @@ LNBITS_ALLOWED_USERS: List[str] = env.list( "LNBITS_ALLOWED_USERS", default=[], subcast=str ) LNBITS_ADMIN_USERS: List[str] = env.list("LNBITS_ADMIN_USERS", default=[], subcast=str) -LNBITS_ADMIN_EXTENSIONS: List[str] = env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str) +LNBITS_ADMIN_EXTENSIONS: List[str] = env.list( + "LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str +) LNBITS_DISABLED_EXTENSIONS: List[str] = env.list( "LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str ) diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py index 04fd498f..aa7ddc39 100644 --- a/lnbits/wallets/eclair.py +++ b/lnbits/wallets/eclair.py @@ -29,6 +29,7 @@ class EclairError(Exception): class UnknownError(Exception): pass + class EclairWallet(Wallet): def __init__(self): url = getenv("ECLAIR_URL") @@ -41,13 +42,10 @@ class EclairWallet(Wallet): auth = str(encodedAuth, "utf-8") self.auth = {"Authorization": f"Basic {auth}"} - async def status(self) -> StatusResponse: async with httpx.AsyncClient() as client: r = await client.post( - f"{self.url}/usablebalances", - headers=self.auth, - timeout=40 + f"{self.url}/usablebalances", headers=self.auth, timeout=40 ) try: data = r.json() @@ -55,7 +53,7 @@ class EclairWallet(Wallet): return StatusResponse( f"Failed to connect to {self.url}, got: '{r.text[:200]}...'", 0 ) - + if r.is_error: return StatusResponse(data["error"], 0) @@ -76,10 +74,7 @@ class EclairWallet(Wallet): async with httpx.AsyncClient() as client: r = await client.post( - f"{self.url}/createinvoice", - headers=self.auth, - data=data, - timeout=40 + f"{self.url}/createinvoice", headers=self.auth, data=data, timeout=40 ) if r.is_error: @@ -95,7 +90,6 @@ class EclairWallet(Wallet): data = r.json() return InvoiceResponse(True, data["paymentHash"], data["serialized"], None) - async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: async with httpx.AsyncClient() as client: r = await client.post( @@ -113,10 +107,9 @@ class EclairWallet(Wallet): error_message = r.text pass return PaymentResponse(False, None, 0, None, error_message) - + data = r.json() - checking_id = data["paymentHash"] preimage = data["paymentPreimage"] @@ -135,22 +128,22 @@ class EclairWallet(Wallet): except: error_message = r.text pass - return PaymentResponse(True, checking_id, 0, preimage, error_message) ## ?? is this ok ?? + return PaymentResponse( + True, checking_id, 0, preimage, error_message + ) ## ?? is this ok ?? data = r.json() fees = [i["status"] for i in data] fee_msat = sum([i["feesPaid"] for i in fees]) - - return PaymentResponse(True, checking_id, fee_msat, preimage, None) - + return PaymentResponse(True, checking_id, fee_msat, preimage, None) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: async with httpx.AsyncClient() as client: r = await client.post( f"{self.url}/getreceivedinfo", headers=self.auth, - data={"paymentHash": checking_id} + data={"paymentHash": checking_id}, ) data = r.json() @@ -160,31 +153,33 @@ class EclairWallet(Wallet): if data["status"]["type"] != "received": return PaymentStatus(False) - return PaymentStatus(True) + return PaymentStatus(True) async def get_payment_status(self, checking_id: str) -> PaymentStatus: async with httpx.AsyncClient() as client: r = await client.post( url=f"{self.url}/getsentinfo", headers=self.auth, - data={"paymentHash": checking_id} - + data={"paymentHash": checking_id}, ) data = r.json()[0] if r.is_error: return PaymentStatus(None) - + if data["status"]["type"] != "sent": return PaymentStatus(False) return PaymentStatus(True) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: - + try: - async with connect(self.ws_url, extra_headers=[('Authorization', self.auth["Authorization"])]) as ws: + async with connect( + self.ws_url, + extra_headers=[("Authorization", self.auth["Authorization"])], + ) as ws: while True: message = await ws.recv() message = json.loads(message) @@ -192,8 +187,13 @@ class EclairWallet(Wallet): if message and message["type"] == "payment-received": yield message["paymentHash"] - except (OSError, ConnectionClosedOK, ConnectionClosedError, ConnectionClosed) as ose: - print('OSE', ose) + except ( + OSError, + ConnectionClosedOK, + ConnectionClosedError, + ConnectionClosed, + ) as ose: + print("OSE", ose) pass print("lost connection to eclair's websocket, retrying in 5 seconds") diff --git a/lnbits/wallets/macaroon/__init__.py b/lnbits/wallets/macaroon/__init__.py index b7cadcfe..4967c100 100644 --- a/lnbits/wallets/macaroon/__init__.py +++ b/lnbits/wallets/macaroon/__init__.py @@ -1 +1 @@ -from .macaroon import load_macaroon, AESCipher \ No newline at end of file +from .macaroon import load_macaroon, AESCipher diff --git a/lnbits/wallets/macaroon/macaroon.py b/lnbits/wallets/macaroon/macaroon.py index dd6ff636..3548e9e2 100644 --- a/lnbits/wallets/macaroon/macaroon.py +++ b/lnbits/wallets/macaroon/macaroon.py @@ -5,10 +5,11 @@ from hashlib import md5 import getpass BLOCK_SIZE = 16 -import getpass +import getpass + def load_macaroon(macaroon: str) -> str: - """Returns hex version of a macaroon encoded in base64 or the file path. + """Returns hex version of a macaroon encoded in base64 or the file path. :param macaroon: Macaroon encoded in base64 or file path. :type macaroon: str @@ -29,6 +30,7 @@ def load_macaroon(macaroon: str) -> str: pass return macaroon + class AESCipher(object): """This class is compatible with crypto-js/aes.js @@ -39,6 +41,7 @@ class AESCipher(object): AES.decrypt(encrypted, password).toString(Utf8); """ + def __init__(self, key=None, description=""): self.key = key self.description = description + " " @@ -47,7 +50,6 @@ class AESCipher(object): length = BLOCK_SIZE - (len(data) % BLOCK_SIZE) return data + (chr(length) * length).encode() - def unpad(self, data): return data[: -(data[-1] if type(data[-1]) == int else ord(data[-1]))] @@ -70,8 +72,7 @@ class AESCipher(object): return final_key[:output] def decrypt(self, encrypted: str) -> str: - """Decrypts a string using AES-256-CBC. - """ + """Decrypts a string using AES-256-CBC.""" passphrase = self.passphrase encrypted = base64.b64decode(encrypted) assert encrypted[0:8] == b"Salted__" @@ -92,7 +93,10 @@ class AESCipher(object): key = key_iv[:32] iv = key_iv[32:] aes = AES.new(key, AES.MODE_CBC, iv) - return base64.b64encode(b"Salted__" + salt + aes.encrypt(self.pad(message))).decode() + return base64.b64encode( + b"Salted__" + salt + aes.encrypt(self.pad(message)) + ).decode() + # if this file is executed directly, ask for a macaroon and encrypt it if __name__ == "__main__": diff --git a/tests/conftest.py b/tests/conftest.py index 127233c1..27ba9137 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,9 +19,10 @@ def app(): loop.run_until_complete(loop.shutdown_asyncgens()) loop.close() + @pytest.fixture async def client(app): - client = AsyncClient(app=app, base_url=f'http://{HOST}:{PORT}') + client = AsyncClient(app=app, base_url=f"http://{HOST}:{PORT}") # yield and pass the client to the test yield client # close the async client after the test has finished diff --git a/tests/core/views/test_generic.py b/tests/core/views/test_generic.py index 4917cde4..5d3db0fb 100644 --- a/tests/core/views/test_generic.py +++ b/tests/core/views/test_generic.py @@ -1,6 +1,7 @@ import pytest from tests.conftest import client + @pytest.mark.asyncio async def test_core_views_generic(client): response = await client.get("/") diff --git a/tests/extensions/bleskomat/conftest.py b/tests/extensions/bleskomat/conftest.py index 924998a7..265d3be0 100644 --- a/tests/extensions/bleskomat/conftest.py +++ b/tests/extensions/bleskomat/conftest.py @@ -4,16 +4,22 @@ import secrets from lnbits.core.crud import create_account, create_wallet from lnbits.extensions.bleskomat.crud import create_bleskomat, create_bleskomat_lnurl from lnbits.extensions.bleskomat.models import CreateBleskomat -from lnbits.extensions.bleskomat.helpers import generate_bleskomat_lnurl_secret, generate_bleskomat_lnurl_signature, prepare_lnurl_params, query_to_signing_payload +from lnbits.extensions.bleskomat.helpers import ( + generate_bleskomat_lnurl_secret, + generate_bleskomat_lnurl_signature, + prepare_lnurl_params, + query_to_signing_payload, +) from lnbits.extensions.bleskomat.exchange_rates import exchange_rate_providers exchange_rate_providers["dummy"] = { "name": "dummy", "domain": None, "api_url": None, - "getter": lambda data, replacements: str(1e8),# 1 BTC = 100000000 sats + "getter": lambda data, replacements: str(1e8), # 1 BTC = 100000000 sats } + @pytest.fixture async def bleskomat(): user = await create_account() @@ -22,11 +28,12 @@ async def bleskomat(): name="Test Bleskomat", fiat_currency="EUR", exchange_rate_provider="dummy", - fee="0" + fee="0", ) bleskomat = await create_bleskomat(data=data, wallet_id=wallet.id) return bleskomat + @pytest.fixture async def lnurl(bleskomat): query = { @@ -43,7 +50,7 @@ async def lnurl(bleskomat): signature = generate_bleskomat_lnurl_signature( payload=payload, api_key_secret=bleskomat.api_key_secret, - api_key_encoding=bleskomat.api_key_encoding + api_key_encoding=bleskomat.api_key_encoding, ) secret = generate_bleskomat_lnurl_secret(bleskomat.api_key_id, signature) params = json.JSONEncoder().encode(params) diff --git a/tests/helpers.py b/tests/helpers.py index 1687e25d..3774f6fc 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -2,6 +2,7 @@ import hashlib import secrets from lnbits.core.crud import create_payment + async def credit_wallet(wallet_id: str, amount: int): preimage = secrets.token_hex(32) m = hashlib.sha256() @@ -14,6 +15,6 @@ async def credit_wallet(wallet_id: str, amount: int): checking_id=payment_hash, preimage=preimage, memo="", - amount=amount,# msat - pending=False,# not pending, so it will increase the wallet's balance + amount=amount, # msat + pending=False, # not pending, so it will increase the wallet's balance ) From 895d9d2e0aa60f075b768061b71bdd712ae9e21b Mon Sep 17 00:00:00 2001 From: Tomas Bezouska Date: Wed, 1 Jun 2022 15:24:17 +0200 Subject: [PATCH 005/116] Extension: LNURLw webhook_url (#610) * LNURLw web_hook * crlf -> lf * Fix typo * LNURLw webhook api doc --- lnbits/extensions/withdraw/crud.py | 6 +++-- lnbits/extensions/withdraw/lnurl.py | 25 +++++++++++++++++-- lnbits/extensions/withdraw/migrations.py | 6 +++++ lnbits/extensions/withdraw/models.py | 2 ++ lnbits/extensions/withdraw/static/js/index.js | 3 ++- .../templates/withdraw/_api_docs.html | 5 ++-- .../withdraw/templates/withdraw/index.html | 14 +++++++++++ 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py index 18a057f3..9d55d245 100644 --- a/lnbits/extensions/withdraw/crud.py +++ b/lnbits/extensions/withdraw/crud.py @@ -25,9 +25,10 @@ async def create_withdraw_link( unique_hash, k1, open_time, - usescsv + usescsv, + webhook_url ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( link_id, @@ -42,6 +43,7 @@ async def create_withdraw_link( urlsafe_short_hash(), int(datetime.now().timestamp()) + data.wait_time, usescsv, + data.webhook_url ), ) link = await get_withdraw_link(link_id, 0) diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py index 801fa62f..603e7dad 100644 --- a/lnbits/extensions/withdraw/lnurl.py +++ b/lnbits/extensions/withdraw/lnurl.py @@ -1,4 +1,7 @@ import json +import traceback +import httpx + from datetime import datetime from http import HTTPStatus @@ -103,17 +106,35 @@ async def api_lnurl_callback( await update_withdraw_link(link.id, **changes) payment_request = pr - - await pay_invoice( + + payment_hash = await pay_invoice( wallet_id=link.wallet, payment_request=payment_request, max_sat=link.max_withdrawable, extra={"tag": "withdraw"}, ) + + if link.webhook_url: + async with httpx.AsyncClient() as client: + try: + r = await client.post( + link.webhook_url, + json={ + "payment_hash": payment_hash, + "payment_request": payment_request, + "lnurlw": link.id, + }, + timeout=40, + ) + except Exception as exc: + # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid + print("Caught exception when dispatching webhook url:", exc) + return {"status": "OK"} except Exception as e: await update_withdraw_link(link.id, **changesback) + print(traceback.format_exc()) return {"status": "ERROR", "reason": "Link not working"} diff --git a/lnbits/extensions/withdraw/migrations.py b/lnbits/extensions/withdraw/migrations.py index 1a13aa6d..5c527e79 100644 --- a/lnbits/extensions/withdraw/migrations.py +++ b/lnbits/extensions/withdraw/migrations.py @@ -108,3 +108,9 @@ async def m003_make_hash_check(db): ); """ ) + +async def m004_webhook_url(db): + """ + Adds webhook_url + """ + await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_url TEXT;") \ No newline at end of file diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py index a03c7db8..c3ca7c45 100644 --- a/lnbits/extensions/withdraw/models.py +++ b/lnbits/extensions/withdraw/models.py @@ -15,6 +15,7 @@ class CreateWithdrawData(BaseModel): uses: int = Query(..., ge=1) wait_time: int = Query(..., ge=1) is_unique: bool + webhook_url: str = Query(None) class WithdrawLink(BaseModel): @@ -32,6 +33,7 @@ class WithdrawLink(BaseModel): used: int = Query(0) usescsv: str = Query(None) number: int = Query(0) + webhook_url: str = Query(None) @property def is_spent(self) -> bool: diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js index 91ff6446..e54005c6 100644 --- a/lnbits/extensions/withdraw/static/js/index.js +++ b/lnbits/extensions/withdraw/static/js/index.js @@ -179,7 +179,8 @@ new Vue({ 'max_withdrawable', 'uses', 'wait_time', - 'is_unique' + 'is_unique', + 'webhook_url' ) ) .then(function (response) { diff --git a/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html b/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html index c1172bcd..76068fcb 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html +++ b/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html @@ -70,7 +70,8 @@ {"title": <string>, "min_withdrawable": <integer>, "max_withdrawable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>}
Returns 201 CREATED (application/json) @@ -81,7 +82,7 @@ >curl -X POST {{ request.base_url }}withdraw/api/v1/links -d '{"title": <string>, "min_withdrawable": <integer>, "max_withdrawable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>}' -H + "wait_time": <integer>, "is_unique": <boolean>, "webhook_url": <string>}' -H "Content-type: application/json" -H "X-Api-Key: {{ user.wallets[0].adminkey }}" diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html index 0ce8507b..f6ea220d 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ b/lnbits/extensions/withdraw/templates/withdraw/index.html @@ -29,6 +29,7 @@ {{ col.label }} + @@ -145,11 +145,13 @@ icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" @click="ticketCard(props)" - > Click to show ticket + > Click to show ticket - {{ col.label == "Ticket" ? col.value.length > 20 ? `${col.value.substring(0, 20)}...` : col.value : col.value }} + {{ col.label == "Ticket" ? col.value.length > 20 ? + `${col.value.substring(0, 20)}...` : col.value : col.value }} @@ -410,7 +412,7 @@ }) }) }, - ticketCard(ticket){ + ticketCard(ticket) { this.ticketDialog.show = true let {date, email, ltext, name} = ticket.row this.ticketDialog.data = { @@ -469,7 +471,7 @@ }, updateformDialog: function (formId) { var link = _.findWhere(this.forms, {id: formId}) - console.log("LINK", link) + console.log('LINK', link) this.formDialog.data.id = link.id this.formDialog.data.wallet = link.wallet diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html index e255d4a0..d5b4b5b8 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html @@ -39,8 +39,8 @@ [<lnurldevice_object>, ...]
Curl example
curl -X POST {{ request.base_url }}lnurldevice/api/v1/lnurlpos -d '{"title": - <string>, "message":<string>, "currency": + >curl -X POST {{ request.base_url }}lnurldevice/api/v1/lnurlpos -d + '{"title": <string>, "message":<string>, "currency": <integer>}' -H "Content-type: application/json" -H "X-Api-Key: {{user.wallets[0].adminkey }}" @@ -104,8 +104,8 @@
Curl example
curl -X GET {{ request.base_url - }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: + {{ user.wallets[0].inkey }}" @@ -159,8 +159,8 @@
Curl example
curl -X DELETE {{ request.base_url - }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" + }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: + {{ user.wallets[0].adminkey }}" diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index b51e2556..24d19484 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -480,12 +480,11 @@ updatedData ) .then(function (response) { - self.lnurldeviceLinks = _.reject( - self.lnurldeviceLinks, - function (obj) { - return obj.id === updatedData.id - } - ) + self.lnurldeviceLinks = _.reject(self.lnurldeviceLinks, function ( + obj + ) { + return obj.id === updatedData.id + }) self.lnurldeviceLinks.push(maplnurldevice(response.data)) self.formDialoglnurldevice.show = false self.clearFormDialoglnurldevice() diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html b/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html index bf920558..200865fb 100644 --- a/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html +++ b/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html @@ -17,8 +17,8 @@ [<pay_link_object>, ...]
Curl example
curl -X GET {{ request.base_url }}lnurlp/api/v1/links -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}lnurlp/api/v1/links -H "X-Api-Key: + {{ user.wallets[0].inkey }}" @@ -39,8 +39,8 @@ {"lnurl": <string>}
Curl example
curl -X GET {{ request.base_url }}lnurlp/api/v1/links/<pay_id> -H - "X-Api-Key: {{ user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}lnurlp/api/v1/links/<pay_id> + -H "X-Api-Key: {{ user.wallets[0].inkey }}" @@ -68,11 +68,11 @@ {"lnurl": <string>}
Curl example
curl -X POST {{ request.base_url }}lnurlp/api/v1/links -d '{"description": - <string>, "amount": <integer>, "max": <integer>, - "min": <integer>, "comment_chars": <integer>}' -H - "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" + >curl -X POST {{ request.base_url }}lnurlp/api/v1/links -d + '{"description": <string>, "amount": <integer>, "max": + <integer>, "min": <integer>, "comment_chars": + <integer>}' -H "Content-type: application/json" -H "X-Api-Key: + {{ user.wallets[0].adminkey }}" @@ -99,8 +99,8 @@ {"lnurl": <string>}
Curl example
curl -X PUT {{ request.base_url }}lnurlp/api/v1/links/<pay_id> -d - '{"description": <string>, "amount": <integer>}' -H + >curl -X PUT {{ request.base_url }}lnurlp/api/v1/links/<pay_id> + -d '{"description": <string>, "amount": <integer>}' -H "Content-type: application/json" -H "X-Api-Key: {{ user.wallets[0].adminkey }}" @@ -126,8 +126,9 @@
Curl example
curl -X DELETE {{ request.base_url }}lnurlp/api/v1/links/<pay_id> -H - "X-Api-Key: {{ user.wallets[0].adminkey }}" + >curl -X DELETE {{ request.base_url + }}lnurlp/api/v1/links/<pay_id> -H "X-Api-Key: {{ + user.wallets[0].adminkey }}" diff --git a/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html b/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html index bd68b629..a472d549 100644 --- a/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html +++ b/lnbits/extensions/offlineshop/templates/offlineshop/_api_docs.html @@ -127,7 +127,13 @@ - + DELETE diff --git a/lnbits/extensions/paywall/templates/paywall/_api_docs.html b/lnbits/extensions/paywall/templates/paywall/_api_docs.html index 664cda0f..2c8fe672 100644 --- a/lnbits/extensions/paywall/templates/paywall/_api_docs.html +++ b/lnbits/extensions/paywall/templates/paywall/_api_docs.html @@ -17,8 +17,8 @@ [<paywall_object>, ...]
Curl example
curl -X GET {{ request.base_url }}paywall/api/v1/paywalls -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}paywall/api/v1/paywalls -H + "X-Api-Key: {{ user.wallets[0].inkey }}"
@@ -48,11 +48,11 @@ >
Curl example
curl -X POST {{ request.base_url }}paywall/api/v1/paywalls -d '{"url": - <string>, "memo": <string>, "description": <string>, - "amount": <integer>, "remembers": <boolean>}' -H - "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" + >curl -X POST {{ request.base_url }}paywall/api/v1/paywalls -d + '{"url": <string>, "memo": <string>, "description": + <string>, "amount": <integer>, "remembers": + <boolean>}' -H "Content-type: application/json" -H "X-Api-Key: + {{ user.wallets[0].adminkey }}" diff --git a/lnbits/extensions/satsdice/templates/satsdice/_api_docs.html b/lnbits/extensions/satsdice/templates/satsdice/_api_docs.html index 644bcdfe..a80fd37a 100644 --- a/lnbits/extensions/satsdice/templates/satsdice/_api_docs.html +++ b/lnbits/extensions/satsdice/templates/satsdice/_api_docs.html @@ -17,8 +17,8 @@ [<satsdice_link_object>, ...]
Curl example
curl -X GET {{ request.base_url }}satsdice/api/v1/links -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}satsdice/api/v1/links -H + "X-Api-Key: {{ user.wallets[0].inkey }}" @@ -44,8 +44,9 @@ {"lnurl": <string>}
Curl example
curl -X GET {{ request.base_url }}satsdice/api/v1/links/<satsdice_id> -H - "X-Api-Key: {{ user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url + }}satsdice/api/v1/links/<satsdice_id> -H "X-Api-Key: {{ + user.wallets[0].inkey }}" @@ -73,8 +74,8 @@ {"lnurl": <string>}
Curl example
curl -X POST {{ request.base_url }}satsdice/api/v1/links -d '{"title": - <string>, "min_satsdiceable": <integer>, + >curl -X POST {{ request.base_url }}satsdice/api/v1/links -d + '{"title": <string>, "min_satsdiceable": <integer>, "max_satsdiceable": <integer>, "uses": <integer>, "wait_time": <integer>, "is_unique": <boolean>}' -H "Content-type: application/json" -H "X-Api-Key: {{ @@ -109,8 +110,9 @@ {"lnurl": <string>}
Curl example
curl -X PUT {{ request.base_url }}satsdice/api/v1/links/<satsdice_id> -d - '{"title": <string>, "min_satsdiceable": <integer>, + >curl -X PUT {{ request.base_url + }}satsdice/api/v1/links/<satsdice_id> -d '{"title": + <string>, "min_satsdiceable": <integer>, "max_satsdiceable": <integer>, "uses": <integer>, "wait_time": <integer>, "is_unique": <boolean>}' -H "Content-type: application/json" -H "X-Api-Key: {{ @@ -137,8 +139,9 @@
Curl example
curl -X DELETE {{ request.base_url }}satsdice/api/v1/links/<satsdice_id> - -H "X-Api-Key: {{ user.wallets[0].adminkey }}" + >curl -X DELETE {{ request.base_url + }}satsdice/api/v1/links/<satsdice_id> -H "X-Api-Key: {{ + user.wallets[0].adminkey }}" @@ -165,8 +168,8 @@
Curl example
curl -X GET {{ request.base_url - }}satsdice/api/v1/links/<the_hash>/<lnurl_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + }}satsdice/api/v1/links/<the_hash>/<lnurl_id> -H + "X-Api-Key: {{ user.wallets[0].inkey }}" diff --git a/lnbits/extensions/satspay/templates/satspay/_api_docs.html b/lnbits/extensions/satspay/templates/satspay/_api_docs.html index 89380de0..77451ae5 100644 --- a/lnbits/extensions/satspay/templates/satspay/_api_docs.html +++ b/lnbits/extensions/satspay/templates/satspay/_api_docs.html @@ -60,12 +60,13 @@ [<charge_object>, ...]
Curl example
curl -X POST {{ request.base_url }}satspay/api/v1/charge/<charge_id> - -d '{"onchainwallet": <string, watchonly_wallet_id>, - "description": <string>, "webhook":<string>, "time": - <integer>, "amount": <integer>, "lnbitswallet": - <string, lnbits_wallet_id>}' -H "Content-type: - application/json" -H "X-Api-Key: {{user.wallets[0].adminkey }}" + >curl -X POST {{ request.base_url + }}satspay/api/v1/charge/<charge_id> -d '{"onchainwallet": + <string, watchonly_wallet_id>, "description": <string>, + "webhook":<string>, "time": <integer>, "amount": + <integer>, "lnbitswallet": <string, lnbits_wallet_id>}' + -H "Content-type: application/json" -H "X-Api-Key: + {{user.wallets[0].adminkey }}" @@ -89,8 +90,9 @@ [<charge_object>, ...]
Curl example
curl -X GET {{ request.base_url }}satspay/api/v1/charge/<charge_id> - -H "X-Api-Key: {{ user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url + }}satspay/api/v1/charge/<charge_id> -H "X-Api-Key: {{ + user.wallets[0].inkey }}" @@ -112,8 +114,8 @@ [<charge_object>, ...]
Curl example
curl -X GET {{ request.base_url }}satspay/api/v1/charges -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}satspay/api/v1/charges -H + "X-Api-Key: {{ user.wallets[0].inkey }}" @@ -145,7 +147,8 @@
diff --git a/lnbits/extensions/splitpayments/static/js/index.js b/lnbits/extensions/splitpayments/static/js/index.js index dea469e5..d9750bef 100644 --- a/lnbits/extensions/splitpayments/static/js/index.js +++ b/lnbits/extensions/splitpayments/static/js/index.js @@ -119,7 +119,7 @@ new Vue({ '/splitpayments/api/v1/targets', this.selectedWallet.adminkey, { - "targets": this.targets + targets: this.targets .filter(isTargetComplete) .map(({wallet, percent, alias}) => ({wallet, percent, alias})) } diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/_api_docs.html b/lnbits/extensions/splitpayments/templates/splitpayments/_api_docs.html index 7a8a6d07..4cf7190c 100644 --- a/lnbits/extensions/splitpayments/templates/splitpayments/_api_docs.html +++ b/lnbits/extensions/splitpayments/templates/splitpayments/_api_docs.html @@ -52,8 +52,8 @@ >
Curl example
curl -X GET {{ request.base_url }}splitpayments/api/v1/targets -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}splitpayments/api/v1/targets -H + "X-Api-Key: {{ user.wallets[0].inkey }}" diff --git a/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html b/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html index 42788bad..95ba6e06 100644 --- a/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html +++ b/lnbits/extensions/tipjar/templates/tipjar/_api_docs.html @@ -4,10 +4,9 @@ Tip Jar: Receive tips with messages!

- Your personal Bitcoin tip page, which supports - lightning and on-chain payments. - Notifications, including a donation message, - can be sent via webhook. + Your personal Bitcoin tip page, which supports lightning and on-chain + payments. Notifications, including a donation message, can be sent via + webhook. Created by, Fitti diff --git a/lnbits/extensions/tipjar/templates/tipjar/index.html b/lnbits/extensions/tipjar/templates/tipjar/index.html index dda49842..19fca6e4 100644 --- a/lnbits/extensions/tipjar/templates/tipjar/index.html +++ b/lnbits/extensions/tipjar/templates/tipjar/index.html @@ -322,11 +322,7 @@ var self = this LNbits.api - .request( - 'GET', - '/tipjar/api/v1/tips', - this.g.user.wallets[0].inkey - ) + .request('GET', '/tipjar/api/v1/tips', this.g.user.wallets[0].inkey) .then(function (response) { self.tips = response.data.map(function (obj) { return mapTipJar(obj) diff --git a/lnbits/extensions/tpos/templates/tpos/_api_docs.html b/lnbits/extensions/tpos/templates/tpos/_api_docs.html index 42160cce..8930d990 100644 --- a/lnbits/extensions/tpos/templates/tpos/_api_docs.html +++ b/lnbits/extensions/tpos/templates/tpos/_api_docs.html @@ -69,8 +69,8 @@

Curl example
curl -X DELETE {{ request.base_url }}tpos/api/v1/tposs/<tpos_id> -H - "X-Api-Key: <admin_key>" + >curl -X DELETE {{ request.base_url + }}tpos/api/v1/tposs/<tpos_id> -H "X-Api-Key: <admin_key>" diff --git a/lnbits/extensions/tpos/templates/tpos/index.html b/lnbits/extensions/tpos/templates/tpos/index.html index af3b0573..76f33000 100644 --- a/lnbits/extensions/tpos/templates/tpos/index.html +++ b/lnbits/extensions/tpos/templates/tpos/index.html @@ -54,7 +54,8 @@ > - {{ (col.name == 'tip_options' ? JSON.parse(col.value).join(", ") : col.value) }} + {{ (col.name == 'tip_options' ? JSON.parse(col.value).join(", ") + : col.value) }} parseInt(str))) : JSON.stringify([])), - tip_wallet: this.formDialog.data.tip_wallet || "", + tip_options: this.formDialog.data.tip_options + ? JSON.stringify( + this.formDialog.data.tip_options.map(str => parseInt(str)) + ) + : JSON.stringify([]), + tip_wallet: this.formDialog.data.tip_wallet || '' } var self = this diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html index e4ea1499..70b84f9b 100644 --- a/lnbits/extensions/tpos/templates/tpos/tpos.html +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -1,7 +1,5 @@ -{% extends "public.html" %} -{% block toolbar_title %} -{{ tpos.name }} +{% extends "public.html" %} {% block toolbar_title %} {{ tpos.name }} -{% endblock %} -{% block footer %}{% endblock %} {% block page_container %} +{% endblock %} {% block footer %}{% endblock %} {% block page_container %} @@ -180,27 +177,24 @@ - +
- Would you like to leave a tip? + Would you like to leave a tip?
- {% raw %}{{ tip }}{% endraw %}% + {% raw %}{{ tip }}{% endraw %}%

No, thanks

@@ -265,7 +259,7 @@ } .keypad .btn-confirm { - grid-area: 1 / 4 / 5 / 4; + grid-area: 1 / 4 / 5 / 4; } {% endblock %} {% block scripts %} @@ -282,7 +276,7 @@ tip_options: JSON.parse('{{ tpos.tip_options }}'), exchangeRate: null, stack: [], - tipAmount: 0.00, + tipAmount: 0.0, invoiceDialog: { show: false, data: null, @@ -290,7 +284,7 @@ paymentChecker: null }, tipDialog: { - show: false, + show: false }, urlDialog: { show: false @@ -324,7 +318,7 @@ methods: { closeInvoiceDialog: function () { this.stack = [] - this.tipAmount = 0.00 + this.tipAmount = 0.0 var dialog = this.invoiceDialog setTimeout(function () { clearInterval(dialog.paymentChecker) @@ -334,8 +328,10 @@ processTipSelection: function (selectedTipOption) { this.tipDialog.show = false - if(selectedTipOption) { - const tipAmount = parseFloat(parseFloat((selectedTipOption / 100) * this.amount)) + if (selectedTipOption) { + const tipAmount = parseFloat( + parseFloat((selectedTipOption / 100) * this.amount) + ) const subtotal = parseFloat(this.amount) const grandTotal = parseFloat((tipAmount + subtotal).toFixed(2)) const totalString = grandTotal.toFixed(2).toString() @@ -344,7 +340,7 @@ for (var i = 0; i < totalString.length; i++) { const char = totalString[i] - if(char !== ".") { + if (char !== '.') { this.stack.push(char) } } @@ -354,14 +350,14 @@ this.showInvoice() }, - submitForm: function() { - if(this.tip_options.length) { + submitForm: function () { + if (this.tip_options.length) { this.showTipModal() } else { this.showInvoice() } }, - showTipModal: function() { + showTipModal: function () { this.tipDialog.show = true }, showInvoice: function () { @@ -372,7 +368,7 @@ .post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, { params: { amount: this.sat, - tipAmount: this.tipAmountSat, + tipAmount: this.tipAmountSat } }) .then(function (response) { diff --git a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html index bff66383..b421186a 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html +++ b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html @@ -37,8 +37,8 @@ [<wallets_object>, ...]
Curl example
curl -X GET {{ request.base_url }}watchonly/api/v1/wallet -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}watchonly/api/v1/wallet -H + "X-Api-Key: {{ user.wallets[0].inkey }}" @@ -66,8 +66,9 @@ [<wallet_object>, ...]
Curl example
curl -X GET {{ request.base_url }}watchonly/api/v1/wallet/<wallet_id> - -H "X-Api-Key: {{ user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url + }}watchonly/api/v1/wallet/<wallet_id> -H "X-Api-Key: {{ + user.wallets[0].inkey }}" @@ -89,9 +90,10 @@ [<wallet_object>, ...]
Curl example
curl -X POST {{ request.base_url }}watchonly/api/v1/wallet -d '{"title": - <string>, "masterpub": <string>}' -H "Content-type: - application/json" -H "X-Api-Key: {{ user.wallets[0].adminkey }}" + >curl -X POST {{ request.base_url }}watchonly/api/v1/wallet -d + '{"title": <string>, "masterpub": <string>}' -H + "Content-type: application/json" -H "X-Api-Key: {{ + user.wallets[0].adminkey }}" @@ -173,8 +175,9 @@ [<address_object>, ...]
Curl example
curl -X GET {{ request.base_url }}watchonly/api/v1/address/<wallet_id> - -H "X-Api-Key: {{ user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url + }}watchonly/api/v1/address/<wallet_id> -H "X-Api-Key: {{ + user.wallets[0].inkey }}" @@ -202,8 +205,8 @@ [<mempool_object>, ...]
Curl example
curl -X GET {{ request.base_url }}watchonly/api/v1/mempool -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" + >curl -X GET {{ request.base_url }}watchonly/api/v1/mempool -H + "X-Api-Key: {{ user.wallets[0].adminkey }}" @@ -233,9 +236,9 @@ [<mempool_object>, ...]
Curl example
curl -X PUT {{ request.base_url }}watchonly/api/v1/mempool -d '{"endpoint": - <string>}' -H "Content-type: application/json" -H "X-Api-Key: - {{ user.wallets[0].adminkey }}" + >curl -X PUT {{ request.base_url }}watchonly/api/v1/mempool -d + '{"endpoint": <string>}' -H "Content-type: application/json" + -H "X-Api-Key: {{ user.wallets[0].adminkey }}" diff --git a/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html b/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html index 76068fcb..095aad3a 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html +++ b/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html @@ -22,8 +22,8 @@ [<withdraw_link_object>, ...]
Curl example
curl -X GET {{ request.base_url }}withdraw/api/v1/links -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url }}withdraw/api/v1/links -H + "X-Api-Key: {{ user.wallets[0].inkey }}" @@ -49,8 +49,9 @@ {"lnurl": <string>}
Curl example
curl -X GET {{ request.base_url }}withdraw/api/v1/links/<withdraw_id> -H - "X-Api-Key: {{ user.wallets[0].inkey }}" + >curl -X GET {{ request.base_url + }}withdraw/api/v1/links/<withdraw_id> -H "X-Api-Key: {{ + user.wallets[0].inkey }}" @@ -79,12 +80,12 @@ {"lnurl": <string>}
Curl example
curl -X POST {{ request.base_url }}withdraw/api/v1/links -d '{"title": - <string>, "min_withdrawable": <integer>, + >curl -X POST {{ request.base_url }}withdraw/api/v1/links -d + '{"title": <string>, "min_withdrawable": <integer>, "max_withdrawable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>, "webhook_url": <string>}' -H - "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" + "wait_time": <integer>, "is_unique": <boolean>, + "webhook_url": <string>}' -H "Content-type: application/json" -H + "X-Api-Key: {{ user.wallets[0].adminkey }}" @@ -115,8 +116,9 @@ {"lnurl": <string>}
Curl example
curl -X PUT {{ request.base_url }}withdraw/api/v1/links/<withdraw_id> -d - '{"title": <string>, "min_withdrawable": <integer>, + >curl -X PUT {{ request.base_url + }}withdraw/api/v1/links/<withdraw_id> -d '{"title": + <string>, "min_withdrawable": <integer>, "max_withdrawable": <integer>, "uses": <integer>, "wait_time": <integer>, "is_unique": <boolean>}' -H "Content-type: application/json" -H "X-Api-Key: {{ @@ -143,8 +145,9 @@
Curl example
curl -X DELETE {{ request.base_url }}withdraw/api/v1/links/<withdraw_id> - -H "X-Api-Key: {{ user.wallets[0].adminkey }}" + >curl -X DELETE {{ request.base_url + }}withdraw/api/v1/links/<withdraw_id> -H "X-Api-Key: {{ + user.wallets[0].adminkey }}" @@ -171,8 +174,8 @@
Curl example
curl -X GET {{ request.base_url - }}withdraw/api/v1/links/<the_hash>/<lnurl_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" + }}withdraw/api/v1/links/<the_hash>/<lnurl_id> -H + "X-Api-Key: {{ user.wallets[0].inkey }}" diff --git a/lnbits/extensions/withdraw/templates/withdraw/csv.html b/lnbits/extensions/withdraw/templates/withdraw/csv.html index d8f8c4d0..62902905 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/csv.html +++ b/lnbits/extensions/withdraw/templates/withdraw/csv.html @@ -1,10 +1,12 @@ -{% extends "print.html" %} {% block page %} {% for page in link %} {% for threes in page %} {% for one in threes %} {{one}}, {% endfor %} {% endfor %} {% endfor %} {% endblock %} {% block scripts %} +{% extends "print.html" %} {% block page %} {% for page in link %} {% for threes +in page %} {% for one in threes %} {{one}}, {% endfor %} {% endfor %} {% endfor +%} {% endblock %} {% block scripts %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html index f6ea220d..6d3ab374 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ b/lnbits/extensions/withdraw/templates/withdraw/index.html @@ -1,28 +1,40 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block scripts %} {{ window_vars(user) }} +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block page %}
-
- - - Quick vouchers - Advanced withdraw link(s) - - +
+ + + Quick vouchers + Advanced withdraw link(s) + + - - -
-
-
Withdraw links
-
-
- Export to CSV -
-
- - {% raw %} -