fix: populate lnurl/lnurl_url in nostr-transport handlers (#1)
Some checks failed
lint.yml / fix: populate lnurl/lnurl_url in nostr-transport handlers (#1) (push) Failing after 0s

The HTTP views populate `link.lnurl` and `link.lnurl_url` from
`request.url_for(...)`; the nostr-transport RPC handlers had no
`Request` and so left both fields as `None`. Consumers (ATMs over
nostr) were forced to provision a separate `LNBITS_HTTP_URL` env var
and compose the LNURL callback themselves.

Add `helpers.create_lnurl_from_baseurl(link)` that mirrors
`create_lnurl` but composes the callback URL from
`settings.lnbits_baseurl` instead, and thread it through the
create/get/update/list RPC handlers via a `_populate_lnurl` shim
so the response shape matches the HTTP path. Encoding errors are
swallowed (fields stay `None`) so a misconfigured baseurl falls
back to current behavior rather than failing the RPC.

Closes #1.
This commit is contained in:
Padreug 2026-06-01 20:01:09 +02:00
commit e9d911e593
2 changed files with 54 additions and 13 deletions

View file

@ -20,12 +20,11 @@ link_id:...})` enforce ownership without core importing this module.
from __future__ import annotations
from shortuuid import uuid
from lnbits.core.crud.wallets import get_wallets
from lnbits.core.models import Account
from lnbits.core.models.wallets import WalletTypeInfo
from lnbits.core.services.nostr_transport.models import NostrRpcRequest
from shortuuid import uuid
from .crud import (
create_withdraw_link,
@ -34,7 +33,8 @@ from .crud import (
get_withdraw_links,
update_withdraw_link,
)
from .models import CreateWithdrawData
from .helpers import create_lnurl_from_baseurl
from .models import CreateWithdrawData, WithdrawLink
async def handle_lnurlw_create_link(
@ -43,7 +43,7 @@ async def handle_lnurlw_create_link(
body = request.body or {}
data = CreateWithdrawData(**body)
link = await create_withdraw_link(data, auth.wallet.id)
return _to_dict(link)
return _to_dict(_populate_lnurl(link))
async def handle_lnurlw_get_link(
@ -51,7 +51,7 @@ async def handle_lnurlw_get_link(
) -> dict:
link_id = _require_id(request)
link = await _require_owned_link(link_id, auth.wallet.id)
return _to_dict(link)
return _to_dict(_populate_lnurl(link))
async def handle_lnurlw_update_link(
@ -77,7 +77,7 @@ async def handle_lnurlw_update_link(
if k in _MUTABLE:
setattr(link, k, v)
updated = await update_withdraw_link(link)
return _to_dict(updated)
return _to_dict(_populate_lnurl(updated))
async def handle_lnurlw_delete_link(
@ -89,9 +89,7 @@ async def handle_lnurlw_delete_link(
return {"ok": True}
async def handle_lnurlw_list_links(
auth: Account, request: NostrRpcRequest
) -> dict:
async def handle_lnurlw_list_links(auth: Account, request: NostrRpcRequest) -> dict:
"""List withdraw links across all wallets owned by the calling account.
Useful for ATMs to re-discover their links after a reconnect.
@ -114,7 +112,7 @@ async def handle_lnurlw_list_links(
page = await get_withdraw_links(wallet_ids, limit, offset)
return {
"data": [_to_dict(link) for link in page.data],
"data": [_to_dict(_populate_lnurl(link)) for link in page.data],
"total": page.total,
}
@ -199,12 +197,29 @@ async def _require_owned_link(link_id: str, wallet_id: str):
if link is None:
raise ValueError(f"withdraw: link not found: {link_id}")
if link.wallet != wallet_id:
raise PermissionError(
"withdraw: link does not belong to caller's wallet"
)
raise PermissionError("withdraw: link does not belong to caller's wallet")
return link
def _populate_lnurl(link: WithdrawLink) -> WithdrawLink:
"""
Compose `lnurl` / `lnurl_url` from `settings.lnbits_baseurl` so
nostr-transport responses match the HTTP `views_api` shape, where
these fields are populated from `request.url_for(...)`. Without
this, consumers (ATMs, etc.) would have to re-derive the callback
URL themselves from a separately-provisioned LNbits HTTPS URL
duplicating state LNbits already knows. See aiolabs/withdraw#1.
"""
try:
encoded = create_lnurl_from_baseurl(link)
link.lnurl = str(encoded.bech32)
link.lnurl_url = str(encoded.url)
except ValueError:
pass
return link
def _to_dict(link) -> dict:
import json
return json.loads(link.json())