From bea8db1595945ba2e811c8a17675954c4e3963c9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:05:52 +0100 Subject: [PATCH] lnaddress works --- lnurl.py | 129 +++++++++++++++++++++++--------------------------- migrations.py | 7 +++ models.py | 13 +++-- views_api.py | 10 ++-- 4 files changed, 81 insertions(+), 78 deletions(-) diff --git a/lnurl.py b/lnurl.py index 1ce2b22..7f31188 100644 --- a/lnurl.py +++ b/lnurl.py @@ -16,7 +16,9 @@ from urllib.parse import urlparse # for .well-known/lnurlp async def lnurl_response(username: str, domain: str, request: Request): address_data = await get_address_data(username) - + # for lnaddress + domain = urlparse(str(request.url)).netloc + link.domain = domain if not address_data: return {"status": "ERROR", "reason": "Address not found."} @@ -25,7 +27,7 @@ async def lnurl_response(username: str, domain: str, request: Request): "callback": request.url_for( "lnurlp.api_lnurl_callback", link_id=address_data.id ), - "metadata": await address_data.lnurlpay_metadata(domain=domain), + "metadata": await address_data.lnurlpay_metadata, "minSendable": int(address_data.min * 1000), "maxSendable": int(address_data.max * 1000), } @@ -34,75 +36,15 @@ async def lnurl_response(username: str, domain: str, request: Request): return resp -# for lnaddress callback @lnurlp_ext.get( - "/api/v1/lnurl/cb/{link_id}", + "/api/v1/lnurl/cb/lnaddr/{link_id}", status_code=HTTPStatus.OK, - name="lnurlp.api_lnurl_callback", + name="lnurlp.api_lnurl_lnaddr_callback", ) -async def api_lnurl_callback(request: Request, link_id, amount: int = Query(...)): - address = await get_pay_link(link_id) - if not address: - return LnurlErrorResponse(reason=f'{"Address not found"}').dict() - - domain = urlparse(str(request.url)).netloc - assert domain - - unhashed_description = await address.lnurlpay_metadata(domain=domain) - unhashed_description = unhashed_description.encode() - payment_hash, payment_request = await create_invoice( - wallet_id=address.wallet, - amount=int(amount / 1000), - memo=address.description, - unhashed_description=unhashed_description, - extra={ - "tag": "lnurlp", - "link": address.id, - "extra": {"tag": f"Payment to {address.username}@{domain}"}, - }, - ) - - success_action = address.success_action(payment_hash) - if success_action: - resp = LnurlPayActionResponse( - pr=payment_request, success_action=success_action, routes=[] - ) - else: - resp = LnurlPayActionResponse(pr=payment_request, routes=[]) - - return resp.dict() - -@lnurlp_ext.get( - "/api/v1/lnurl/{link_id}", # Backwards compatibility for old LNURLs / QR codes (with long URL) - status_code=HTTPStatus.OK, - name="lnurlp.api_lnurl_response.deprecated", -) -@lnurlp_ext.get( - "/{link_id}", - status_code=HTTPStatus.OK, - name="lnurlp.api_lnurl_response", -) -async def api_lnurl_response(request: Request, link_id): - link = await increment_pay_link(link_id, served_meta=1) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." - ) - - rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 - - resp = LnurlPayResponse( - callback=request.url_for("lnurlp.api_lnurl_callback", link_id=link.id), - min_sendable=round(link.min * rate) * 1000, - max_sendable=round(link.max * rate) * 1000, - metadata=link.lnurlpay_metadata, - ) - params = resp.dict() - - if link.comment_chars > 0: - params["commentAllowed"] = link.comment_chars - - return params +async def api_lnurl_lnaddr_callback( + request: Request, link_id, amount: int = Query(...) +): + return await api_lnurl_callback(request, link_id, amount, lnaddress=True) @lnurlp_ext.get( @@ -110,7 +52,9 @@ async def api_lnurl_response(request: Request, link_id): status_code=HTTPStatus.OK, name="lnurlp.api_lnurl_callback", ) -async def api_lnurl_callback(request: Request, link_id): +async def api_lnurl_callback( + request: Request, link_id, amount: int = Query(...), lnaddress=False +): link = await increment_pay_link(link_id, served_pr=1) if not link: raise HTTPException( @@ -126,7 +70,7 @@ async def api_lnurl_callback(request: Request, link_id): min = link.min * 1000 max = link.max * 1000 - amount_received = int(request.query_params.get("amount") or 0) + amount_received = amount if amount_received < min: return LnurlErrorResponse( reason=f"Amount {amount_received} is smaller than minimum {min}." @@ -143,6 +87,10 @@ async def api_lnurl_callback(request: Request, link_id): reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}" ).dict() + if lnaddress: + domain = urlparse(str(request.url)).netloc + link.domain = domain + payment_hash, payment_request = await create_invoice( wallet_id=link.wallet, amount=int(amount_received / 1000), @@ -165,3 +113,44 @@ async def api_lnurl_callback(request: Request, link_id): resp = LnurlPayActionResponse(pr=payment_request, routes=[]) return resp.dict() + + +@lnurlp_ext.get( + "/api/v1/lnurl/{link_id}", # Backwards compatibility for old LNURLs / QR codes (with long URL) + status_code=HTTPStatus.OK, + name="lnurlp.api_lnurl_response.deprecated", +) +@lnurlp_ext.get( + "/{link_id}", + status_code=HTTPStatus.OK, + name="lnurlp.api_lnurl_response", +) +async def api_lnurl_response(request: Request, link_id, lnaddress=False): + link = await increment_pay_link(link_id, served_meta=1) + if not link: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." + ) + + rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 + + if lnaddress: + # for lnaddress + domain = urlparse(str(request.url)).netloc + link.domain = domain + callback = request.url_for("lnurlp.api_lnurl_lnaddr_callback", link_id=link.id) + else: + callback = request.url_for("lnurlp.api_lnurl_callback", link_id=link.id) + + resp = LnurlPayResponse( + callback=callback, + min_sendable=round(link.min * rate) * 1000, + max_sendable=round(link.max * rate) * 1000, + metadata=link.lnurlpay_metadata, + ) + params = resp.dict() + + if link.comment_chars > 0: + params["commentAllowed"] = link.comment_chars + + return params diff --git a/migrations.py b/migrations.py index d3ec18a..cd2db9a 100644 --- a/migrations.py +++ b/migrations.py @@ -150,3 +150,10 @@ async def m006_redux(db): ) await db.execute("DROP TABLE lnurlp.pay_links_old") + + +async def m007_add_lnaddress_username(db): + """ + Add headers and body to webhooks + """ + await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN username TEXT;") diff --git a/models.py b/models.py index a8156b7..9611d41 100644 --- a/models.py +++ b/models.py @@ -34,6 +34,7 @@ class PayLink(BaseModel): served_meta: int served_pr: int username: Optional[str] + domain: Optional[str] webhook_url: Optional[str] webhook_headers: Optional[str] webhook_body: Optional[str] @@ -72,9 +73,13 @@ class PayLink(BaseModel): else: return None - async def lnurlpay_metadata(self, domain) -> LnurlPayMetadata: - text = f"Payment to {self.username}" - identifier = f"{self.username}@{domain}" - metadata = [["text/plain", text], ["text/identifier", identifier]] + @property + def lnurlpay_metadata(self) -> LnurlPayMetadata: + if self.domain and self.username: + text = f"Payment to {self.username}" + identifier = f"{self.username}@{self.domain}" + metadata = [["text/plain", text], ["text/identifier", identifier]] + else: + metadata = [["text/plain", self.description]] return LnurlPayMetadata(json.dumps(metadata)) diff --git a/views_api.py b/views_api.py index 9d7ffd2..97accd2 100644 --- a/views_api.py +++ b/views_api.py @@ -18,15 +18,17 @@ from .crud import ( get_pay_link, get_pay_links, update_pay_link, + get_address_data, ) from .models import CreatePayLinkData -from .lnurl import lnurl_response +from .lnurl import api_lnurl_response + @lnurlp_ext.get("/api/v1/well-known/{username}") async def lnaddress(username: str, request: Request): - print("calling /api/v1/well-known/" + username) - domain = urlparse(str(request.url)).netloc - return await lnurl_response(username, domain, request) + address_data = await get_address_data(username) + assert address_data, "User not found" + return await api_lnurl_response(request, address_data.id, lnaddress=True) @lnurlp_ext.get("/api/v1/currencies")