From 1eb3df60fc97387cd35e2eb2ed4463d0c0f958dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 17 Feb 2023 15:19:39 +0100 Subject: [PATCH] remove withdraw (#1517) --- lnbits/extensions/withdraw/README.md | 48 -- lnbits/extensions/withdraw/__init__.py | 27 - lnbits/extensions/withdraw/config.json | 6 - lnbits/extensions/withdraw/crud.py | 173 ------- lnbits/extensions/withdraw/lnurl.py | 200 -------- lnbits/extensions/withdraw/migrations.py | 134 ----- lnbits/extensions/withdraw/models.py | 79 --- .../withdraw/static/image/lnurl-withdraw.png | Bin 13081 -> 0 bytes lnbits/extensions/withdraw/static/js/index.js | 323 ------------ .../templates/withdraw/_api_docs.html | 204 -------- .../withdraw/templates/withdraw/_lnurl.html | 32 -- .../withdraw/templates/withdraw/csv.html | 12 - .../withdraw/templates/withdraw/display.html | 68 --- .../withdraw/templates/withdraw/index.html | 471 ------------------ .../withdraw/templates/withdraw/print_qr.html | 71 --- .../templates/withdraw/print_qr_custom.html | 113 ----- lnbits/extensions/withdraw/views.py | 149 ------ lnbits/extensions/withdraw/views_api.py | 128 ----- 18 files changed, 2238 deletions(-) delete mode 100644 lnbits/extensions/withdraw/README.md delete mode 100644 lnbits/extensions/withdraw/__init__.py delete mode 100644 lnbits/extensions/withdraw/config.json delete mode 100644 lnbits/extensions/withdraw/crud.py delete mode 100644 lnbits/extensions/withdraw/lnurl.py delete mode 100644 lnbits/extensions/withdraw/migrations.py delete mode 100644 lnbits/extensions/withdraw/models.py delete mode 100644 lnbits/extensions/withdraw/static/image/lnurl-withdraw.png delete mode 100644 lnbits/extensions/withdraw/static/js/index.js delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/_api_docs.html delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/_lnurl.html delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/csv.html delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/display.html delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/index.html delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/print_qr.html delete mode 100644 lnbits/extensions/withdraw/templates/withdraw/print_qr_custom.html delete mode 100644 lnbits/extensions/withdraw/views.py delete mode 100644 lnbits/extensions/withdraw/views_api.py diff --git a/lnbits/extensions/withdraw/README.md b/lnbits/extensions/withdraw/README.md deleted file mode 100644 index fce2c6e5a..000000000 --- a/lnbits/extensions/withdraw/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# LNURLw - -## Create a static QR code people can use to withdraw funds from a Lightning Network wallet - -LNURL is a range of lightning-network standards that allow us to use lightning-network differently. An LNURL withdraw is the permission for someone to pull a certain amount of funds from a lightning wallet. - -The most common use case for an LNURL withdraw is a faucet, although it is a very powerful technology, with much further reaching implications. For example, an LNURL withdraw could be minted to pay for a subscription service. Or you can have a LNURLw as an offline Lightning wallet (a pre paid "card"), you use to pay for something without having to even reach your smartphone. - -LNURL withdraw is a **very powerful tool** and should not have his use limited to just faucet applications. With LNURL withdraw, you have the ability to give someone the right to spend a range, once or multiple times. **This functionality has not existed in money before**. - -[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets) - -## Usage - -#### Quick Vouchers - -LNbits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes that you can print and distribute as rewards, onboarding people into Lightning Network, gifts, etc... - -1. Create Quick Vouchers\ - ![quick vouchers](https://i.imgur.com/IUfwdQz.jpg) - - select wallet - - set the amount each voucher will allow someone to withdraw - - set the amount of vouchers you want to create - _have in mind you need to have a balance on the wallet that supports the amount \* number of vouchers_ -2. You can now print, share, display your LNURLw links or QR codes\ - ![lnurlw created](https://i.imgur.com/X00twiX.jpg) - - on details you can print the vouchers\ - ![printable vouchers](https://i.imgur.com/2xLHbob.jpg) - - every printed LNURLw QR code is unique, it can only be used once -3. Bonus: you can use an LNbits themed voucher, or use a custom one. There's a _template.svg_ file in `static/images` folder if you want to create your own.\ - ![voucher](https://i.imgur.com/qyQoHi3.jpg) - -#### Advanced - -1. Create the Advanced LNURLw\ - ![create advanced lnurlw](https://i.imgur.com/OR0f885.jpg) - - set the wallet - - set a title for the LNURLw (it will show up in users wallet) - - define the minimum and maximum a user can withdraw, if you want a fixed amount set them both to an equal value - - set how many times can the LNURLw be scanned, if it's a one time use or it can be scanned 100 times - - LNbits has the "_Time between withdraws_" setting, you can define how long the LNURLw will be unavailable between scans - - you can set the time in _seconds, minutes or hours_ - - the "_Use unique withdraw QR..._" reduces the chance of your LNURL withdraw being exploited and depleted by one person, by generating a new QR code every time it's scanned -2. Print, share or display your LNURLw link or it's QR code\ - ![lnurlw created](https://i.imgur.com/X00twiX.jpg) - -**LNbits bonus:** If a user doesn't have a Lightning Network wallet and scans the LNURLw QR code with their smartphone camera, or a QR scanner app, they can follow the link provided to claim their satoshis and get an instant LNbits wallet! - -![](https://i.imgur.com/2zZ7mi8.jpg) diff --git a/lnbits/extensions/withdraw/__init__.py b/lnbits/extensions/withdraw/__init__.py deleted file mode 100644 index cb5eb9c43..000000000 --- a/lnbits/extensions/withdraw/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -from fastapi import APIRouter -from fastapi.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer - -db = Database("ext_withdraw") - -withdraw_static_files = [ - { - "path": "/withdraw/static", - "app": StaticFiles(packages=[("lnbits", "extensions/withdraw/static")]), - "name": "withdraw_static", - } -] - - -withdraw_ext: APIRouter = APIRouter(prefix="/withdraw", tags=["withdraw"]) - - -def withdraw_renderer(): - return template_renderer(["lnbits/extensions/withdraw/templates"]) - - -from .lnurl import * # noqa: F401,F403 -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/withdraw/config.json b/lnbits/extensions/withdraw/config.json deleted file mode 100644 index c22d69c85..000000000 --- a/lnbits/extensions/withdraw/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "LNURLw", - "short_description": "Make LNURL withdraw links", - "tile": "/withdraw/static/image/lnurl-withdraw.png", - "contributors": ["arcbtc", "eillarra"] -} diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py deleted file mode 100644 index 83dd0593d..000000000 --- a/lnbits/extensions/withdraw/crud.py +++ /dev/null @@ -1,173 +0,0 @@ -from datetime import datetime -from typing import List, Optional, Union - -import shortuuid - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import CreateWithdrawData, HashCheck, WithdrawLink - - -async def create_withdraw_link( - data: CreateWithdrawData, wallet_id: str -) -> WithdrawLink: - link_id = urlsafe_short_hash()[:6] - available_links = ",".join([str(i) for i in range(data.uses)]) - await db.execute( - """ - INSERT INTO withdraw.withdraw_link ( - id, - wallet, - title, - min_withdrawable, - max_withdrawable, - uses, - wait_time, - is_unique, - unique_hash, - k1, - open_time, - usescsv, - webhook_url, - webhook_headers, - webhook_body, - custom_url - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - link_id, - wallet_id, - data.title, - data.min_withdrawable, - data.max_withdrawable, - data.uses, - data.wait_time, - int(data.is_unique), - urlsafe_short_hash(), - urlsafe_short_hash(), - int(datetime.now().timestamp()) + data.wait_time, - available_links, - data.webhook_url, - data.webhook_headers, - data.webhook_body, - data.custom_url, - ), - ) - link = await get_withdraw_link(link_id, 0) - assert link, "Newly created link couldn't be retrieved" - return link - - -async def get_withdraw_link(link_id: str, num=0) -> Optional[WithdrawLink]: - row = await db.fetchone( - "SELECT * FROM withdraw.withdraw_link WHERE id = ?", (link_id,) - ) - if not row: - return None - - link = dict(**row) - link["number"] = num - - return WithdrawLink.parse_obj(link) - - -async def get_withdraw_link_by_hash(unique_hash: str, num=0) -> Optional[WithdrawLink]: - row = await db.fetchone( - "SELECT * FROM withdraw.withdraw_link WHERE unique_hash = ?", (unique_hash,) - ) - if not row: - return None - - link = dict(**row) - link["number"] = num - - return WithdrawLink.parse_obj(link) - - -async def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT * FROM withdraw.withdraw_link WHERE wallet IN ({q})", (*wallet_ids,) - ) - return [WithdrawLink(**row) for row in rows] - - -async def remove_unique_withdraw_link(link: WithdrawLink, unique_hash: str) -> None: - unique_links = [ - x.strip() - for x in link.usescsv.split(",") - if unique_hash != shortuuid.uuid(name=link.id + link.unique_hash + x.strip()) - ] - await update_withdraw_link( - link.id, - usescsv=",".join(unique_links), - ) - - -async def increment_withdraw_link(link: WithdrawLink) -> None: - await update_withdraw_link( - link.id, - used=link.used + 1, - open_time=link.wait_time + int(datetime.now().timestamp()), - ) - - -async def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]: - if "is_unique" in kwargs: - kwargs["is_unique"] = int(kwargs["is_unique"]) - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE withdraw.withdraw_link SET {q} WHERE id = ?", - (*kwargs.values(), link_id), - ) - row = await db.fetchone( - "SELECT * FROM withdraw.withdraw_link WHERE id = ?", (link_id,) - ) - return WithdrawLink(**row) if row else None - - -async def delete_withdraw_link(link_id: str) -> None: - await db.execute("DELETE FROM withdraw.withdraw_link WHERE id = ?", (link_id,)) - - -def chunks(lst, n): - for i in range(0, len(lst), n): - yield lst[i : i + n] - - -async def create_hash_check(the_hash: str, lnurl_id: str) -> HashCheck: - await db.execute( - """ - INSERT INTO withdraw.hash_check ( - id, - lnurl_id - ) - VALUES (?, ?) - """, - (the_hash, lnurl_id), - ) - hashCheck = await get_hash_check(the_hash, lnurl_id) - return hashCheck - - -async def get_hash_check(the_hash: str, lnurl_id: str) -> HashCheck: - rowid = await db.fetchone( - "SELECT * FROM withdraw.hash_check WHERE id = ?", (the_hash,) - ) - rowlnurl = await db.fetchone( - "SELECT * FROM withdraw.hash_check WHERE lnurl_id = ?", (lnurl_id,) - ) - if not rowlnurl: - await create_hash_check(the_hash, lnurl_id) - return HashCheck(lnurl=True, hash=False) - else: - if not rowid: - await create_hash_check(the_hash, lnurl_id) - return HashCheck(lnurl=True, hash=False) - else: - return HashCheck(lnurl=True, hash=True) diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py deleted file mode 100644 index 5ef521fac..000000000 --- a/lnbits/extensions/withdraw/lnurl.py +++ /dev/null @@ -1,200 +0,0 @@ -import json -from datetime import datetime -from http import HTTPStatus - -import httpx -import shortuuid -from fastapi import HTTPException, Query, Request, Response -from loguru import logger - -from lnbits.core.crud import update_payment_extra -from lnbits.core.services import pay_invoice - -from . import withdraw_ext -from .crud import ( - get_withdraw_link_by_hash, - increment_withdraw_link, - remove_unique_withdraw_link, -) -from .models import WithdrawLink - - -@withdraw_ext.get( - "/api/v1/lnurl/{unique_hash}", - response_class=Response, - name="withdraw.api_lnurl_response", -) -async def api_lnurl_response(request: Request, unique_hash): - link = await get_withdraw_link_by_hash(unique_hash) - - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - - if link.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", - "callback": url, - "k1": link.k1, - "minWithdrawable": link.min_withdrawable * 1000, - "maxWithdrawable": link.max_withdrawable * 1000, - "defaultDescription": link.title, - "webhook_url": link.webhook_url, - "webhook_headers": link.webhook_headers, - "webhook_body": link.webhook_body, - } - - return json.dumps(withdrawResponse) - - -@withdraw_ext.get( - "/api/v1/lnurl/cb/{unique_hash}", - name="withdraw.api_lnurl_callback", - summary="lnurl withdraw callback", - description=""" - This endpoints allows you to put unique_hash, k1 - and a payment_request to get your payment_request paid. - """, - response_description="JSON with status", - responses={ - 200: {"description": "status: OK"}, - 400: {"description": "k1 is wrong or link open time or withdraw not working."}, - 404: {"description": "withdraw link not found."}, - 405: {"description": "withdraw link is spent."}, - }, -) -async def api_lnurl_callback( - unique_hash, - k1: str = Query(...), - pr: str = Query(...), - id_unique_hash=None, -): - link = await get_withdraw_link_by_hash(unique_hash) - now = int(datetime.now().timestamp()) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found." - ) - - if link.is_spent: - raise HTTPException( - status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="withdraw is spent." - ) - - if link.k1 != k1: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="k1 is wrong.") - - if now < link.open_time: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, - detail=f"wait link open_time {link.open_time - now} seconds.", - ) - - if id_unique_hash: - if check_unique_link(link, id_unique_hash): - await remove_unique_withdraw_link(link, id_unique_hash) - else: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found." - ) - - try: - payment_hash = await pay_invoice( - wallet_id=link.wallet, - payment_request=pr, - max_sat=link.max_withdrawable, - extra={"tag": "withdraw"}, - ) - await increment_withdraw_link(link) - if link.webhook_url: - await dispatch_webhook(link, payment_hash, pr) - return {"status": "OK"} - except Exception as e: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, detail=f"withdraw not working. {str(e)}" - ) - - -def check_unique_link(link: WithdrawLink, unique_hash: str) -> bool: - return any( - unique_hash == shortuuid.uuid(name=link.id + link.unique_hash + x.strip()) - for x in link.usescsv.split(",") - ) - - -async def dispatch_webhook( - link: WithdrawLink, payment_hash: str, payment_request: str -) -> None: - async with httpx.AsyncClient() as client: - try: - r: httpx.Response = await client.post( - link.webhook_url, - json={ - "payment_hash": payment_hash, - "payment_request": payment_request, - "lnurlw": link.id, - "body": json.loads(link.webhook_body) if link.webhook_body else "", - }, - headers=json.loads(link.webhook_headers) - if link.webhook_headers - else None, - timeout=40, - ) - await update_payment_extra( - payment_hash=payment_hash, - extra={ - "wh_success": r.is_success, - "wh_message": r.reason_phrase, - "wh_response": r.text, - }, - outgoing=True, - ) - except Exception as exc: - # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid - logger.error("Caught exception when dispatching webhook url: " + str(exc)) - await update_payment_extra( - payment_hash=payment_hash, - extra={"wh_success": False, "wh_message": str(exc)}, - outgoing=True, - ) - - -# FOR LNURLs WHICH ARE UNIQUE -@withdraw_ext.get( - "/api/v1/lnurl/{unique_hash}/{id_unique_hash}", - response_class=Response, - name="withdraw.api_lnurl_multi_response", -) -async def api_lnurl_multi_response(request: Request, unique_hash, id_unique_hash): - link = await get_withdraw_link_by_hash(unique_hash) - - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found." - ) - - if link.is_spent: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent." - ) - - if not check_unique_link(link, id_unique_hash): - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found." - ) - - url = request.url_for("withdraw.api_lnurl_callback", unique_hash=link.unique_hash) - withdrawResponse = { - "tag": "withdrawRequest", - "callback": url + "?id_unique_hash=" + id_unique_hash, - "k1": link.k1, - "minWithdrawable": link.min_withdrawable * 1000, - "maxWithdrawable": link.max_withdrawable * 1000, - "defaultDescription": link.title, - } - return json.dumps(withdrawResponse) diff --git a/lnbits/extensions/withdraw/migrations.py b/lnbits/extensions/withdraw/migrations.py deleted file mode 100644 index 95805ae75..000000000 --- a/lnbits/extensions/withdraw/migrations.py +++ /dev/null @@ -1,134 +0,0 @@ -async def m001_initial(db): - """ - Creates an improved withdraw table and migrates the existing data. - """ - await db.execute( - f""" - CREATE TABLE withdraw.withdraw_links ( - id TEXT PRIMARY KEY, - wallet TEXT, - title TEXT, - min_withdrawable {db.big_int} DEFAULT 1, - max_withdrawable {db.big_int} DEFAULT 1, - uses INTEGER DEFAULT 1, - wait_time INTEGER, - is_unique INTEGER DEFAULT 0, - unique_hash TEXT UNIQUE, - k1 TEXT, - open_time INTEGER, - used INTEGER DEFAULT 0, - usescsv TEXT - ); - """ - ) - - -async def m002_change_withdraw_table(db): - """ - Creates an improved withdraw table and migrates the existing data. - """ - await db.execute( - f""" - CREATE TABLE withdraw.withdraw_link ( - id TEXT PRIMARY KEY, - wallet TEXT, - title TEXT, - min_withdrawable {db.big_int} DEFAULT 1, - max_withdrawable {db.big_int} DEFAULT 1, - uses INTEGER DEFAULT 1, - wait_time INTEGER, - is_unique INTEGER DEFAULT 0, - unique_hash TEXT UNIQUE, - k1 TEXT, - open_time INTEGER, - used INTEGER DEFAULT 0, - usescsv TEXT - ); - """ - ) - - for row in [ - list(row) for row in await db.fetchall("SELECT * FROM withdraw.withdraw_links") - ]: - usescsv = "" - - for i in range(row[5]): - if row[7]: - usescsv += "," + str(i + 1) - else: - usescsv += "," + str(1) - usescsv = usescsv[1:] - await db.execute( - """ - INSERT INTO withdraw.withdraw_link ( - id, - wallet, - title, - min_withdrawable, - max_withdrawable, - uses, - wait_time, - is_unique, - unique_hash, - k1, - open_time, - used, - usescsv - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - row[0], - row[1], - row[2], - row[3], - row[4], - row[5], - row[6], - row[7], - row[8], - row[9], - row[10], - row[11], - usescsv, - ), - ) - await db.execute("DROP TABLE withdraw.withdraw_links") - - -async def m003_make_hash_check(db): - """ - Creates a hash check table. - """ - await db.execute( - """ - CREATE TABLE withdraw.hash_check ( - id TEXT PRIMARY KEY, - lnurl_id TEXT - ); - """ - ) - - -async def m004_webhook_url(db): - """ - Adds webhook_url - """ - await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_url TEXT;") - - -async def m005_add_custom_print_design(db): - """ - Adds custom print design - """ - await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;") - - -async def m006_webhook_headers_and_body(db): - """ - Add headers and body to webhooks - """ - await db.execute( - "ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_headers TEXT;" - ) - await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_body TEXT;") diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py deleted file mode 100644 index 49421a796..000000000 --- a/lnbits/extensions/withdraw/models.py +++ /dev/null @@ -1,79 +0,0 @@ -import shortuuid -from fastapi import Query -from lnurl import Lnurl, LnurlWithdrawResponse -from lnurl import encode as lnurl_encode -from lnurl.models import ClearnetUrl, MilliSatoshi -from pydantic import BaseModel -from starlette.requests import Request - - -class CreateWithdrawData(BaseModel): - title: str = Query(...) - min_withdrawable: int = Query(..., ge=1) - max_withdrawable: int = Query(..., ge=1) - uses: int = Query(..., ge=1) - wait_time: int = Query(..., ge=1) - is_unique: bool - webhook_url: str = Query(None) - webhook_headers: str = Query(None) - webhook_body: str = Query(None) - custom_url: str = Query(None) - - -class WithdrawLink(BaseModel): - id: str - wallet: str = Query(None) - title: str = Query(None) - min_withdrawable: int = Query(0) - max_withdrawable: int = Query(0) - uses: int = Query(0) - wait_time: int = Query(0) - is_unique: bool = Query(False) - unique_hash: str = Query(0) - k1: str = Query(None) - open_time: int = Query(0) - used: int = Query(0) - usescsv: str = Query(None) - number: int = Query(0) - webhook_url: str = Query(None) - webhook_headers: str = Query(None) - webhook_body: str = Query(None) - custom_url: str = Query(None) - - @property - def is_spent(self) -> bool: - return self.used >= self.uses - - def lnurl(self, req: Request) -> Lnurl: - if self.is_unique: - usescssv = self.usescsv.split(",") - tohash = self.id + self.unique_hash + usescssv[self.number] - multihash = shortuuid.uuid(name=tohash) - url = req.url_for( - "withdraw.api_lnurl_multi_response", - unique_hash=self.unique_hash, - id_unique_hash=multihash, - ) - else: - url = req.url_for( - "withdraw.api_lnurl_response", unique_hash=self.unique_hash - ) - - return lnurl_encode(url) - - def lnurl_response(self, req: Request) -> LnurlWithdrawResponse: - url = req.url_for( - name="withdraw.api_lnurl_callback", unique_hash=self.unique_hash - ) - return LnurlWithdrawResponse( - callback=ClearnetUrl(url, scheme="https"), - k1=self.k1, - minWithdrawable=MilliSatoshi(self.min_withdrawable * 1000), - maxWithdrawable=MilliSatoshi(self.max_withdrawable * 1000), - defaultDescription=self.title, - ) - - -class HashCheck(BaseModel): - hash: bool - lnurl: bool diff --git a/lnbits/extensions/withdraw/static/image/lnurl-withdraw.png b/lnbits/extensions/withdraw/static/image/lnurl-withdraw.png deleted file mode 100644 index 4f036423ab12fa866ac401fdac0b6e692847f70f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13081 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 diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js deleted file mode 100644 index ced78439d..000000000 --- a/lnbits/extensions/withdraw/static/js/index.js +++ /dev/null @@ -1,323 +0,0 @@ -/* global Vue, VueQrcode, _, Quasar, LOCALE, windowMixin, LNbits */ - -Vue.component(VueQrcode.name, VueQrcode) - -var locationPath = [ - window.location.protocol, - '//', - window.location.host, - window.location.pathname -].join('') - -var mapWithdrawLink = function (obj) { - obj._data = _.clone(obj) - obj.date = Quasar.utils.date.formatDate( - new Date(obj.time * 1000), - 'YYYY-MM-DD HH:mm' - ) - obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable) - obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable) - obj.uses_left = obj.uses - obj.used - obj.print_url = [locationPath, 'print/', obj.id].join('') - obj.withdraw_url = [locationPath, obj.id].join('') - obj._data.use_custom = Boolean(obj.custom_url) - return obj -} - -const CUSTOM_URL = '/static/images/default_voucher.png' - -new Vue({ - el: '#vue', - mixins: [windowMixin], - data: function () { - return { - checker: null, - withdrawLinks: [], - withdrawLinksTable: { - columns: [ - {name: 'id', align: 'left', label: 'ID', field: 'id'}, - {name: 'title', align: 'left', label: 'Title', field: 'title'}, - { - name: 'wait_time', - align: 'right', - label: 'Wait', - field: 'wait_time' - }, - { - name: 'uses_left', - align: 'right', - label: 'Uses left', - field: 'uses_left' - }, - {name: 'min', align: 'right', label: 'Min (sat)', field: 'min_fsat'}, - {name: 'max', align: 'right', label: 'Max (sat)', field: 'max_fsat'} - ], - pagination: { - rowsPerPage: 10 - } - }, - nfcTagWriting: false, - formDialog: { - show: false, - secondMultiplier: 'seconds', - secondMultiplierOptions: ['seconds', 'minutes', 'hours'], - data: { - is_unique: false, - use_custom: false, - has_webhook: false - } - }, - simpleformDialog: { - show: false, - data: { - is_unique: true, - use_custom: false, - title: 'Vouchers', - min_withdrawable: 0, - wait_time: 1 - } - }, - qrCodeDialog: { - show: false, - data: null - } - } - }, - computed: { - sortedWithdrawLinks: function () { - return this.withdrawLinks.sort(function (a, b) { - return b.uses_left - a.uses_left - }) - } - }, - methods: { - getWithdrawLinks: function () { - var self = this - - LNbits.api - .request( - 'GET', - '/withdraw/api/v1/links?all_wallets=true', - this.g.user.wallets[0].inkey - ) - .then(function (response) { - self.withdrawLinks = response.data.map(function (obj) { - return mapWithdrawLink(obj) - }) - }) - .catch(function (error) { - clearInterval(self.checker) - LNbits.utils.notifyApiError(error) - }) - }, - closeFormDialog: function () { - this.formDialog.data = { - is_unique: false, - use_custom: false - } - }, - simplecloseFormDialog: function () { - this.simpleformDialog.data = { - is_unique: false, - use_custom: false - } - }, - openQrCodeDialog: function (linkId) { - var link = _.findWhere(this.withdrawLinks, {id: linkId}) - - this.qrCodeDialog.data = _.clone(link) - this.qrCodeDialog.data.url = - window.location.protocol + '//' + window.location.host - this.qrCodeDialog.show = true - }, - openUpdateDialog: function (linkId) { - var link = _.findWhere(this.withdrawLinks, {id: linkId}) - this.formDialog.data = _.clone(link._data) - this.formDialog.show = true - }, - sendFormData: function () { - var wallet = _.findWhere(this.g.user.wallets, { - id: this.formDialog.data.wallet - }) - var data = _.omit(this.formDialog.data, 'wallet') - - if (!data.use_custom) { - data.custom_url = null - } - - if (data.use_custom && !data?.custom_url) { - data.custom_url = CUSTOM_URL - } - - data.wait_time = - data.wait_time * - { - seconds: 1, - minutes: 60, - hours: 3600 - }[this.formDialog.secondMultiplier] - if (data.id) { - this.updateWithdrawLink(wallet, data) - } else { - this.createWithdrawLink(wallet, data) - } - }, - simplesendFormData: function () { - var wallet = _.findWhere(this.g.user.wallets, { - id: this.simpleformDialog.data.wallet - }) - var data = _.omit(this.simpleformDialog.data, 'wallet') - - data.wait_time = 1 - data.min_withdrawable = data.max_withdrawable - data.title = 'vouchers' - data.is_unique = true - - if (!data.use_custom) { - data.custom_url = null - } - - if (data.use_custom && !data?.custom_url) { - data.custom_url = '/static/images/default_voucher.png' - } - - if (data.id) { - this.updateWithdrawLink(wallet, data) - } else { - this.createWithdrawLink(wallet, data) - } - }, - updateWithdrawLink: function (wallet, data) { - var self = this - const body = _.pick( - data, - 'title', - 'min_withdrawable', - 'max_withdrawable', - 'uses', - 'wait_time', - 'is_unique', - 'webhook_url', - 'webhook_headers', - 'webhook_body', - 'custom_url' - ) - - if (data.has_webhook) { - body = { - ...body, - webhook_url: data.webhook_url, - webhook_headers: data.webhook_headers, - webhook_body: data.webhook_body - } - } - - LNbits.api - .request( - 'PUT', - '/withdraw/api/v1/links/' + data.id, - wallet.adminkey, - body - ) - .then(function (response) { - self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { - return obj.id === data.id - }) - self.withdrawLinks.push(mapWithdrawLink(response.data)) - self.formDialog.show = false - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) - }) - }, - createWithdrawLink: function (wallet, data) { - var self = this - - LNbits.api - .request('POST', '/withdraw/api/v1/links', wallet.adminkey, data) - .then(function (response) { - self.withdrawLinks.push(mapWithdrawLink(response.data)) - self.formDialog.show = false - self.simpleformDialog.show = false - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) - }) - }, - deleteWithdrawLink: function (linkId) { - var self = this - var link = _.findWhere(this.withdrawLinks, {id: linkId}) - - LNbits.utils - .confirmDialog('Are you sure you want to delete this withdraw link?') - .onOk(function () { - LNbits.api - .request( - 'DELETE', - '/withdraw/api/v1/links/' + linkId, - _.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey - ) - .then(function (response) { - self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { - return obj.id === linkId - }) - }) - .catch(function (error) { - LNbits.utils.notifyApiError(error) - }) - }) - }, - writeNfcTag: async function (lnurl) { - try { - if (typeof NDEFReader == 'undefined') { - throw { - toString: function () { - return 'NFC not supported on this device or browser.' - } - } - } - - const ndef = new NDEFReader() - - this.nfcTagWriting = true - this.$q.notify({ - message: 'Tap your NFC tag to write the LNURL-withdraw link to it.' - }) - - await ndef.write({ - records: [{recordType: 'url', data: 'lightning:' + lnurl, lang: 'en'}] - }) - - this.nfcTagWriting = false - this.$q.notify({ - type: 'positive', - message: 'NFC tag written successfully.' - }) - } catch (error) { - this.nfcTagWriting = false - this.$q.notify({ - type: 'negative', - message: error - ? error.toString() - : 'An unexpected error has occurred.' - }) - } - }, - exportCSV() { - LNbits.utils.exportCSV( - this.withdrawLinksTable.columns, - this.withdrawLinks, - 'withdraw-links' - ) - } - }, - created: function () { - if (this.g.user.wallets.length) { - var getWithdrawLinks = this.getWithdrawLinks - getWithdrawLinks() - this.checker = setInterval(function () { - getWithdrawLinks() - }, 300000) - } - } -}) diff --git a/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html b/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html deleted file mode 100644 index ff88189d5..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/_api_docs.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - GET /withdraw/api/v1/links -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<withdraw_link_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}withdraw/api/v1/links -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /withdraw/api/v1/links/<withdraw_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 201 CREATED (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X GET {{ request.base_url - }}withdraw/api/v1/links/<withdraw_id> -H "X-Api-Key: {{ - user.wallets[0].inkey }}" - -
-
-
- - - - POST /withdraw/api/v1/links -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"title": <string>, "min_withdrawable": <integer>, - "max_withdrawable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>, - "webhook_url": <string>} -
- Returns 201 CREATED (application/json) -
- {"lnurl": <string>} -
Curl example
- 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 }}" - -
-
-
- - - - PUT - /withdraw/api/v1/links/<withdraw_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"title": <string>, "min_withdrawable": <integer>, - "max_withdrawable": <integer>, "uses": <integer>, - "wait_time": <integer>, "is_unique": <boolean>} -
- Returns 200 OK (application/json) -
- {"lnurl": <string>} -
Curl example
- 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: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - DELETE - /withdraw/api/v1/links/<withdraw_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}withdraw/api/v1/links/<withdraw_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - GET - /withdraw/api/v1/links/<the_hash>/<lnurl_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 201 CREATED (application/json) -
- {"status": <bool>} -
Curl example
- curl -X GET {{ request.base_url - }}withdraw/api/v1/links/<the_hash>/<lnurl_id> -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /withdraw/img/<lnurl_id> -
Curl example
- curl -X GET {{ request.base_url }}withdraw/img/<lnurl_id>" - -
-
-
-
diff --git a/lnbits/extensions/withdraw/templates/withdraw/_lnurl.html b/lnbits/extensions/withdraw/templates/withdraw/_lnurl.html deleted file mode 100644 index f6b520502..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/_lnurl.html +++ /dev/null @@ -1,32 +0,0 @@ - - - -

- WARNING: LNURL must be used over https or TOR
- LNURL is a range of lightning-network standards that allow us to use - lightning-network differently. An LNURL withdraw is the permission for - someone to pull a certain amount of funds from a lightning wallet. In - this extension time is also added - an amount can be withdraw over a - period of time. A typical use case for an LNURL withdraw is a faucet, - although it is a very powerful technology, with much further reaching - implications. For example, an LNURL withdraw could be minted to pay for - a subscription service. -

-

- Exploring LNURL and finding use cases, is really helping inform - lightning protocol development, rather than the protocol dictating how - lightning-network should be engaged with. -

- Check - Awesome LNURL - for further information. -
-
-
diff --git a/lnbits/extensions/withdraw/templates/withdraw/csv.html b/lnbits/extensions/withdraw/templates/withdraw/csv.html deleted file mode 100644 index 62902905d..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/csv.html +++ /dev/null @@ -1,12 +0,0 @@ -{% 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 %} diff --git a/lnbits/extensions/withdraw/templates/withdraw/display.html b/lnbits/extensions/withdraw/templates/withdraw/display.html deleted file mode 100644 index 3ef545c30..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/display.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
- {% if link.is_spent %} - Withdraw is spent. - {% endif %} - - - - - - -
-
- Copy LNURL - -
-
-
-
-
- - -
- LNbits LNURL-withdraw link -
-

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

