chore: update lnurl library (#87)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled

* chore: update lnurl library
This commit is contained in:
dni ⚡ 2025-07-17 16:57:29 +02:00 committed by GitHub
commit 0e0af4d656
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1229 additions and 486 deletions

View file

@ -1,9 +1,9 @@
{ {
"name": "Pay Links", "name": "Pay Links",
"version": "1.0.1", "version": "1.1.0",
"short_description": "Make reusable LNURL pay links", "short_description": "Make reusable LNURL pay links",
"tile": "/lnurlp/static/image/lnurl-pay.png", "tile": "/lnurlp/static/image/lnurl-pay.png",
"min_lnbits_version": "1.0.0", "min_lnbits_version": "1.3.0",
"contributors": [ "contributors": [
{ {
"name": "arcbtc", "name": "arcbtc",
@ -24,6 +24,11 @@
"name": "callebtc", "name": "callebtc",
"uri": "https://github.com/callebtc", "uri": "https://github.com/callebtc",
"role": "Contributor" "role": "Contributor"
},
{
"name": "dni",
"uri": "https://github.com/dni",
"role": "Contributor"
} }
], ],
"images": [ "images": [

1616
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,10 +3,11 @@ name = "lnbits-lnurlp"
version = "0.0.0" version = "0.0.0"
description = "LNbits, free and open-source Lightning wallet and accounts system." description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = ["Alan Bits <alan@lnbits.com>"] authors = ["Alan Bits <alan@lnbits.com>"]
package-mode = false
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.10 | ^3.9" python = "~3.12 | ~3.11"
lnbits = {version = "*", allow-prereleases = true} lnbits = {version = "1.3.0-rc1", allow-prereleases = true}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^24.3.0" black = "^24.3.0"

View file

@ -1,19 +1,20 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Optional, Union from typing import Optional
from fastapi import APIRouter, HTTPException, Query, Request from fastapi import APIRouter, HTTPException, Query, Request
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.helpers import normalize_path
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse from lnurl import (
from lnurl.models import MessageAction, UrlAction CallbackUrl,
from lnurl.types import (
ClearnetUrl,
DebugUrl,
LightningInvoice, LightningInvoice,
LnurlErrorResponse,
LnurlPayActionResponse,
LnurlPayResponse,
LnurlPaySuccessActionTag,
Max144Str, Max144Str,
MessageAction,
MilliSatoshi, MilliSatoshi,
OnionUrl, UrlAction,
) )
from pydantic import parse_obj_as from pydantic import parse_obj_as
@ -37,7 +38,7 @@ async def api_lnurl_callback(
link_id: str, link_id: str,
amount: int = Query(...), amount: int = Query(...),
webhook_data: str = Query(None), webhook_data: str = Query(None),
): ) -> LnurlErrorResponse | LnurlPayActionResponse:
link = await get_pay_link(link_id) link = await get_pay_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
@ -61,12 +62,12 @@ async def api_lnurl_callback(
if amount < minimum: if amount < minimum:
return LnurlErrorResponse( return LnurlErrorResponse(
reason=f"Amount {amount} is smaller than minimum {min}." reason=f"Amount {amount} is smaller than minimum {min}."
).dict() )
elif amount > maximum: elif amount > maximum:
return LnurlErrorResponse( return LnurlErrorResponse(
reason=f"Amount {amount} is greater than maximum {max}." reason=f"Amount {amount} is greater than maximum {max}."
).dict() )
comment = request.query_params.get("comment") comment = request.query_params.get("comment")
if len(comment or "") > link.comment_chars: if len(comment or "") > link.comment_chars:
@ -75,7 +76,7 @@ async def api_lnurl_callback(
f"Got a comment with {len(comment or '')} characters, " f"Got a comment with {len(comment or '')} characters, "
f"but can only accept {link.comment_chars}" f"but can only accept {link.comment_chars}"
) )
).dict() )
# for lnaddress, we have to set this otherwise # for lnaddress, we have to set this otherwise
# the metadata won't have the identifier # the metadata won't have the identifier
@ -111,28 +112,29 @@ async def api_lnurl_callback(
unhashed_description=unhashed_description, unhashed_description=unhashed_description,
extra=extra, extra=extra,
) )
action: Optional[Union[MessageAction, UrlAction]] = None
if link.success_url:
url = parse_obj_as(
Union[DebugUrl, OnionUrl, ClearnetUrl], # type: ignore
str(link.success_url),
)
desc = parse_obj_as(Max144Str, link.success_text)
action = UrlAction(url=url, description=desc)
elif link.success_text:
message = parse_obj_as(Max144Str, link.success_text)
action = MessageAction(message=message)
invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11)) invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11))
resp = LnurlPayActionResponse(pr=invoice, successAction=action, routes=[])
return resp.dict() 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)
if link.success_text:
message = parse_obj_as(Max144Str, link.success_text)
return LnurlPayActionResponse(
pr=invoice, successAction=MessageAction(message=message)
)
return LnurlPayActionResponse(pr=invoice)
@lnurlp_lnurl_router.get( @lnurlp_lnurl_router.get(
"/api/v1/lnurl/{link_id}", # Backwards compatibility for old LNURLs / QR codes "/api/v1/lnurl/{link_id}", # Backwards compatibility for old LNURLs / QR codes
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
name="lnurlp.api_lnurl_response.deprecated", name="lnurlp.api_lnurl_response.deprecated",
deprecated=True,
) )
@lnurlp_lnurl_router.get( @lnurlp_lnurl_router.get(
"/{link_id}", "/{link_id}",
@ -141,7 +143,7 @@ async def api_lnurl_callback(
) )
async def api_lnurl_response( async def api_lnurl_response(
request: Request, link_id: str, webhook_data: Optional[str] = Query(None) request: Request, link_id: str, webhook_data: Optional[str] = Query(None)
): ) -> LnurlPayResponse:
link = await get_pay_link(link_id) link = await get_pay_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
@ -152,37 +154,38 @@ async def api_lnurl_response(
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
url = request.url_for("lnurlp.api_lnurl_callback", link_id=link.id) url = request.url_for("lnurlp.api_lnurl_callback", link_id=link.id)
url = url.replace(path=normalize_path(url.path))
if webhook_data: if webhook_data:
url = url.include_query_params(webhook_data=webhook_data) url = url.include_query_params(webhook_data=webhook_data)
link.domain = request.url.netloc link.domain = request.url.netloc
callback_url = parse_obj_as( callback_url = parse_obj_as(CallbackUrl, str(url))
Union[DebugUrl, OnionUrl, ClearnetUrl], # type: ignore
str(url),
)
resp = LnurlPayResponse( res = LnurlPayResponse(
callback=callback_url, callback=callback_url,
minSendable=MilliSatoshi(round(link.min * rate) * 1000), minSendable=MilliSatoshi(round(link.min * rate) * 1000),
maxSendable=MilliSatoshi(round(link.max * rate) * 1000), maxSendable=MilliSatoshi(round(link.max * rate) * 1000),
metadata=link.lnurlpay_metadata, metadata=link.lnurlpay_metadata,
# todo library bug should not be in issue to onot specify
payerData=None,
commentAllowed=None,
allowsNostr=None,
nostrPubkey=None,
) )
params = resp.dict()
if link.comment_chars > 0: if link.comment_chars > 0:
params["commentAllowed"] = link.comment_chars res.comment_allowed = link.comment_chars
if link.zaps: if link.zaps:
settings = await get_or_create_lnurlp_settings() settings = await get_or_create_lnurlp_settings()
params["allowsNostr"] = True res.allows_nostr = True
params["nostrPubkey"] = settings.public_key res.nostr_pubkey = settings.public_key
return params
return res
# redirected from /.well-known/lnurlp # redirected from /.well-known/lnurlp
@lnurlp_lnurl_router.get("/api/v1/well-known/{username}") @lnurlp_lnurl_router.get("/api/v1/well-known/{username}")
async def lnaddress(username: str, request: Request): async def lnaddress(username: str, request: Request) -> LnurlPayResponse:
address_data = await get_address_data(username) address_data = await get_address_data(username)
assert address_data, "User not found" assert address_data, "User not found"
return await api_lnurl_response(request, address_data.id, webhook_data=None) return await api_lnurl_response(request, address_data.id, webhook_data=None)