From dbcf2cce5967ad492eff0c8ec8aadf89fee8a137 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 30 Jul 2026 11:51:12 +0300 Subject: [PATCH] refactor: clean public functions --- lnbits/core/crud/wallets.py | 4 +- lnbits/core/services/lightning_address.py | 247 ++++++++++------------ lnbits/core/views/lnurl_api.py | 6 +- 3 files changed, 119 insertions(+), 138 deletions(-) diff --git a/lnbits/core/crud/wallets.py b/lnbits/core/crud/wallets.py index 0a3b4eef1..a88259515 100644 --- a/lnbits/core/crud/wallets.py +++ b/lnbits/core/crud/wallets.py @@ -4,15 +4,13 @@ from uuid import uuid4 from lnbits.core.db import db from lnbits.core.models.wallets import BaseWallet, WalletsFilters, WalletType -from lnbits.db import Connection, Database, Filters, Page +from lnbits.db import Connection, Filters, Page from lnbits.helpers import generate_ln_address from lnbits.settings import settings from lnbits.utils.cache import cache from ..models import Wallet -_LEGACY_LNURLP_DB = Database("ext_lnurlp") - async def create_wallet( *, diff --git a/lnbits/core/services/lightning_address.py b/lnbits/core/services/lightning_address.py index d819d5681..421b33156 100644 --- a/lnbits/core/services/lightning_address.py +++ b/lnbits/core/services/lightning_address.py @@ -16,10 +16,15 @@ from pydantic import parse_obj_as from lnbits.core.crud.wallets import ( get_wallet, get_wallet_id_by_ln_address, - legacy_lnurlp_address_exists, + update_wallet, ) -from lnbits.core.db import db +from lnbits.core.models.payments import CreateInvoice from lnbits.core.models.wallets import Wallet +from lnbits.core.services.payments import ( + create_invoice, + create_wallet_invoice, + pay_invoice, +) from lnbits.db import Connection from lnbits.exceptions import PaymentError from lnbits.settings import settings @@ -29,100 +34,6 @@ COMMENT_ALLOWED = 799 LIGHTNING_ADDRESS_REGEX = re.compile(r"^[a-z0-9_.-]{1,210}$") -def normalize_lightning_address_local_part(local_part: str) -> str: - return local_part.strip().lower() - - -def _blacklist_words() -> set[str]: - return { - word.strip().lower() - for word in settings.lnbits_wallet_lightning_address_blacklist - if word.strip() - } - - -def _uses_blacklisted_word(local_part: str) -> bool: - words = _blacklist_words() - if not words: - return False - segments = [segment for segment in re.split(r"[._-]+", local_part) if segment] - return local_part in words or any(segment in words for segment in segments) - - -async def validate_lightning_address_local_part( - *, - local_part: str, - wallet: Wallet, - allow_blacklisted: bool = False, - conn: Connection | None = None, -) -> str: - local_part = normalize_lightning_address_local_part(local_part) - if not local_part: - raise ValueError("Lightning Address is required.") - if "+" in local_part: - raise ValueError("Lightning Address cannot include tags.") - if "@" in local_part: - raise ValueError("Enter only the Lightning Address name before @.") - if not LIGHTNING_ADDRESS_REGEX.match(local_part): - raise ValueError( - "Lightning Address can only contain lowercase letters, numbers, " - "dash, underscore, and dot." - ) - if not allow_blacklisted and _uses_blacklisted_word(local_part): - raise ValueError("Lightning Address contains a reserved word.") - existing_wallet_id = await get_wallet_id_by_ln_address(local_part) - if existing_wallet_id and existing_wallet_id != wallet.id: - raise ValueError("Lightning Address is already taken.") - if await legacy_lnurlp_address_exists(local_part): - raise ValueError("Lightning Address is already taken.") - return local_part - - -async def _charge_for_lightning_address(wallet: Wallet) -> None: - price_sats = settings.lnbits_wallet_lightning_address_price_sats - if not settings.lnbits_charge_wallet_lightning_addresses or price_sats <= 0: - return - if not settings.lnbits_service_fee_wallet: - raise ValueError("Lightning Address fee wallet is not configured.") - if settings.lnbits_service_fee_wallet == wallet.source_wallet_id: - raise ValueError("Lightning Address fee wallet cannot be the same wallet.") - - from lnbits.core.crud.wallets import get_wallet - from lnbits.core.models.payments import CreateInvoice - from lnbits.core.services.payments import create_wallet_invoice, pay_invoice - - fee_wallet = await get_wallet(settings.lnbits_service_fee_wallet) - if not fee_wallet: - raise ValueError("Lightning Address fee wallet is not configured.") - - invoice = await create_wallet_invoice( - settings.lnbits_service_fee_wallet, - CreateInvoice( - out=False, - amount=price_sats, - memo="Lightning Address fee", - internal=True, - extra={ - "tag": "wallet_lightning_address_fee", - "wallet": wallet.source_wallet_id, - }, - ), - ) - try: - await pay_invoice( - wallet_id=wallet.source_wallet_id, - payment_request=invoice.bolt11, - description="Lightning Address fee", - tag="wallet_lightning_address_fee", - extra={ - "tag": "wallet_lightning_address_fee", - "fee_wallet": settings.lnbits_service_fee_wallet, - }, - ) - except PaymentError as exc: - raise ValueError(exc.message) from exc - - async def set_wallet_lightning_address( *, wallet: Wallet, @@ -131,17 +42,13 @@ async def set_wallet_lightning_address( charge: bool = False, conn: Connection | None = None, ) -> Wallet: - # todo: recheck if not settings.ln_address_creation_allowed: raise ValueError("Wallet Lightning Addresses are disabled.") if not wallet.is_lightning_wallet or wallet.deleted: raise ValueError("Lightning Address can only be set for active wallets.") - local_part = await validate_lightning_address_local_part( - local_part=local_part, - wallet=wallet, - allow_blacklisted=allow_blacklisted, - conn=conn, + local_part = await _validate_local_part( + local_part, wallet.id, allow_blacklisted, conn=conn ) if wallet.lightning_address == local_part: return wallet @@ -150,30 +57,7 @@ async def set_wallet_lightning_address( await _charge_for_lightning_address(wallet) wallet.lightning_address = local_part - await (conn or db).update("wallets", wallet) - - return await get_wallet(wallet.id, conn=conn) or wallet - - -def lightning_address_for_request(request: Request, local_part: str) -> str: - return f"{local_part}@{request.url.netloc}" - - -def _split_tagged_local_part(local_part: str) -> tuple[str, str | None]: - username, separator, tag = local_part.partition("+") - if not separator or not tag: - return username.lower(), None - return username.lower(), tag - - -def _metadata(identifier: str, tag: str | None = None) -> list[list[str]]: - metadata = [ - ["text/plain", f"Payment to {identifier}"], - ["text/identifier", identifier], - ] - if tag: - metadata.append(["text/tag", tag]) - return metadata + return await update_wallet(wallet, conn=conn) async def wallet_lightning_address_response( @@ -192,7 +76,7 @@ async def wallet_lightning_address_response( "lnurl.api_wallet_lightning_address_callback", username=tagged_local_part, ) - identifier = lightning_address_for_request(request, tagged_local_part) + identifier = _lightning_address_for_request(request, tagged_local_part) return LnurlPayResponse( callback=parse_obj_as(CallbackUrl, str(callback)), minSendable=MilliSatoshi(1000), @@ -231,7 +115,7 @@ async def wallet_lightning_address_callback( tagged_local_part = local_part if tag: tagged_local_part = f"{tagged_local_part}+{tag}" - identifier = lightning_address_for_request(request, tagged_local_part) + identifier = _lightning_address_for_request(request, tagged_local_part) extra = { "tag": "wallet_lightning_address", "lnaddress": identifier, @@ -241,8 +125,6 @@ async def wallet_lightning_address_callback( if comment: extra["comment"] = comment - from lnbits.core.services.payments import create_invoice - metadata = LnurlPayMetadata(json.dumps(_metadata(identifier, tag))) payment = await create_invoice( wallet_id=wallet_id, @@ -253,3 +135,108 @@ async def wallet_lightning_address_callback( ) invoice = parse_obj_as(LightningInvoice, LightningInvoice(payment.bolt11)) return LnurlPayActionResponse(pr=invoice, disposable=False) + + +def _lightning_address_for_request(request: Request, local_part: str) -> str: + return f"{local_part}@{request.url.netloc}" + + +async def _validate_local_part( + local_part: str, + wallet_id: str, + allow_blacklisted: bool = False, + conn: Connection | None = None, +) -> str: + local_part = local_part.strip().lower() + if not local_part: + raise ValueError("Lightning Address is required.") + if "+" in local_part: + raise ValueError("Lightning Address cannot include tags.") + if "@" in local_part: + raise ValueError("Enter only the Lightning Address name before @.") + if not LIGHTNING_ADDRESS_REGEX.match(local_part): + raise ValueError( + "Lightning Address can only contain lowercase letters, numbers, " + "dash, underscore, and dot." + ) + if not allow_blacklisted and _uses_blacklisted_word(local_part): + raise ValueError("Lightning Address contains a reserved word.") + existing_wallet_id = await get_wallet_id_by_ln_address(local_part, conn=conn) + if existing_wallet_id and existing_wallet_id != wallet_id: + raise ValueError("Lightning Address is already taken.") + + return local_part + + +def _split_tagged_local_part(local_part: str) -> tuple[str, str | None]: + username, separator, tag = local_part.partition("+") + if not separator or not tag: + return username.lower(), None + return username.lower(), tag + + +def _metadata(identifier: str, tag: str | None = None) -> list[list[str]]: + metadata = [ + ["text/plain", f"Payment to {identifier}"], + ["text/identifier", identifier], + ] + if tag: + metadata.append(["text/tag", tag]) + return metadata + + +async def _charge_for_lightning_address(wallet: Wallet) -> None: + price_sats = settings.lnbits_wallet_lightning_address_price_sats + if not settings.lnbits_charge_wallet_lightning_addresses or price_sats <= 0: + return + if not settings.lnbits_service_fee_wallet: + raise ValueError("Lightning Address fee wallet is not configured.") + if settings.lnbits_service_fee_wallet == wallet.source_wallet_id: + raise ValueError("Lightning Address fee wallet cannot be the same wallet.") + + fee_wallet = await get_wallet(settings.lnbits_service_fee_wallet) + if not fee_wallet: + raise ValueError("Lightning Address fee wallet is not configured.") + + invoice = await create_wallet_invoice( + settings.lnbits_service_fee_wallet, + CreateInvoice( + out=False, + amount=price_sats, + memo="Lightning Address fee", + internal=True, + extra={ + "tag": "wallet_lightning_address_fee", + "wallet": wallet.source_wallet_id, + }, + ), + ) + try: + await pay_invoice( + wallet_id=wallet.source_wallet_id, + payment_request=invoice.bolt11, + description="Lightning Address fee", + tag="wallet_lightning_address_fee", + extra={ + "tag": "wallet_lightning_address_fee", + "fee_wallet": settings.lnbits_service_fee_wallet, + }, + ) + except PaymentError as exc: + raise ValueError(exc.message) from exc + + +def _blacklist_words() -> set[str]: + return { + word.strip().lower() + for word in settings.lnbits_wallet_lightning_address_blacklist + if word.strip() + } + + +def _uses_blacklisted_word(local_part: str) -> bool: + words = _blacklist_words() + if not words: + return False + segments = [segment for segment in re.split(r"[._-]+", local_part) if segment] + return local_part in words or any(segment in words for segment in segments) diff --git a/lnbits/core/views/lnurl_api.py b/lnbits/core/views/lnurl_api.py index 1bd4de763..23ca95f68 100644 --- a/lnbits/core/views/lnurl_api.py +++ b/lnbits/core/views/lnurl_api.py @@ -23,7 +23,6 @@ from lnurl.models import LnurlResponseModel from loguru import logger from pydantic import ValidationError -from lnbits.core.crud.wallets import get_wallet_id_by_ln_address from lnbits.core.models import Payment from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan from lnbits.decorators import ( @@ -63,10 +62,7 @@ async def api_wallet_lightning_address_response( reason="Lightning addresses are not supported on this instance." ) - if await get_wallet_id_by_ln_address(username): - return await wallet_lightning_address_response(username, request) - - return LnurlErrorResponse(reason="Lightning Address not found.") + return await wallet_lightning_address_response(username, request) @lnurl_router.get(