-
- - - {% include "withdraw/_lnurl.html" %} - -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html deleted file mode 100644 index 3ae244e67..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ /dev/null @@ -1,471 +0,0 @@ -{% 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) - - - - - -
-
-
Withdraw links
-
-
- Export to CSV -
-
- - {% raw %} - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} LNURL-withdraw extension -
-
- - - - {% include "withdraw/_api_docs.html" %} - - {% include "withdraw/_lnurl.html" %} - - -
-
- - - - - - - - - - -
-
- - -
-
- - -
-
- - - - - - - - - - - Use a custom voucher design - You can use an LNbits voucher design or a custom - one - - - - - - - - - - - Use unique withdraw QR codes to reduce `assmilking` - - This is recommended if you are sharing the links on social - media or print QR codes. - - - -
- Update withdraw link - Create withdraw link - Cancel -
-
-
-
- - - - - - - - - - - - - - - Use a custom voucher design - You can use an LNbits voucher design or a custom - one - - - - - -
- Create vouchers - Cancel -
-
-
-
- - - - - - {% raw %} - -

- ID: {{ qrCodeDialog.data.id }}
- Unique: {{ qrCodeDialog.data.is_unique }} - (QR code will change after each withdrawal)
- Max. withdrawable: {{ - qrCodeDialog.data.max_withdrawable }} sat
- Wait time: {{ qrCodeDialog.data.wait_time }} seconds
- Withdraws: {{ qrCodeDialog.data.used }} / {{ - qrCodeDialog.data.uses }} - -

- {% endraw %} -
- Copy LNURL - Copy sharable link - - Write to NFC - Print - Close -
-
-
-
-{% endblock %} diff --git a/lnbits/extensions/withdraw/templates/withdraw/print_qr.html b/lnbits/extensions/withdraw/templates/withdraw/print_qr.html deleted file mode 100644 index df4ca7d72..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/print_qr.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends "print.html" %} {% block page %} - -
-
- {% for page in link %} - - - {% for threes in page %} - - {% for one in threes %} - - {% endfor %} - - {% endfor %} -
-
- -
-
-
- {% endfor %} -
-
-{% endblock %} {% block styles %} - -{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/withdraw/templates/withdraw/print_qr_custom.html b/lnbits/extensions/withdraw/templates/withdraw/print_qr_custom.html deleted file mode 100644 index ca47cec44..000000000 --- a/lnbits/extensions/withdraw/templates/withdraw/print_qr_custom.html +++ /dev/null @@ -1,113 +0,0 @@ -{% extends "print.html" %} {% block page %} - -
-
- {% for page in link %} - - {% for one in page %} -
- ... - {{ amt }} sats -
- -
-
- {% endfor %} -
- {% endfor %} -
-
-{% endblock %} {% block styles %} - -{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/withdraw/views.py b/lnbits/extensions/withdraw/views.py deleted file mode 100644 index e8e5719a4..000000000 --- a/lnbits/extensions/withdraw/views.py +++ /dev/null @@ -1,149 +0,0 @@ -from http import HTTPStatus -from io import BytesIO - -import pyqrcode -from fastapi import Depends, HTTPException, Request -from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse, StreamingResponse - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import withdraw_ext, withdraw_renderer -from .crud import chunks, get_withdraw_link - -templates = Jinja2Templates(directory="templates") - - -@withdraw_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return withdraw_renderer().TemplateResponse( - "withdraw/index.html", {"request": request, "user": user.dict()} - ) - - -@withdraw_ext.get("/{link_id}", response_class=HTMLResponse) -async def display(request: Request, link_id): - link = await get_withdraw_link(link_id, 0) - - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - return withdraw_renderer().TemplateResponse( - "withdraw/display.html", - { - "request": request, - "link": link.dict(), - "lnurl": link.lnurl(req=request), - "unique": True, - }, - ) - - -@withdraw_ext.get("/img/{link_id}", response_class=StreamingResponse) -async def img(request: Request, link_id): - link = await get_withdraw_link(link_id, 0) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - qr = pyqrcode.create(link.lnurl(request)) - stream = BytesIO() - qr.svg(stream, scale=3) - stream.seek(0) - - async def _generator(stream: BytesIO): - yield stream.getvalue() - - return StreamingResponse( - _generator(stream), - headers={ - "Content-Type": "image/svg+xml", - "Cache-Control": "no-cache, no-store, must-revalidate", - "Pragma": "no-cache", - "Expires": "0", - }, - ) - - -@withdraw_ext.get("/print/{link_id}", response_class=HTMLResponse) -async def print_qr(request: Request, link_id): - link = await get_withdraw_link(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - # response.status_code = HTTPStatus.NOT_FOUND - # return "Withdraw link does not exist." - - if link.uses == 0: - - return withdraw_renderer().TemplateResponse( - "withdraw/print_qr.html", - {"request": request, "link": link.dict(), "unique": False}, - ) - links = [] - count = 0 - - for x in link.usescsv.split(","): - linkk = await get_withdraw_link(link_id, count) - if not linkk: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - links.append(str(linkk.lnurl(request))) - count = count + 1 - page_link = list(chunks(links, 2)) - linked = list(chunks(page_link, 5)) - - if link.custom_url: - return withdraw_renderer().TemplateResponse( - "withdraw/print_qr_custom.html", - { - "request": request, - "link": page_link, - "unique": True, - "custom_url": link.custom_url, - "amt": link.max_withdrawable, - }, - ) - - return withdraw_renderer().TemplateResponse( - "withdraw/print_qr.html", {"request": request, "link": linked, "unique": True} - ) - - -@withdraw_ext.get("/csv/{link_id}", response_class=HTMLResponse) -async def csv(request: Request, link_id): - link = await get_withdraw_link(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - # response.status_code = HTTPStatus.NOT_FOUND - # return "Withdraw link does not exist." - - if link.uses == 0: - - return withdraw_renderer().TemplateResponse( - "withdraw/csv.html", - {"request": request, "link": link.dict(), "unique": False}, - ) - links = [] - count = 0 - - for x in link.usescsv.split(","): - linkk = await get_withdraw_link(link_id, count) - if not linkk: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist." - ) - links.append(str(linkk.lnurl(request))) - count = count + 1 - page_link = list(chunks(links, 2)) - linked = list(chunks(page_link, 5)) - - return withdraw_renderer().TemplateResponse( - "withdraw/csv.html", {"request": request, "link": linked, "unique": True} - ) diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py deleted file mode 100644 index 525796c90..000000000 --- a/lnbits/extensions/withdraw/views_api.py +++ /dev/null @@ -1,128 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, HTTPException, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl - -from lnbits.core.crud import get_user -from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key - -from . import withdraw_ext -from .crud import ( - create_withdraw_link, - delete_withdraw_link, - get_hash_check, - get_withdraw_link, - get_withdraw_links, - update_withdraw_link, -) -from .models import CreateWithdrawData - - -@withdraw_ext.get("/api/v1/links", status_code=HTTPStatus.OK) -async def api_links( - req: Request, - wallet: WalletTypeInfo = Depends(get_key_type), - all_wallets: bool = Query(False), -): - wallet_ids = [wallet.wallet.id] - - if all_wallets: - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - - try: - return [ - {**link.dict(), **{"lnurl": link.lnurl(req)}} - for link in await get_withdraw_links(wallet_ids) - ] - - except LnurlInvalidUrl: - raise HTTPException( - status_code=HTTPStatus.UPGRADE_REQUIRED, - detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.", - ) - - -@withdraw_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_retrieve( - link_id: str, request: Request, wallet: WalletTypeInfo = Depends(get_key_type) -): - link = await get_withdraw_link(link_id, 0) - - if not link: - raise HTTPException( - detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN - ) - return {**link.dict(), **{"lnurl": link.lnurl(request)}} - - -@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED) -@withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_create_or_update( - req: Request, - data: CreateWithdrawData, - link_id: str = Query(None), - wallet: WalletTypeInfo = Depends(require_admin_key), -): - if data.uses > 250: - 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 - ) - - if data.max_withdrawable < data.min_withdrawable: - raise HTTPException( - detail="`max_withdrawable` needs to be at least `min_withdrawable`.", - status_code=HTTPStatus.BAD_REQUEST, - ) - - if link_id: - link = await get_withdraw_link(link_id, 0) - if not link: - raise HTTPException( - detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND - ) - if link.wallet != wallet.wallet.id: - raise HTTPException( - detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN - ) - link = await update_withdraw_link(link_id, **data.dict()) - else: - link = await create_withdraw_link(wallet_id=wallet.wallet.id, data=data) - assert link - return {**link.dict(), **{"lnurl": link.lnurl(req)}} - - -@withdraw_ext.delete("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(require_admin_key)): - link = await get_withdraw_link(link_id) - - if not link: - raise HTTPException( - detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN - ) - - await delete_withdraw_link(link_id) - return {"success": True} - - -@withdraw_ext.get( - "/api/v1/links/{the_hash}/{lnurl_id}", - status_code=HTTPStatus.OK, - dependencies=[Depends(get_key_type)], -) -async def api_hash_retrieve(the_hash, lnurl_id): - hashCheck = await get_hash_check(the_hash, lnurl_id) - return hashCheck