lnaddress works

This commit is contained in:
callebtc 2023-03-14 15:05:52 +01:00
commit bea8db1595
4 changed files with 81 additions and 78 deletions

129
lnurl.py
View file

@ -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

View file

@ -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;")

View file

@ -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))

View file

@ -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")