From 3644e0e2547fa4a5895b57784e033972a899e8c4 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 14:43:26 -0800 Subject: [PATCH 01/37] add to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0832bfb..515cf90 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,5 @@ LNURL is a range of lightning-network standards that allow us to use lightning-n ![LNURLp](https://i.imgur.com/C8s1P0Q.jpg) - you can now open your LNURLp and copy the LNURL, get the shareable link or print it\ ![view lnurlp](https://i.imgur.com/4n41S7T.jpg) + +3. Optional - add Lightning Address \ No newline at end of file From 577a3932f5bac8314c7da9136e81e16fcb1c7908 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 15:51:42 -0800 Subject: [PATCH 02/37] initial crud, index form --- README.md | 4 ++- crud.py | 62 +++++++++++++++++++++++++++++++++++-- static/js/index.js | 6 +++- templates/lnurlp/index.html | 16 ++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 515cf90..ec603ec 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,6 @@ LNURL is a range of lightning-network standards that allow us to use lightning-n - you can now open your LNURLp and copy the LNURL, get the shareable link or print it\ ![view lnurlp](https://i.imgur.com/4n41S7T.jpg) -3. Optional - add Lightning Address \ No newline at end of file +3. Optional - add Lightning Address + - attach a username to your lnurlp to create a lightning address + - the LN address format will be username@lnbits-domain-name diff --git a/crud.py b/crud.py index 4acb4a4..2362601 100644 --- a/crud.py +++ b/crud.py @@ -1,10 +1,12 @@ +import re from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash -from . import db +from . import db, maindb from .models import CreatePayLinkData, PayLink +from loguru import logger async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: link_id = urlsafe_short_hash()[:6] @@ -26,9 +28,11 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: success_url, comment_chars, currency, - fiat_base_multiplier + fiat_base_multiplier, + username + ) - VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( link_id, @@ -44,6 +48,7 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: data.comment_chars, data.currency, data.fiat_base_multiplier, + data.username, ), ) assert result @@ -53,6 +58,57 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: return link + +async def check_lnaddress_update(username: str, id: str) -> bool: + # check no duplicates for lnaddress when updating an username + row = await db.fetchall( + "SELECT username FROM lnurlp.pay_links WHERE username = ? AND id = ?", + (username, id), + ) + logger.info("number of rows from username search") + logger.info(len(row)) + if len(row) > 1: + assert False, "Lightning Address Already exists. Try a different One?" + return + else: + return True + + +async def check_lnaddress_exists(username: str) -> bool: + # check if lnaddress username exists in the database when creating a new entry + row = await db.fetchall( + "SELECT username FROM lnurlp.pay_links WHERE username = ?", (username,) + ) + logger.info("number of rows from lnaddress search") + if row: + assert False, "Lighting Address Already exists. Try a different One?" + return + else: + return True + + +async def check_lnaddress_format(username: str) -> bool: + # check username complies with lnaddress specification + if not re.match("^[a-z0-9-_.]{3,15}$", username): + assert False, "Only letters a-z0-9-_. allowed, min 3 and max 15 characters!" + return + return True + +async def get_wallet_key(wallet_id: str) -> str: + row = await maindb.fetchone("SELECT inkey FROM wallets WHERE id = ?", (wallet_id,)) + if row is not None: + return row[0] + else: + assert False, "Cannot locate wallet invoice key" + return + +async def get_address_data(username: str) -> Optional[PayLink]: + row = await db.fetchone( + "SELECT * FROM lnurl.pay_links WHERE username = ?", (username,) + ) + return PayLink.from_row(row) if row else None + + async def get_pay_link(link_id: str) -> Optional[PayLink]: row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,)) return PayLink.from_row(row) if row else None diff --git a/static/js/index.js b/static/js/index.js index c1372be..0075f28 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -9,6 +9,8 @@ var locationPath = [ window.location.pathname ].join('') +var domain = window.location.hostname + var mapPayLink = obj => { obj._data = _.clone(obj) obj.date = Quasar.utils.date.formatDate( @@ -26,6 +28,7 @@ new Vue({ mixins: [windowMixin], data() { return { + domain: domain, currencies: [], fiatRates: {}, checker: null, @@ -137,7 +140,8 @@ new Vue({ 'success_text', 'success_url', 'comment_chars', - 'currency' + 'currency', + 'username' ), (value, key) => (key === 'webhook_url' || diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index 3fbd344..f1ffacc 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -29,6 +29,7 @@ Description + Lightning Address Amount Currency @@ -148,6 +149,21 @@ label="Wallet *" > +
+
+ +
+
+ @{% raw %} {{domain}} {% endraw %} +
+ +
Date: Thu, 2 Mar 2023 16:03:57 -0800 Subject: [PATCH 03/37] remove circular, fix html --- crud.py | 16 ++++++++-------- templates/lnurlp/index.html | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crud.py b/crud.py index 2362601..31495ac 100644 --- a/crud.py +++ b/crud.py @@ -3,7 +3,7 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash -from . import db, maindb +from . import db #, maindb from .models import CreatePayLinkData, PayLink from loguru import logger @@ -94,13 +94,13 @@ async def check_lnaddress_format(username: str) -> bool: return return True -async def get_wallet_key(wallet_id: str) -> str: - row = await maindb.fetchone("SELECT inkey FROM wallets WHERE id = ?", (wallet_id,)) - if row is not None: - return row[0] - else: - assert False, "Cannot locate wallet invoice key" - return +# async def get_wallet_key(wallet_id: str) -> str: +# row = await maindb.fetchone("SELECT inkey FROM wallets WHERE id = ?", (wallet_id,)) +# if row is not None: +# return row[0] +# else: +# assert False, "Cannot locate wallet invoice key" +# return async def get_address_data(username: str) -> Optional[PayLink]: row = await db.fetchone( diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index f1ffacc..c902b23 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -29,9 +29,9 @@ Description - Lightning Address Amount Currency + Lightning Address @@ -149,6 +149,14 @@ label="Wallet *" > + +
- -
Date: Thu, 2 Mar 2023 16:45:41 -0800 Subject: [PATCH 04/37] update migrations, models, crud --- crud.py | 6 ------ migrations.py | 12 ++++++++---- models.py | 9 +++++++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/crud.py b/crud.py index 31495ac..8667a09 100644 --- a/crud.py +++ b/crud.py @@ -95,12 +95,6 @@ async def check_lnaddress_format(username: str) -> bool: return True # async def get_wallet_key(wallet_id: str) -> str: -# row = await maindb.fetchone("SELECT inkey FROM wallets WHERE id = ?", (wallet_id,)) -# if row is not None: -# return row[0] -# else: -# assert False, "Cannot locate wallet invoice key" -# return async def get_address_data(username: str) -> Optional[PayLink]: row = await db.fetchone( diff --git a/migrations.py b/migrations.py index 1ec85eb..d3ec18a 100644 --- a/migrations.py +++ b/migrations.py @@ -10,7 +10,8 @@ async def m001_initial(db): description TEXT NOT NULL, amount {db.big_int} NOT NULL, served_meta INTEGER NOT NULL, - served_pr INTEGER NOT NULL + served_pr INTEGER NOT NULL, + username TEXT ); """ ) @@ -97,7 +98,8 @@ async def m006_redux(db): success_url TEXT, comment_chars INTEGER DEFAULT 0, webhook_headers TEXT, - webhook_body TEXT + webhook_body TEXT, + username TEXT ); """ ) @@ -122,9 +124,10 @@ async def m006_redux(db): max, fiat_base_multiplier, webhook_headers, - webhook_body + webhook_body, + username ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( row[0], @@ -142,6 +145,7 @@ async def m006_redux(db): row[12], row[13], row[14], + row[15], ), ) diff --git a/models.py b/models.py index de66d40..f8c6d98 100644 --- a/models.py +++ b/models.py @@ -23,6 +23,7 @@ class CreatePayLinkData(BaseModel): success_text: str = Query(None) success_url: str = Query(None) fiat_base_multiplier: int = Query(100, ge=1) + username: str = Query(None) class PayLink(BaseModel): @@ -41,6 +42,7 @@ class PayLink(BaseModel): comment_chars: int max: float fiat_base_multiplier: int + username: str @classmethod def from_row(cls, row: Row) -> "PayLink": @@ -73,3 +75,10 @@ class PayLink(BaseModel): return {"tag": "message", "message": self.success_text} else: return None + + async def lnurlpay_metadata(self, domain) -> LnurlPayMetadata: + text = f"Payment to {self.lnaddress}" + identifier = f"{self.lnaddress}@{domain}" + metadata = [["text/plain", text], ["text/identifier", identifier]] + + return LnurlPayMetadata(json.dumps(metadata)) From a44f70dcb6d7789f3fd3931cf749bd0eae96c93d Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 17:03:00 -0800 Subject: [PATCH 05/37] add methods to check dup address and format --- crud.py | 81 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/crud.py b/crud.py index 8667a09..aeefe2a 100644 --- a/crud.py +++ b/crud.py @@ -6,9 +6,44 @@ from lnbits.helpers import urlsafe_short_hash from . import db #, maindb from .models import CreatePayLinkData, PayLink -from loguru import logger +# from loguru import logger + +async def check_lnaddress_update(username: str, id: str) -> bool: + # check no duplicates for lnaddress when updating an username + row = await db.fetchall( + "SELECT username FROM lnurlp.pay_links WHERE username = ? AND id = ?", + (username, id), + ) + if len(row) > 1: + assert False, "Lightning Address Already exists. Try a different One?" + return + else: + return True + + +async def check_lnaddress_exists(username: str) -> bool: + # check if lnaddress username exists in the database when creating a new entry + row = await db.fetchall( + "SELECT username FROM lnurlp.pay_links WHERE username = ?", (username,) + ) + if row: + assert False, "Lighting Address Already exists. Try a different One?" + return + else: + return True + + +async def check_lnaddress_format(username: str) -> bool: + # check username complies with lnaddress specification + if not re.match("^[a-z0-9-_.]{3,15}$", username): + assert False, "Only letters a-z0-9-_. allowed, min 3 and max 15 characters!" + return + return True + async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: + await check_lnaddress_format(data.username) + await check_lnaddress_exists(data.username) link_id = urlsafe_short_hash()[:6] result = await db.execute( @@ -58,44 +93,6 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: return link - -async def check_lnaddress_update(username: str, id: str) -> bool: - # check no duplicates for lnaddress when updating an username - row = await db.fetchall( - "SELECT username FROM lnurlp.pay_links WHERE username = ? AND id = ?", - (username, id), - ) - logger.info("number of rows from username search") - logger.info(len(row)) - if len(row) > 1: - assert False, "Lightning Address Already exists. Try a different One?" - return - else: - return True - - -async def check_lnaddress_exists(username: str) -> bool: - # check if lnaddress username exists in the database when creating a new entry - row = await db.fetchall( - "SELECT username FROM lnurlp.pay_links WHERE username = ?", (username,) - ) - logger.info("number of rows from lnaddress search") - if row: - assert False, "Lighting Address Already exists. Try a different One?" - return - else: - return True - - -async def check_lnaddress_format(username: str) -> bool: - # check username complies with lnaddress specification - if not re.match("^[a-z0-9-_.]{3,15}$", username): - assert False, "Only letters a-z0-9-_. allowed, min 3 and max 15 characters!" - return - return True - -# async def get_wallet_key(wallet_id: str) -> str: - async def get_address_data(username: str) -> Optional[PayLink]: row = await db.fetchone( "SELECT * FROM lnurl.pay_links WHERE username = ?", (username,) @@ -124,6 +121,12 @@ async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]: async def update_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: + for field in kwargs.items(): + if field[0] == "lnaddress": + value = field[1] + await check_lnaddress_format(value) + await check_lnaddress_update(value, str(link_id)) + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) From ec091817c78091b6f7c102bb35bfd3528a656f2e Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 17:10:11 -0800 Subject: [PATCH 06/37] remove metadata method --- models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/models.py b/models.py index f8c6d98..20c23a1 100644 --- a/models.py +++ b/models.py @@ -75,10 +75,3 @@ class PayLink(BaseModel): return {"tag": "message", "message": self.success_text} else: return None - - async def lnurlpay_metadata(self, domain) -> LnurlPayMetadata: - text = f"Payment to {self.lnaddress}" - identifier = f"{self.lnaddress}@{domain}" - metadata = [["text/plain", text], ["text/identifier", identifier]] - - return LnurlPayMetadata(json.dumps(metadata)) From 4a2d41964d14fa239fe66718b0b2214174c8346a Mon Sep 17 00:00:00 2001 From: Bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 17:47:29 -0800 Subject: [PATCH 07/37] Update manifest.json try to see if this can import by manifest --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 508a576..7dbe831 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "repos": [ { "id": "lnurlp", - "organisation": "lnbits", + "organisation": "bitkarrot", "repository": "lnurlp" } ] From 9fa895aa6d17f6d83d053eac98d62537bb07b573 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 20:34:40 -0800 Subject: [PATCH 08/37] fix username --- models.py | 2 +- templates/lnurlp/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index 20c23a1..0a4e341 100644 --- a/models.py +++ b/models.py @@ -23,7 +23,7 @@ class CreatePayLinkData(BaseModel): success_text: str = Query(None) success_url: str = Query(None) fiat_base_multiplier: int = Query(100, ge=1) - username: str = Query(None) + username: str class PayLink(BaseModel): diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index c902b23..b3142e0 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -162,7 +162,7 @@ From abf0305853e5e6fcb6256f48919825acf1eae1ea Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 20:48:21 -0800 Subject: [PATCH 09/37] fix username display --- templates/lnurlp/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index b3142e0..1384441 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -31,7 +31,7 @@ Description Amount Currency - Lightning Address + LN Username @@ -66,6 +66,7 @@ {{ props.row.min }} - {{ props.row.max }} {{ props.row.currency || 'sat' }} + {{ props.row.username }} Webhook to {{ props.row.webhook_url}} From 6367dee6c2ecd9e4e640e0c6d969960791bc12f6 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:12:42 -0800 Subject: [PATCH 10/37] add test lnaddress buttton --- templates/lnurlp/index.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index 1384441..22f22bc 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -57,6 +57,18 @@ :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" @click="openQrCodeDialog(props.row.id)" > + + + Check LN Address works via external call {{ props.row.description }} From f904c784621514499ed2d147d24d838c5f117804 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:19:25 -0800 Subject: [PATCH 11/37] update api docs --- templates/lnurlp/_api_docs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lnurlp/_api_docs.html b/templates/lnurlp/_api_docs.html index abb37e9..9c07eba 100644 --- a/templates/lnurlp/_api_docs.html +++ b/templates/lnurlp/_api_docs.html @@ -62,7 +62,7 @@ {"description": <string> "amount": <integer> "max": <integer> "min": <integer> "comment_chars": - <integer>}
Returns 201 CREATED (application/json) From 1176fd032221134c3754c79254bf7ef3de27b24a Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 22:10:18 -0800 Subject: [PATCH 12/37] add redirect paths --- __init__.py | 11 +++++++++++ views_api.py | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/__init__.py b/__init__.py index aa13bb9..0d6bb20 100644 --- a/__init__.py +++ b/__init__.py @@ -17,6 +17,17 @@ lnurlp_static_files = [ "name": "lnurlp_static", } ] + +lnurlp_redirect_paths = [ + { + "from_path": "/.well-known/lnurlp", + "redirect_to_path": "/api/v1/well-known", + "header_filters": { + "accept": "application/json" + } + } +] + scheduled_tasks: List[asyncio.Task] = [] lnurlp_ext: APIRouter = APIRouter(prefix="/lnurlp", tags=["lnurlp"]) diff --git a/views_api.py b/views_api.py index b4af294..624406f 100644 --- a/views_api.py +++ b/views_api.py @@ -1,6 +1,7 @@ import json from asyncio.log import logger from http import HTTPStatus +from urllib.parse import urlparse from fastapi import Depends, Query, Request from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl @@ -19,6 +20,12 @@ from .crud import ( update_pay_link, ) from .models import CreatePayLinkData +from .lnurl import lnurl_response + +@lnurlp_ext.get("/api/v1/well-known/{username}") +async def lnaddress(username: str, request: Request): + domain = urlparse(str(request.url)).netloc + return await lnurl_response(username, domain, request) @lnurlp_ext.get("/api/v1/currencies") From 57c36e50aabd7a88e776bbba9dfadd132a1ed3c1 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 22:25:04 -0800 Subject: [PATCH 13/37] add lnurl callback methods --- lnurl.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/lnurl.py b/lnurl.py index 918a5bd..a1d2d58 100644 --- a/lnurl.py +++ b/lnurl.py @@ -1,6 +1,6 @@ from http import HTTPStatus -from fastapi import Request +from fastapi import Request, Query from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse from starlette.exceptions import HTTPException @@ -8,8 +8,68 @@ from lnbits.core.services import create_invoice from lnbits.utils.exchange_rates import get_fiat_rate_satoshis from . import lnurlp_ext -from .crud import increment_pay_link +from .crud import increment_pay_link, get_pay_link, get_address_data +from loguru import logger +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) + + if not address_data: + return {"status": "ERROR", "reason": "Address not found."} + + resp = { + "tag": "payRequest", + "callback": request.url_for( + "lnurlp.api_lnurl_callback", link_id=address_data.id + ), + "metadata": await address_data.lnurlpay_metadata(domain=domain), + "minSendable": int(address_data.min * 1000), + "maxSendable": int(address_data.max * 1000), + } + + logger.debug("RESP", resp) + return resp + + +# for lnaddress callback +@lnurlp_ext.get( + "/api/v1/lnurl/cb/{link_id}", + status_code=HTTPStatus.OK, + name="lnurlp.api_lnurl_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) From e3fafa5c20c1af1504a6a0d33ea48e7fa9b80633 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 22:30:08 -0800 Subject: [PATCH 14/37] adjust lnurl endpoints and calls --- lnurl.py | 1 + views_api.py | 1 + 2 files changed, 2 insertions(+) diff --git a/lnurl.py b/lnurl.py index a1d2d58..1ce2b22 100644 --- a/lnurl.py +++ b/lnurl.py @@ -12,6 +12,7 @@ from .crud import increment_pay_link, get_pay_link, get_address_data from loguru import logger 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) diff --git a/views_api.py b/views_api.py index 624406f..e35e535 100644 --- a/views_api.py +++ b/views_api.py @@ -24,6 +24,7 @@ from .lnurl import lnurl_response @lnurlp_ext.get("/api/v1/well-known/{username}") async def lnaddress(username: str, request: Request): + print("calling /api/v1/well-known") domain = urlparse(str(request.url)).netloc return await lnurl_response(username, domain, request) From a665714978ef23fb3b00cc2aada3ed2ddad9d27c Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 22:40:43 -0800 Subject: [PATCH 15/37] add lnurlp to application/json --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 0d6bb20..c83d46b 100644 --- a/__init__.py +++ b/__init__.py @@ -23,7 +23,7 @@ lnurlp_redirect_paths = [ "from_path": "/.well-known/lnurlp", "redirect_to_path": "/api/v1/well-known", "header_filters": { - "accept": "application/json" + "accept": "application/lnurlp+json" } } ] From afd1cece0b6c0333a99c505af6f276a290dfc52d Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 23:23:52 -0800 Subject: [PATCH 16/37] test redirects --- __init__.py | 7 ++----- views_api.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/__init__.py b/__init__.py index c83d46b..eba504b 100644 --- a/__init__.py +++ b/__init__.py @@ -20,11 +20,8 @@ lnurlp_static_files = [ lnurlp_redirect_paths = [ { - "from_path": "/.well-known/lnurlp", - "redirect_to_path": "/api/v1/well-known", - "header_filters": { - "accept": "application/lnurlp+json" - } + "from_path": "/.well-known/lnurlp/", + "redirect_to_path": "/api/v1/well-known/{username}", } ] diff --git a/views_api.py b/views_api.py index e35e535..bfc2e5d 100644 --- a/views_api.py +++ b/views_api.py @@ -23,10 +23,15 @@ from .models import CreatePayLinkData from .lnurl import lnurl_response @lnurlp_ext.get("/api/v1/well-known/{username}") -async def lnaddress(username: str, request: Request): - print("calling /api/v1/well-known") - domain = urlparse(str(request.url)).netloc - return await lnurl_response(username, domain, request) +async def lnaddress(username: str): + msg = "calling /api/v1/well-known" + username + return msg + +# @lnurlp_ext.get("/api/v1/well-known/{username}") +# async def lnaddress(username: str, request: Request): +# print("calling /api/v1/well-known") +# domain = urlparse(str(request.url)).netloc +# return await lnurl_response(username, domain, request) @lnurlp_ext.get("/api/v1/currencies") From 58737b58e7091f8eff0d8f04b16ab75d9d3a8aff Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 23:38:32 -0800 Subject: [PATCH 17/37] temporary hard link for testing --- __init__.py | 4 ++-- views_api.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/__init__.py b/__init__.py index eba504b..288fa54 100644 --- a/__init__.py +++ b/__init__.py @@ -20,8 +20,8 @@ lnurlp_static_files = [ lnurlp_redirect_paths = [ { - "from_path": "/.well-known/lnurlp/", - "redirect_to_path": "/api/v1/well-known/{username}", + "from_path": "/.well-known/lnurlp/two", + "redirect_to_path": "/api/v1/well-known/two", } ] diff --git a/views_api.py b/views_api.py index bfc2e5d..4f53a20 100644 --- a/views_api.py +++ b/views_api.py @@ -22,16 +22,16 @@ from .crud import ( from .models import CreatePayLinkData from .lnurl import lnurl_response -@lnurlp_ext.get("/api/v1/well-known/{username}") -async def lnaddress(username: str): - msg = "calling /api/v1/well-known" + username - return msg - # @lnurlp_ext.get("/api/v1/well-known/{username}") -# async def lnaddress(username: str, request: Request): -# print("calling /api/v1/well-known") -# domain = urlparse(str(request.url)).netloc -# return await lnurl_response(username, domain, request) +# async def lnaddress(username: str): +# msg = "calling /api/v1/well-known" + username +# return msg + +@lnurlp_ext.get("/api/v1/well-known/{username}") +async def lnaddress(username: str, request: Request): + print("calling /api/v1/well-known") + domain = urlparse(str(request.url)).netloc + return await lnurl_response(username, domain, request) @lnurlp_ext.get("/api/v1/currencies") From 017cb7353f998988380fe97246325e56809ca053 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 2 Mar 2023 23:49:00 -0800 Subject: [PATCH 18/37] fix metadata, crud --- crud.py | 2 +- models.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crud.py b/crud.py index aeefe2a..84b5e53 100644 --- a/crud.py +++ b/crud.py @@ -95,7 +95,7 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: async def get_address_data(username: str) -> Optional[PayLink]: row = await db.fetchone( - "SELECT * FROM lnurl.pay_links WHERE username = ?", (username,) + "SELECT * FROM lnurlp.pay_links WHERE username = ?", (username,) ) return PayLink.from_row(row) if row else None diff --git a/models.py b/models.py index 0a4e341..0bf70b1 100644 --- a/models.py +++ b/models.py @@ -56,10 +56,6 @@ class PayLink(BaseModel): url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id) return lnurl_encode(url) - @property - def lnurlpay_metadata(self) -> LnurlPayMetadata: - return LnurlPayMetadata(json.dumps([["text/plain", self.description]])) - def success_action(self, payment_hash: str) -> Optional[Dict]: if self.success_url: url: ParseResult = urlparse(self.success_url) @@ -75,3 +71,10 @@ class PayLink(BaseModel): return {"tag": "message", "message": self.success_text} 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]] + + return LnurlPayMetadata(json.dumps(metadata)) \ No newline at end of file From 8082913eba76287101ac02db2bbca08bd17b0cc6 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Sun, 5 Mar 2023 21:24:54 -0800 Subject: [PATCH 19/37] fix redirect paths --- __init__.py | 4 ++-- views_api.py | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/__init__.py b/__init__.py index 288fa54..89734b0 100644 --- a/__init__.py +++ b/__init__.py @@ -20,8 +20,8 @@ lnurlp_static_files = [ lnurlp_redirect_paths = [ { - "from_path": "/.well-known/lnurlp/two", - "redirect_to_path": "/api/v1/well-known/two", + "from_path": "/.well-known/lnurlp/", + "redirect_to_path": "/api/v1/well-known/", } ] diff --git a/views_api.py b/views_api.py index 4f53a20..9d7ffd2 100644 --- a/views_api.py +++ b/views_api.py @@ -22,14 +22,9 @@ from .crud import ( from .models import CreatePayLinkData from .lnurl import lnurl_response -# @lnurlp_ext.get("/api/v1/well-known/{username}") -# async def lnaddress(username: str): -# msg = "calling /api/v1/well-known" + username -# return msg - @lnurlp_ext.get("/api/v1/well-known/{username}") async def lnaddress(username: str, request: Request): - print("calling /api/v1/well-known") + print("calling /api/v1/well-known/" + username) domain = urlparse(str(request.url)).netloc return await lnurl_response(username, domain, request) From 572ab62a027f0eabba7f9fe8bf441d495df352b9 Mon Sep 17 00:00:00 2001 From: HackMD <37423+hackmd-hub[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 00:51:28 +0000 Subject: [PATCH 20/37] last changed at Mar 6, 2023 4:49 PM, pushed by Bitkarrot --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ec603ec..8957dff 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,4 @@ LNURL is a range of lightning-network standards that allow us to use lightning-n 3. Optional - add Lightning Address - attach a username to your lnurlp to create a lightning address - the LN address format will be username@lnbits-domain-name + - Find out more about the lightning address spec at lightningaddress.com From 89f9cda6f49fbe9966385690c567e42206ab8fde Mon Sep 17 00:00:00 2001 From: Bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Mon, 6 Mar 2023 23:24:13 -0800 Subject: [PATCH 21/37] remove trailing slashes for redirect --- __init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 89734b0..4ef4c7d 100644 --- a/__init__.py +++ b/__init__.py @@ -20,8 +20,8 @@ lnurlp_static_files = [ lnurlp_redirect_paths = [ { - "from_path": "/.well-known/lnurlp/", - "redirect_to_path": "/api/v1/well-known/", + "from_path": "/.well-known/lnurlp", + "redirect_to_path": "/api/v1/well-known", } ] From 8ad4d5564b8fdac529c8b577b7d584760135cdfe Mon Sep 17 00:00:00 2001 From: Bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Mon, 6 Mar 2023 23:33:59 -0800 Subject: [PATCH 22/37] Update manifest.json put back org --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 7dbe831..508a576 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "repos": [ { "id": "lnurlp", - "organisation": "bitkarrot", + "organisation": "lnbits", "repository": "lnurlp" } ] From 0ae3751cdc13688576ce8f9e51ff88557673293f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:40:01 +0100 Subject: [PATCH 23/37] username optional in model --- models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models.py b/models.py index 0bf70b1..a8156b7 100644 --- a/models.py +++ b/models.py @@ -23,7 +23,7 @@ class CreatePayLinkData(BaseModel): success_text: str = Query(None) success_url: str = Query(None) fiat_base_multiplier: int = Query(100, ge=1) - username: str + username: str = Query(None) class PayLink(BaseModel): @@ -33,6 +33,7 @@ class PayLink(BaseModel): min: float served_meta: int served_pr: int + username: Optional[str] webhook_url: Optional[str] webhook_headers: Optional[str] webhook_body: Optional[str] @@ -42,7 +43,6 @@ class PayLink(BaseModel): comment_chars: int max: float fiat_base_multiplier: int - username: str @classmethod def from_row(cls, row: Row) -> "PayLink": @@ -77,4 +77,4 @@ class PayLink(BaseModel): identifier = f"{self.username}@{domain}" metadata = [["text/plain", text], ["text/identifier", identifier]] - return LnurlPayMetadata(json.dumps(metadata)) \ No newline at end of file + return LnurlPayMetadata(json.dumps(metadata)) From 8fbaaeb31b67594a2663dda1d6f937856cbc5a39 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:47:07 +0100 Subject: [PATCH 24/37] fix table --- templates/lnurlp/index.html | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index 22f22bc..e569d7c 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -26,7 +26,7 @@ > {% raw %} From f2a72a31f1e9e801df45f335235779c3fc9ceeee Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:48:25 +0100 Subject: [PATCH 25/37] wording --- crud.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crud.py b/crud.py index 84b5e53..c8a96e4 100644 --- a/crud.py +++ b/crud.py @@ -3,11 +3,12 @@ from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash -from . import db #, maindb +from . import db # , maindb from .models import CreatePayLinkData, PayLink # from loguru import logger + async def check_lnaddress_update(username: str, id: str) -> bool: # check no duplicates for lnaddress when updating an username row = await db.fetchall( @@ -15,7 +16,7 @@ async def check_lnaddress_update(username: str, id: str) -> bool: (username, id), ) if len(row) > 1: - assert False, "Lightning Address Already exists. Try a different One?" + assert False, "Username already exists. Try a different one." return else: return True @@ -27,7 +28,7 @@ async def check_lnaddress_exists(username: str) -> bool: "SELECT username FROM lnurlp.pay_links WHERE username = ?", (username,) ) if row: - assert False, "Lighting Address Already exists. Try a different One?" + assert False, "Username already exists. Try a different one." return else: return True From 5109833b8f9b06f8e25fa2fa7c62f5d4e01619e1 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 14 Mar 2023 11:52:43 +0100 Subject: [PATCH 26/37] table entry if no ln address is set --- templates/lnurlp/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index e569d7c..c40a0b1 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -31,7 +31,7 @@ Description Amount Currency - LN Username + LN address @@ -66,7 +66,7 @@ {{ props.row.min }} - {{ props.row.max }} {{ props.row.currency || 'sat' }} - {{ props.row.username }} + {{ props.row.username || 'None' }} Webhook to {{ props.row.webhook_url}} 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 27/37] 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") From 07a39e6343b34b0b2866ea11bee57877b1ba9d5e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:10:57 +0100 Subject: [PATCH 28/37] clean types --- lnurl.py | 31 ++++--------------------------- views_api.py | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/lnurl.py b/lnurl.py index 7f31188..8b79678 100644 --- a/lnurl.py +++ b/lnurl.py @@ -13,29 +13,6 @@ from loguru import logger 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."} - - resp = { - "tag": "payRequest", - "callback": request.url_for( - "lnurlp.api_lnurl_callback", link_id=address_data.id - ), - "metadata": await address_data.lnurlpay_metadata, - "minSendable": int(address_data.min * 1000), - "maxSendable": int(address_data.max * 1000), - } - - logger.debug("RESP", resp) - return resp - - @lnurlp_ext.get( "/api/v1/lnurl/cb/lnaddr/{link_id}", status_code=HTTPStatus.OK, @@ -107,10 +84,10 @@ async def api_lnurl_callback( success_action = link.success_action(payment_hash) if success_action: resp = LnurlPayActionResponse( - pr=payment_request, success_action=success_action, routes=[] + pr=payment_request, success_action=success_action, routes=[] # type: ignore ) else: - resp = LnurlPayActionResponse(pr=payment_request, routes=[]) + resp = LnurlPayActionResponse(pr=payment_request, routes=[]) # type: ignore return resp.dict() @@ -144,8 +121,8 @@ async def api_lnurl_response(request: Request, link_id, lnaddress=False): resp = LnurlPayResponse( callback=callback, - min_sendable=round(link.min * rate) * 1000, - max_sendable=round(link.max * rate) * 1000, + min_sendable=round(link.min * rate) * 1000, # type: ignore + max_sendable=round(link.max * rate) * 1000, # type: ignore metadata=link.lnurlpay_metadata, ) params = resp.dict() diff --git a/views_api.py b/views_api.py index 97accd2..7ffed6e 100644 --- a/views_api.py +++ b/views_api.py @@ -23,7 +23,7 @@ from .crud import ( from .models import CreatePayLinkData from .lnurl import api_lnurl_response - +# redirected from /.well-known/lnurlp @lnurlp_ext.get("/api/v1/well-known/{username}") async def lnaddress(username: str, request: Request): address_data = await get_address_data(username) From fcd5a307121ffa30e71770c61e73700992132e02 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:06:04 +0100 Subject: [PATCH 29/37] fix migrations --- migrations.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/migrations.py b/migrations.py index cd2db9a..5910699 100644 --- a/migrations.py +++ b/migrations.py @@ -10,8 +10,7 @@ async def m001_initial(db): description TEXT NOT NULL, amount {db.big_int} NOT NULL, served_meta INTEGER NOT NULL, - served_pr INTEGER NOT NULL, - username TEXT + served_pr INTEGER NOT NULL ); """ ) @@ -98,8 +97,7 @@ async def m006_redux(db): success_url TEXT, comment_chars INTEGER DEFAULT 0, webhook_headers TEXT, - webhook_body TEXT, - username TEXT + webhook_body TEXT ); """ ) @@ -124,8 +122,7 @@ async def m006_redux(db): max, fiat_base_multiplier, webhook_headers, - webhook_body, - username + webhook_body ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, From e46f1fb0271520860c3ec060973cdf28a24c2bae Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:07:00 +0100 Subject: [PATCH 30/37] fix migrations --- migrations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/migrations.py b/migrations.py index 5910699..879bec3 100644 --- a/migrations.py +++ b/migrations.py @@ -124,7 +124,7 @@ async def m006_redux(db): webhook_headers, webhook_body ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( row[0], @@ -142,7 +142,6 @@ async def m006_redux(db): row[12], row[13], row[14], - row[15], ), ) From 8c2f718c66d86a2c074d5d3f202a91b619ab4d1d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:14:25 +0100 Subject: [PATCH 31/37] add lnaddress to extra --- lnurl.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lnurl.py b/lnurl.py index 8b79678..d03faaa 100644 --- a/lnurl.py +++ b/lnurl.py @@ -65,20 +65,25 @@ async def api_lnurl_callback( ).dict() if lnaddress: - domain = urlparse(str(request.url)).netloc - link.domain = domain + # for lnaddress, we have to set this otherwise the metadata won't have the identifier + link.domain = urlparse(str(request.url)).netloc + + extra = { + "tag": "lnurlp", + "link": link.id, + "comment": comment, + "extra": request.query_params.get("amount"), + } + + if lnaddress and link.username and link.domain: + extra["lnaddress"] = f"{link.username}@{link.domain}" payment_hash, payment_request = await create_invoice( wallet_id=link.wallet, amount=int(amount_received / 1000), memo=link.description, unhashed_description=link.lnurlpay_metadata.encode(), - extra={ - "tag": "lnurlp", - "link": link.id, - "comment": comment, - "extra": request.query_params.get("amount"), - }, + extra=extra, ) success_action = link.success_action(payment_hash) @@ -112,9 +117,8 @@ async def api_lnurl_response(request: Request, link_id, lnaddress=False): 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 + # for lnaddress, we have to set this otherwise the metadata won't have the identifier + link.domain = urlparse(str(request.url)).netloc 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) From aadebddd82130846a1d2e04ea07f3a89219673c7 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:21:40 -0700 Subject: [PATCH 32/37] change LN address display label to username --- templates/lnurlp/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index c40a0b1..405ea3a 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -31,7 +31,7 @@ Description Amount Currency - LN address + Username From 8e5ed7d23d37eec78fde16c861d506f10e1cc343 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:26:06 +0100 Subject: [PATCH 33/37] show comment extra only when present --- lnurl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnurl.py b/lnurl.py index d03faaa..0f94f54 100644 --- a/lnurl.py +++ b/lnurl.py @@ -71,10 +71,12 @@ async def api_lnurl_callback( extra = { "tag": "lnurlp", "link": link.id, - "comment": comment, "extra": request.query_params.get("amount"), } + if comment: + extra["comment"] = (comment,) + if lnaddress and link.username and link.domain: extra["lnaddress"] = f"{link.username}@{link.domain}" From 096190cfd2500750c6f532b100ea01f133f9347a Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:27:06 -0700 Subject: [PATCH 34/37] add label for lnaddress to qrcode html --- templates/lnurlp/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index 405ea3a..b5f5909 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -319,6 +319,7 @@ Dispatches webhook to: {{ qrCodeDialog.data.webhook }}
On success: {{ qrCodeDialog.data.success }}
+ Lightning Address: {{ qrCodeDialog.data.username}}

