From b357410c88d4f9e7a55496b84cf711fed8620dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 15 Oct 2025 12:11:44 +0200 Subject: [PATCH 1/2] feat: add lud21 verify base spec. - added as optional in advanced settings - api endpoint returning the success response --- crud.py | 1 + migrations.py | 6 ++++++ models.py | 2 ++ static/js/index.js | 1 + templates/lnurlp/index.html | 13 +++++++++++++ views_lnurl.py | 39 +++++++++++++++++++++++++++++++++++-- 6 files changed, 60 insertions(+), 2 deletions(-) diff --git a/crud.py b/crud.py index 4fb6f82..dec6007 100644 --- a/crud.py +++ b/crud.py @@ -65,6 +65,7 @@ async def create_pay_link(data: CreatePayLinkData) -> PayLink: created_at=now, updated_at=now, disposable=data.disposable if data.disposable is not None else True, + verify=data.verify if data.verify is not None else False, ) await db.insert("lnurlp.pay_links", link) diff --git a/migrations.py b/migrations.py index 2b0e28b..da163b2 100644 --- a/migrations.py +++ b/migrations.py @@ -218,3 +218,9 @@ async def m012_add_disposable(db: Connection): await db.execute( "ALTER TABLE lnurlp.pay_links ADD COLUMN disposable BOOLEAN DEFAULT TRUE" ) + + +async def m013_add_verify(db: Connection): + await db.execute( + "ALTER TABLE lnurlp.pay_links ADD COLUMN verify BOOLEAN DEFAULT FALSE" + ) diff --git a/models.py b/models.py index d8e6d6d..f209ccb 100644 --- a/models.py +++ b/models.py @@ -35,6 +35,7 @@ class CreatePayLinkData(BaseModel): username: str | None = Query(None) zaps: bool | None = Query(False) disposable: bool | None = Query(True) + verify: bool | None = Query(False) class PayLink(BaseModel): @@ -69,6 +70,7 @@ class PayLink(BaseModel): fiat_base_multiplier: int | None = None disposable: bool + verify: bool # TODO deprecated, unused in the code, should be deleted from db. domain: str | None = None diff --git a/static/js/index.js b/static/js/index.js index 3e24281..7000bf1 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -96,6 +96,7 @@ window.app = Vue.createApp({ fixedAmount: true, data: { disposable: true, + verify: false, zaps: false } }, diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index a0669db..b45461c 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -249,6 +249,19 @@
/> +
+ LUD-21: verify base spec. +
+
+
+ +
+
LNURL
diff --git a/views_lnurl.py b/views_lnurl.py index adc88b4..f102307 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -2,6 +2,7 @@ from http import HTTPStatus from fastapi import APIRouter, HTTPException, Query, Request +from lnbits.core.crud import get_standalone_payment from lnbits.core.services import create_invoice from lnbits.utils.exchange_rates import get_fiat_rate_satoshis from lnurl import ( @@ -120,13 +121,21 @@ async def api_lnurl_callback( ) invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11)) + verify: CallbackUrl | None = None + + if link.verify: + verify_url = request.url_for( + "lnurlp.api_lnurl_verify", payment_hash=payment.payment_hash + ) + verify = parse_obj_as(CallbackUrl, str(verify_url)) + if link.success_url: url = parse_obj_as(CallbackUrl, str(link.success_url)) text = link.success_text or f"Link to {link.success_url}" desc = parse_obj_as(Max144Str, text) action = UrlAction(tag=LnurlPaySuccessActionTag.url, url=url, description=desc) return LnurlPayActionResponse( - pr=invoice, successAction=action, disposable=link.disposable + pr=invoice, successAction=action, disposable=link.disposable, verify=verify ) if link.success_text: @@ -135,9 +144,10 @@ async def api_lnurl_callback( pr=invoice, successAction=MessageAction(message=message), disposable=link.disposable, + verify=verify, ) - return LnurlPayActionResponse(pr=invoice, disposable=link.disposable) + return LnurlPayActionResponse(pr=invoice, disposable=link.disposable, verify=verify) @lnurlp_lnurl_router.get( @@ -206,3 +216,28 @@ async def lnaddress( if not address_data: return LnurlErrorResponse(reason="Lightning address not found.") return await api_lnurl_response(request, address_data.id) + + +# LUD-21. verify base spec. https://github.com/lnurl/luds/blob/luds/21.md +# TODO: update lnurl lib to use updated `LnurlSuccessResponse` instead of dict +@lnurlp_lnurl_router.get( + "/api/v1/lnurl/verify/{payment_hash}", + name="lnurlp.api_lnurl_verify", +) +async def api_lnurl_verify(payment_hash: str) -> LnurlErrorResponse | dict: + payment = await get_standalone_payment(payment_hash, incoming=True) + if not payment: + return LnurlErrorResponse(reason="Not found") + if payment.success: + return { + "status": "OK", + "settled": True, + "preimage": payment.preimage, + "pr": payment.bolt11, + } + else: + return { + "status": "OK", + "settled": False, + "pr": payment.bolt11, + } From eafb8ab5e309ec51af33edbf9f48a07affa68f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 15 Oct 2025 12:16:53 +0200 Subject: [PATCH 2/2] remove todo --- views_lnurl.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/views_lnurl.py b/views_lnurl.py index f102307..6d990f5 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -13,6 +13,7 @@ LnurlPayMetadata, LnurlPayResponse, LnurlPaySuccessActionTag, + LnurlPayVerifyResponse, Max144Str, MessageAction, MilliSatoshi, @@ -219,25 +220,20 @@ async def lnaddress( # LUD-21. verify base spec. https://github.com/lnurl/luds/blob/luds/21.md -# TODO: update lnurl lib to use updated `LnurlSuccessResponse` instead of dict @lnurlp_lnurl_router.get( "/api/v1/lnurl/verify/{payment_hash}", name="lnurlp.api_lnurl_verify", ) -async def api_lnurl_verify(payment_hash: str) -> LnurlErrorResponse | dict: +async def api_lnurl_verify( + payment_hash: str, +) -> LnurlErrorResponse | LnurlPayVerifyResponse: payment = await get_standalone_payment(payment_hash, incoming=True) if not payment: return LnurlErrorResponse(reason="Not found") + invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11)) if payment.success: - return { - "status": "OK", - "settled": True, - "preimage": payment.preimage, - "pr": payment.bolt11, - } + return LnurlPayVerifyResponse( + settled=True, preimage=payment.preimage, pr=invoice + ) else: - return { - "status": "OK", - "settled": False, - "pr": payment.bolt11, - } + return LnurlPayVerifyResponse(settled=False, pr=invoice)