From a8fdb5192a0d46fd052a13d0d85cd03ceaa6994f Mon Sep 17 00:00:00 2001 From: Sat <792024+santyr@users.noreply.github.com> Date: Wed, 13 Aug 2025 10:51:57 -0600 Subject: [PATCH 1/5] fix(splitpayments): Correct import path for api_lnurlscan api_lnurlscan function is being imported from nbits.core.views.api, but it is located in lnbits.core.views.lnurl_api. This corrects the import path, resolving the error. --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index c51b90a..9a10c52 100644 --- a/tasks.py +++ b/tasks.py @@ -99,7 +99,7 @@ async def get_lnurl_invoice( payoraddress, wallet_id, amount_msat, memo ) -> Optional[str]: - from lnbits.core.views.api import api_lnurlscan + from lnbits.core.views.lnurl_api import api_lnurlscan data = await api_lnurlscan(payoraddress) rounded_amount = floor(amount_msat / 1000) * 1000 From 1a3a2464cb3a43e9f7624b81f8d9a510084d580c Mon Sep 17 00:00:00 2001 From: Sat <792024+santyr@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:00:25 -0600 Subject: [PATCH 2/5] fix(splitpayments): Use attribute access for LNURL response object `api_lnurlscan` function returns a `LnurlPayResponse` object, not a dictionary. The code was attempting to access its properties using dictionary subscription (`data['callback']`), which caused a `TypeError`. Updated the code to use attribute access (`data.callback`) to correctly handle the response object and resolve the runtime error. --- tasks.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tasks.py b/tasks.py index 9a10c52..e744020 100644 --- a/tasks.py +++ b/tasks.py @@ -101,13 +101,14 @@ async def get_lnurl_invoice( from lnbits.core.views.lnurl_api import api_lnurlscan + # The data returned here is a Pydantic model, not a dict data = await api_lnurlscan(payoraddress) rounded_amount = floor(amount_msat / 1000) * 1000 async with httpx.AsyncClient() as client: try: r = await client.get( - data["callback"], + data.callback, # Use attribute access instead of dict access params={"amount": rounded_amount, "comment": memo}, timeout=5, ) @@ -116,7 +117,7 @@ async def get_lnurl_invoice( r.raise_for_status() except (httpx.ConnectError, httpx.RequestError): logger.error( - f"splitting LNURL failed: Failed to connect to {data['callback']}." + f"splitting LNURL failed: Failed to connect to {data.callback}." # Use attribute access ) return None except Exception as exc: @@ -125,7 +126,7 @@ async def get_lnurl_invoice( params = json.loads(r.text) if params.get("status") == "ERROR": - logger.error(f"{data['callback']} said: '{params.get('reason', '')}'") + logger.error(f"{data.callback} said: '{params.get('reason', '')}'") # Use attribute access return None invoice = bolt11.decode(params["pr"]) @@ -139,10 +140,10 @@ async def get_lnurl_invoice( if invoice.amount_msat != rounded_amount: logger.error( f""" - {data['callback']} returned an invalid invoice. + {data.callback} returned an invalid invoice. Expected {amount_msat} msat, got {invoice.amount_msat}. """ ) return None - return params["pr"] + return params["pr"]``` From 8e8814667da7ecea4ec33925bb2dad1aeb4fcada Mon Sep 17 00:00:00 2001 From: Sat <792024+santyr@users.noreply.github.com> Date: Thu, 14 Aug 2025 08:32:07 -0600 Subject: [PATCH 3/5] fix(splitpayments): Use lnurl.handle directly for robust payments The extension was silently failing when processing LNURL payments because it was calling the `api_lnurlscan` web endpoint. Errors from the `lnurl` library were being caught and returned as `None`, halting the process without a specific error log. This commit changes the implementation to use the `lnurl.handle` function directly, bypassing the API layer. This provides more direct error handling, logs specific LNURL failures, and prevents the task from failing silently. --- tasks.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tasks.py b/tasks.py index e744020..c0bb955 100644 --- a/tasks.py +++ b/tasks.py @@ -5,10 +5,14 @@ import bolt11 import httpx +# New imports needed for direct lnurl handling +from lnurl import LnurlResponseException +from lnurl import handle as lnurl_handle from lnbits.core.crud import get_standalone_payment from lnbits.core.crud.wallets import get_wallet_for_key from lnbits.core.models import Payment from lnbits.core.services import create_invoice, fee_reserve, pay_invoice +from lnbits.settings import settings # New import for user_agent from lnbits.tasks import register_invoice_listener from loguru import logger @@ -99,16 +103,19 @@ async def get_lnurl_invoice( payoraddress, wallet_id, amount_msat, memo ) -> Optional[str]: - from lnbits.core.views.lnurl_api import api_lnurlscan + # MODIFICATION: Call the lnurl library directly instead of the API endpoint + try: + data = await lnurl_handle(payoraddress, user_agent=settings.user_agent, timeout=5) + except (LnurlResponseException, Exception) as exc: + logger.error(f"Failed to handle '{payoraddress}'. Error: {exc}") + return None - # The data returned here is a Pydantic model, not a dict - data = await api_lnurlscan(payoraddress) rounded_amount = floor(amount_msat / 1000) * 1000 async with httpx.AsyncClient() as client: try: r = await client.get( - data.callback, # Use attribute access instead of dict access + data.callback, params={"amount": rounded_amount, "comment": memo}, timeout=5, ) @@ -117,7 +124,7 @@ async def get_lnurl_invoice( r.raise_for_status() except (httpx.ConnectError, httpx.RequestError): logger.error( - f"splitting LNURL failed: Failed to connect to {data.callback}." # Use attribute access + f"splitting LNURL failed: Failed to connect to {data.callback}." ) return None except Exception as exc: @@ -126,7 +133,7 @@ async def get_lnurl_invoice( params = json.loads(r.text) if params.get("status") == "ERROR": - logger.error(f"{data.callback} said: '{params.get('reason', '')}'") # Use attribute access + logger.error(f"{data.callback} said: '{params.get('reason', '')}'") return None invoice = bolt11.decode(params["pr"]) @@ -146,4 +153,4 @@ async def get_lnurl_invoice( ) return None - return params["pr"]``` + return params["pr"] From 5a1ded8f2559d81c962ff1442c6def86cf2d1e9a Mon Sep 17 00:00:00 2001 From: Sat <792024+santyr@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:27:35 -0600 Subject: [PATCH 4/5] Update tasks.py Remove extra memo and payment hash from memo generation. --- tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks.py b/tasks.py index c0bb955..702a07f 100644 --- a/tasks.py +++ b/tasks.py @@ -52,7 +52,6 @@ async def on_invoice_paid(payment: Payment) -> None: memo = ( f"Split payment: {target.percent}% " f"for {target.alias or target.wallet}" - f";{payment.memo};{payment.payment_hash}" ) if "@" in target.wallet or "LNURL" in target.wallet: From e20a2140935b7b76aa349097d3fa9637ec5964ec Mon Sep 17 00:00:00 2001 From: Sat <792024+santyr@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:35:20 -0600 Subject: [PATCH 5/5] fix(splitpayments): Convert Lightning Address to lowercase before handling Valid Lightning Addresses containing uppercase characters (e.g., `Name@domain.com`) were being incorrectly rejected with an "Invalid Lightning address" error. This was caused by the underlying `lnurl-python` library performing a strict, case-sensitive validation, which violates the case-insensitive LUD-16 specification. This commit fixes the issue by converting the address to lowercase before it is passed to the `lnurl_handle` function. This ensures compliance with the spec and allows all valid Lightning Addresses to be processed correctly, regardless of case. --- tasks.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tasks.py b/tasks.py index 702a07f..9c01236 100644 --- a/tasks.py +++ b/tasks.py @@ -5,14 +5,13 @@ import bolt11 import httpx -# New imports needed for direct lnurl handling from lnurl import LnurlResponseException from lnurl import handle as lnurl_handle from lnbits.core.crud import get_standalone_payment from lnbits.core.crud.wallets import get_wallet_for_key from lnbits.core.models import Payment from lnbits.core.services import create_invoice, fee_reserve, pay_invoice -from lnbits.settings import settings # New import for user_agent +from lnbits.settings import settings from lnbits.tasks import register_invoice_listener from loguru import logger @@ -102,7 +101,9 @@ async def get_lnurl_invoice( payoraddress, wallet_id, amount_msat, memo ) -> Optional[str]: - # MODIFICATION: Call the lnurl library directly instead of the API endpoint + # FIX: Convert address to lowercase to support case-insensitive spec + payoraddress = payoraddress.lower() + try: data = await lnurl_handle(payoraddress, user_agent=settings.user_agent, timeout=5) except (LnurlResponseException, Exception) as exc: