diff --git a/README.md b/README.md index 9fcef84..72fe035 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,3 @@ - - - - LNbits - - - -[![License: MIT](https://img.shields.io/badge/License-MIT-success?logo=open-source-initiative&logoColor=white)](./LICENSE) -[![Built for LNbits](https://img.shields.io/badge/Built%20for-LNbits-4D4DFF?logo=lightning&logoColor=white)](https://github.com/lnbits/lnbits) - # LNURLp - [LNbits](https://github.com/lnbits/lnbits) extension For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Extensions) @@ -65,10 +55,3 @@ Now you can receive sats to your newly created LN address. You will find this in [![lnurl-details.jpg](https://i.postimg.cc/zDwq1V2X/lnurl-details.jpg)](https://postimg.cc/3WwsXJHP) - -## Powered by LNbits - -[LNbits](https://lnbits.com) is a free and open-source lightning accounts system. - -[![Visit LNbits Shop](https://img.shields.io/badge/Visit-LNbits%20Shop-7C3AED?logo=shopping-cart&logoColor=white&labelColor=5B21B6)](https://shop.lnbits.com/) -[![Try myLNbits SaaS](https://img.shields.io/badge/Try-myLNbits%20SaaS-2563EB?logo=lightning&logoColor=white&labelColor=1E40AF)](https://my.lnbits.com/login) diff --git a/__init__.py b/__init__.py index 782315b..27ca96e 100644 --- a/__init__.py +++ b/__init__.py @@ -42,42 +42,6 @@ def lnurlp_start(): task = create_permanent_unique_task("lnurlp", wait_for_paid_invoices) scheduled_tasks.append(task) - # Expose lnurlp's CRUD over the LNbits nostr transport so an HTTP- - # allergic client (e.g. lamassu-next ATM) can manage PayLinks over - # kind-21000 encrypted events. Also wires the link-owner resolver so - # `subscribe_payments({tag:"lnurlp", link_id:...})` can verify - # ownership of the underlying wallet. No-op if the core transport - # module isn't present in the LNbits build. - try: - from lnbits.core.services.nostr_transport.dispatcher import ( - AUTH_ACCOUNT, - AUTH_WALLET, - register_rpc, - ) - from lnbits.core.services.nostr_transport.subscriptions import ( - register_link_owner_resolver, - ) - except ImportError: - return - - from .transport_rpcs import ( - handle_lnurlp_create, - handle_lnurlp_delete, - handle_lnurlp_get, - handle_lnurlp_list, - handle_lnurlp_update, - resolve_lnurlp_owner, - ) - - register_rpc("lnurlp_create", handle_lnurlp_create, AUTH_WALLET) - register_rpc("lnurlp_get", handle_lnurlp_get, AUTH_WALLET) - register_rpc("lnurlp_list", handle_lnurlp_list, AUTH_ACCOUNT) - register_rpc("lnurlp_update", handle_lnurlp_update, AUTH_WALLET) - register_rpc("lnurlp_delete", handle_lnurlp_delete, AUTH_WALLET) - # lnurlp stamps `extra["link"] = link.id` on settlement - # (views_lnurl.py:86), which is the default extras-key, so no override. - register_link_owner_resolver("lnurlp", resolve_lnurlp_owner) - __all__ = [ "db", diff --git a/config.json b/config.json index 5742544..41a398f 100644 --- a/config.json +++ b/config.json @@ -1,10 +1,7 @@ { - "id": "paylink", - "version": "1.3.0", "name": "Pay Links", - "repo": "https://github.com/lnbits/lnurlp", - "short_description": "Make static reusable LNURL pay links or lightning addresses", - "description": "", + "version": "1.3.0", + "short_description": "Make reusable LNURL pay links", "tile": "/lnurlp/static/image/lnurl-pay.png", "min_lnbits_version": "1.4.0", "contributors": [ @@ -54,9 +51,5 @@ ], "description_md": "https://raw.githubusercontent.com/lnbits/lnurlp/main/description.md", "terms_and_conditions_md": "https://raw.githubusercontent.com/lnbits/lnurlp/main/toc.md", - "license": "MIT", - "paid_features": "", - "tags": ["Merchant", "Payments"], - "donate": "", - "hidden": false + "license": "MIT" } diff --git a/description.md b/description.md index a4cdd4f..3a81e0d 100644 --- a/description.md +++ b/description.md @@ -1,10 +1 @@ -Create static LNURL-pay links and Lightning addresses for receiving payments. - -Its functions include: - -- Generating reusable LNURL-pay QR codes -- Creating custom Lightning addresses -- Setting minimum and maximum payment amounts -- Adding payment descriptions and metadata - -A foundational tool for anyone who wants a simple, reusable way to receive Lightning payments with a static QR code or Lightning address. +Create a static LNURLp or LNaddress people can use to pay. diff --git a/static/index.js b/static/index.js index 497d2b6..d11f7d7 100644 --- a/static/index.js +++ b/static/index.js @@ -150,7 +150,7 @@ window.PageLnurlp = { username: link.username } const domain = link.domain || window.location.host - this.activeUrl = `https://${domain}/lnurlp/${link.id}` + this.activeUrl = `https://${domain}/lnurlp//${link.id}` this.qrCodeDialog.show = true }, openUpdateDialog(linkId) { diff --git a/tasks.py b/tasks.py index 5c1decd..c03e6ce 100644 --- a/tasks.py +++ b/tasks.py @@ -144,16 +144,6 @@ async def send_zap(payment: Payment): async with websockets.connect(relay_url, open_timeout=5) as websocket: logger.debug(f"Sending zap to {relay_url}") await websocket.send(event_message) - response = await asyncio.wait_for(websocket.recv(), timeout=5) - relay_response = json.loads(response) - if relay_response[0] != "OK" or not relay_response[2]: - logger.debug( - f"Relay did not acknowledge zap receipt: {relay_response}" - ) - return - logger.debug(f"Zap sent to {relay_url} successfully") - except asyncio.TimeoutError: - logger.debug(f"Relay did not acknowledge zap receipt: {relay_url}") except Exception as e: logger.warning(f"Failed to send zap to {relay_url}: {e}") diff --git a/transport_rpcs.py b/transport_rpcs.py deleted file mode 100644 index 87f3f59..0000000 --- a/transport_rpcs.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Nostr-transport RPC handlers for the lnurlp (LNURL-pay) extension. - -Exposes the same CRUD surface that `views_api.py` exposes via HTTP, but -encrypted over kind-21000 events through the LNbits nostr transport. -Mirrors the withdraw extension's `transport_rpcs.py`; both extensions -hang their handlers off the core dispatcher via their `*_start()` hook. - -Auth model (set by the registrations in `__init__.py:lnurlp_start`): -- *_create / *_get / *_update / *_delete → AUTH_WALLET. The transport - resolves the caller's pubkey to a wallet (admin access) before - invoking the handler, so we know `auth.wallet.id` and `auth.wallet.user`. -- *_list → AUTH_ACCOUNT. The caller may list links across all wallets - they own, optionally narrowed by `request.wallet_id`. - -Ownership: *_get / *_update / *_delete also verify the link's stored -`wallet` field matches the caller's wallet id — defense in depth, since -a malicious client could otherwise probe link metadata they don't own. - -`resolve_lnurlp_owner` is registered with the core subscription module -under tag `"lnurlp"` (default link_extra_key `"link"` — that's where -`views_lnurl.py:86` stamps the link id on settlement). That lets clients -call `subscribe_payments({tag:"lnurlp", link_id:...})` and stream real- -time pay events without polling, with ownership enforced server-side. -""" - -from __future__ import annotations - -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 .crud import ( - create_pay_link, - delete_pay_link, - get_pay_link, - get_pay_links, - update_pay_link, -) -from .models import CreatePayLinkData - - -async def handle_lnurlp_create( - auth: WalletTypeInfo, request: NostrRpcRequest -) -> dict: - body = request.body or {} - body["wallet"] = auth.wallet.id # always create under the calling wallet - data = CreatePayLinkData(**body) - link = await create_pay_link(data) - return _to_dict(link) - - -async def handle_lnurlp_get( - auth: WalletTypeInfo, request: NostrRpcRequest -) -> dict: - link_id = _require_id(request) - link = await _require_owned_link(link_id, auth.wallet.id) - return _to_dict(link) - - -async def handle_lnurlp_list( - auth: Account, request: NostrRpcRequest -) -> list[dict]: - """List PayLinks across all wallets owned by the calling account. - If `request.wallet_id` is set and is one of those wallets, narrow to - just that wallet.""" - wallets = await get_wallets(auth.id) - wallet_ids = [w.id for w in wallets] - if not wallet_ids: - return [] - if request.wallet_id and request.wallet_id in wallet_ids: - wallet_ids = [request.wallet_id] - links = await get_pay_links(wallet_ids) - return [_to_dict(link) for link in links] - - -async def handle_lnurlp_update( - auth: WalletTypeInfo, request: NostrRpcRequest -) -> dict: - link_id = _require_id(request) - link = await _require_owned_link(link_id, auth.wallet.id) - body = request.body or {} - # Only patchable fields. Identity / counter fields (id, wallet, - # served_meta, served_pr, created_at) are not client-mutable. - _MUTABLE = { - "description", - "min", - "max", - "comment_chars", - "currency", - "webhook_url", - "webhook_headers", - "webhook_body", - "success_text", - "success_url", - "fiat_base_multiplier", - "username", - "zaps", - "disposable", - "domain", - } - for k, v in body.items(): - if k in _MUTABLE: - setattr(link, k, v) - updated = await update_pay_link(link) - return _to_dict(updated) - - -async def handle_lnurlp_delete( - auth: WalletTypeInfo, request: NostrRpcRequest -) -> dict: - link_id = _require_id(request) - await _require_owned_link(link_id, auth.wallet.id) - await delete_pay_link(link_id) - return {"ok": True} - - -async def resolve_lnurlp_owner(link_id: str) -> str | None: - """For the core subscription module: link_id -> wallet_id (or None).""" - link = await get_pay_link(link_id) - return link.wallet if link else None - - -# --------------------------------------------------------------------------- -# helpers -# --------------------------------------------------------------------------- - - -def _require_id(request: NostrRpcRequest) -> str: - body = request.body or {} - link_id = body.get("id") - if not link_id: - raise ValueError("lnurlp: body.id is required") - return str(link_id) - - -async def _require_owned_link(link_id: str, wallet_id: str): - link = await get_pay_link(link_id) - if link is None: - raise ValueError(f"lnurlp: link not found: {link_id}") - if link.wallet != wallet_id: - raise PermissionError( - "lnurlp: link does not belong to caller's wallet" - ) - return link - - -def _to_dict(link) -> dict: - import json - return json.loads(link.json())