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 fce2c6e5..00000000 --- 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 cb5eb9c4..00000000 --- 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 c22d69c8..00000000 --- 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 83dd0593..00000000 --- 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 5ef521fa..00000000 --- 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 95805ae7..00000000 --- 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 49421a79..00000000 --- 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 ced78439..00000000 --- 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 ff88189d..00000000 --- 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 f6b52050..00000000 --- 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 62902905..00000000 --- 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 3ef545c3..00000000 --- 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 3ae244e6..00000000 --- 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 df4ca7d7..00000000 --- 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 ca47cec4..00000000 --- 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 e8e5719a..00000000 --- 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 525796c9..00000000 --- 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