{% endraw %}
From b672a7710cfa75cf35d2624179e067feea99b826 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:35:31 -0700 Subject: [PATCH 35/37] add ln address to qr code --- static/js/index.js | 3 ++- templates/lnurlp/index.html | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 0075f28..c44c4ca 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -93,7 +93,8 @@ new Vue({ : 'do nothing', lnurl: link.lnurl, pay_url: link.pay_url, - print_url: link.print_url + print_url: link.print_url, + username: link.username } this.qrCodeDialog.show = true }, diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index b5f5909..adbcdb7 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -319,7 +319,10 @@ Dispatches webhook to: {{ qrCodeDialog.data.webhook }}
On success: {{ qrCodeDialog.data.success }}
- Lightning Address: {{ qrCodeDialog.data.username}}
+ + Lightning Address: {{ qrCodeDialog.data.username}}@{{domain}} +
+

{% endraw %}
From 11a9d02f9386df18d2e41c34ac700f5bff5a4487 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:45:22 +0100 Subject: [PATCH 36/37] fix missing username --- crud.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/crud.py b/crud.py index c8a96e4..aeb78d0 100644 --- a/crud.py +++ b/crud.py @@ -22,14 +22,13 @@ async def check_lnaddress_update(username: str, id: str) -> bool: return True -async def check_lnaddress_exists(username: str) -> bool: +async def check_lnaddress_not_exists(username: str) -> bool: # check if lnaddress username exists in the database when creating a new entry row = await db.fetchall( "SELECT username FROM lnurlp.pay_links WHERE username = ?", (username,) ) if row: assert False, "Username already exists. Try a different one." - return else: return True @@ -43,8 +42,10 @@ async def check_lnaddress_format(username: str) -> bool: async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: - await check_lnaddress_format(data.username) - await check_lnaddress_exists(data.username) + if data.username: + await check_lnaddress_format(data.username) + await check_lnaddress_not_exists(data.username) + link_id = urlsafe_short_hash()[:6] result = await db.execute( @@ -121,12 +122,10 @@ async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]: return [PayLink.from_row(row) for row in rows] -async def update_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: - for field in kwargs.items(): - if field[0] == "lnaddress": - value = field[1] - await check_lnaddress_format(value) - await check_lnaddress_update(value, str(link_id)) +async def update_pay_link(link_id: str, **kwargs) -> Optional[PayLink]: + if "lnaddress" in kwargs: + await check_lnaddress_format(kwargs["lnaddress"]) + await check_lnaddress_update(kwargs["lnaddress"], link_id) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( @@ -136,7 +135,7 @@ async def update_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: return PayLink.from_row(row) if row else None -async def increment_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: +async def increment_pay_link(link_id: str, **kwargs) -> Optional[PayLink]: q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()]) await db.execute( f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) @@ -145,5 +144,5 @@ async def increment_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: return PayLink.from_row(row) if row else None -async def delete_pay_link(link_id: int) -> None: +async def delete_pay_link(link_id: str) -> None: await db.execute("DELETE FROM lnurlp.pay_links WHERE id = ?", (link_id,)) From 9b7d96ca3d5aa09cfb75d89c811266547283faf6 Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:46:09 -0700 Subject: [PATCH 37/37] add spacer for lnaddress --- templates/lnurlp/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/lnurlp/index.html b/templates/lnurlp/index.html index adbcdb7..1ecd490 100644 --- a/templates/lnurlp/index.html +++ b/templates/lnurlp/index.html @@ -166,11 +166,11 @@ dense v-model.trim="formDialog.data.username" type="text" - label="Lightning Username" + label="Lightning Address" >
- @{% raw %} {{domain}} {% endraw %} +   @ {% raw %} {{domain}} {% endraw %}