From 4d5d3388d2360396ab4fbc67473a938418613be7 Mon Sep 17 00:00:00 2001 From: Arc Date: Tue, 1 Oct 2024 19:50:25 +0100 Subject: [PATCH 1/6] removed timestamp from models --- crud.py | 6 ++++-- models.py | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crud.py b/crud.py index 202f0c8..3c22ecc 100644 --- a/crud.py +++ b/crud.py @@ -130,7 +130,7 @@ async def get_lnurldevice(lnurldevice_id: str, req: Request) -> Optional[Lnurlde async def get_lnurldevices(wallet_ids: List[str], req: Request) -> List[Lnurldevice]: - + q = ",".join(["?"] * len(wallet_ids)) rows = await db.fetchall( f""" @@ -216,7 +216,9 @@ async def get_lnurldevicepayment( async def get_lnurldevicepayments( lnurldevice_ids: List[str], -) -> List[LnurldevicePayment]: +) -> Optional[List[LnurldevicePayment]]: + if not lnurldevice_ids: + return [] q = ",".join(["?"] * len(lnurldevice_ids)) rows = await db.fetchall( f""" diff --git a/models.py b/models.py index ff6d6ab..b280a7a 100644 --- a/models.py +++ b/models.py @@ -32,7 +32,6 @@ class Lnurldevice(BaseModel): currency: str device: str extra: Optional[Union[Json[List[LnurldeviceExtra]], str]] - timestamp: str @property def lnurlpay_metadata(self) -> LnurlPayMetadata: @@ -46,7 +45,6 @@ class LnurldevicePayment(BaseModel): payload: str pin: int sats: int - timestamp: str class Lnurlencode(BaseModel): From 669a426d1842a535d3e9a3760a808668e8eb0fb5 Mon Sep 17 00:00:00 2001 From: Arc Date: Sat, 5 Oct 2024 02:57:48 +0100 Subject: [PATCH 2/6] atm fixed! --- templates/lnurldevice/atm.html | 18 +++++----- views_api.py | 44 +++++++++++++++++------- views_lnurl.py | 63 +++++++++++++++++++--------------- 3 files changed, 77 insertions(+), 48 deletions(-) diff --git a/templates/lnurldevice/atm.html b/templates/lnurldevice/atm.html index a0e80b1..c7fbedb 100644 --- a/templates/lnurldevice/atm.html +++ b/templates/lnurldevice/atm.html @@ -29,26 +29,26 @@ > - Amount is too small to send over onchain, needs to be 10000+ + Amount is too small to send over onchain, needs to be 50000+ sats Onchain not available - Amount is too small to send over liquid, needs to be 2000+ + Amount is too small to send over liquid, needs to be 10000+ sats Onchain not available @@ -232,11 +232,11 @@

this.tab = val.name }, sendOnchainAddress() { - this.onchain_liquid = 'BTC-BTC' + this.onchain_liquid = 'BTCtempBTC' this.sendAddress() }, sendLiquidAddress() { - this.onchain_liquid = 'L-BTC-BTC' + this.onchain_liquid = 'L-BTCtempBTC' this.sendAddress() }, async sendAddress() { @@ -247,8 +247,8 @@

'' ) if (response.data) { + this.ln = '' this.notifyUser('Payment should be with you shortly', 'positive') - this.connectWebsocket(payment_id) } } catch (error) { this.notifyApiError(error) diff --git a/views_api.py b/views_api.py index 38e1c68..71a8b28 100644 --- a/views_api.py +++ b/views_api.py @@ -1,5 +1,6 @@ from http import HTTPStatus +import bolt11 import httpx from fastapi import APIRouter, Depends, HTTPException, Request from lnbits.core.crud import get_user, get_wallet @@ -29,7 +30,7 @@ ) from .helpers import register_atm_payment from .models import CreateLnurldevice, Lnurlencode - +from loguru import logger lnurldevice_api_router = APIRouter() @@ -158,12 +159,6 @@ async def get_lnurldevice_payment_lightning( status_code=HTTPStatus.NOT_FOUND, detail="Payment already claimed." ) - # If its an invoice check its a legit invoice - if ln[:4] == "lnbc": - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Invoices not supported." - ) - # If its an lnaddress or lnurlp get the request from callback elif ln[:5] == "lnurl" or "@" in ln and "." in ln.split("@")[-1]: data = await api_lnurlscan(ln) @@ -183,6 +178,10 @@ async def get_lnurldevice_payment_lightning( ) ln = response.json()["pr"] + # If just an invoice + elif ln[:4] == "lnbc": + ln = ln + # If ln is gibberish, return an error else: raise HTTPException( @@ -193,6 +192,22 @@ async def get_lnurldevice_payment_lightning( """, ) + # If its an invoice check its a legit invoice + if ln[:4] == "lnbc": + invoice = bolt11.decode(ln) + if not invoice.payment_hash: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" + ) + if not invoice.payment_hash: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" + ) + if int(invoice.amount_msat / 1000) != lnurldevicepayment.sats: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Request is not the same as withdraw amount" + ) + # Finally log the payment and make the payment try: lnurldevicepayment, price_msat = await register_atm_payment(lnurldevice, p) @@ -215,7 +230,7 @@ async def get_lnurldevice_payment_lightning( @lnurldevice_api_router.get( - "'/api/v1/boltz/{lnurldevice_id}/{payload}/{onchain_liquid}/{address}" + "/api/v1/boltz/{lnurldevice_id}/{payload}/{onchain_liquid}/{address}" ) async def get_lnurldevice_payment_boltz( req: Request, lnurldevice_id: str, payload: str, onchain_liquid: str, address: str @@ -246,11 +261,15 @@ async def get_lnurldevice_payment_boltz( data = { "wallet": lnurldevice.wallet, - "asset": onchain_liquid.replace("-", "/"), - "amount": price_msat, + "asset": onchain_liquid.replace("temp", "/"), + "amount": lnurldevicepayment.sats, + "direction": "send", "instant_settlement": True, "onchain_address": address, + "feerate": False, + "feerate_value": 0, } + try: lnurldevicepayment.payload = payload await update_lnurldevicepayment(lnurldevicepayment) @@ -258,8 +277,9 @@ async def get_lnurldevice_payment_boltz( response = await client.post( url=f"http://{settings.host}:{settings.port}/boltz/api/v1/swap/reverse", headers={"X-API-KEY": wallet.adminkey}, - data=data, + json=data, ) - return response.json() + resp = response.json() + return resp except Exception as exc: return {"status": "ERROR", "reason": str(exc)} diff --git a/views_lnurl.py b/views_lnurl.py index 3453983..fc51100 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -16,6 +16,8 @@ ) from .helpers import register_atm_payment, xor_decrypt +from loguru import logger + lnurldevice_lnurl_router = APIRouter() @@ -219,39 +221,46 @@ async def lnurl_callback( raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" ) + if not invoice.payment_hash: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" + ) + if int(invoice.amount_msat / 1000) != lnurldevicepayment.sats: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Request is not the same as withdraw amount" + ) wallet = await get_wallet(device.wallet) assert wallet if wallet.balance_msat < (int(lnurldevicepayment.sats / 1000) + 100): raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="Not enough funds" ) - else: - if lnurldevicepayment.payload != k1: - return {"status": "ERROR", "reason": "Bad K1"} - if lnurldevicepayment.payhash != "payment_hash": - return {"status": "ERROR", "reason": "Payment already claimed"} - try: - lnurldevicepayment.payhash = lnurldevicepayment.payload - lnurldevicepayment_updated = await update_lnurldevicepayment( - lnurldevicepayment - ) - assert lnurldevicepayment_updated - await pay_invoice( - wallet_id=device.wallet, - payment_request=pr, - max_sat=int(lnurldevicepayment_updated.sats / 1000), - extra={"tag": "withdraw"}, - ) - except Exception as exc: - lnurldevicepayment.payhash = "payment_hash" - lnurldevicepayment_updated = await update_lnurldevicepayment( - lnurldevicepayment - ) - assert lnurldevicepayment_updated - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Failed to make payment" - ) from exc - return {"status": "OK"} + if lnurldevicepayment.payload != k1: + return {"status": "ERROR", "reason": "Bad K1"} + if lnurldevicepayment.payhash != "payment_hash": + return {"status": "ERROR", "reason": "Payment already claimed"} + try: + lnurldevicepayment.payhash = lnurldevicepayment.payload + lnurldevicepayment_updated = await update_lnurldevicepayment( + lnurldevicepayment + ) + assert lnurldevicepayment_updated + await pay_invoice( + wallet_id=device.wallet, + payment_request=pr, + max_sat=int(lnurldevicepayment_updated.sats / 1000), + extra={"tag": "lnurldevice_withdraw"}, + ) + except Exception as exc: + lnurldevicepayment.payhash = "payment_hash" + lnurldevicepayment_updated = await update_lnurldevicepayment( + lnurldevicepayment + ) + assert lnurldevicepayment_updated + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Failed to make payment" + ) from exc + return {"status": "OK"} if device.device == "switch": if not amount: return {"status": "ERROR", "reason": "No amount"} From a44ae6e800d6c92179154f14c1f4abd1ae92a444 Mon Sep 17 00:00:00 2001 From: Arc Date: Mon, 7 Oct 2024 13:58:43 +0100 Subject: [PATCH 3/6] added delete payment --- views_lnurl.py | 77 ++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/views_lnurl.py b/views_lnurl.py index fc51100..8cbc3ca 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -12,6 +12,7 @@ create_lnurldevicepayment, get_lnurldevice, get_lnurldevicepayment, + delete_atm_payment_link, update_lnurldevicepayment, ) from .helpers import register_atm_payment, xor_decrypt @@ -146,21 +147,26 @@ async def lnurl_params( ) if atm: - lnurldevicepayment, price_msat = await register_atm_payment(device, p) - if not lnurldevicepayment: + try: + lnurldevicepayment, price_msat = await register_atm_payment(device, p) + if not lnurldevicepayment: + return {"status": "ERROR", "reason": "Could not create ATM payment."} + if not price_msat: + return {"status": "ERROR", "reason": "Could not create ATM payment."} + return { + "tag": "withdrawRequest", + "callback": str( + request.url_for( + "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id + ) + ), + "k1": p, + "minWithdrawable": price_msat, + "maxWithdrawable": price_msat, + "defaultDescription": f"{device.title} ID: {lnurldevicepayment.id}", + } + except: return {"status": "ERROR", "reason": "Could not create ATM payment."} - return { - "tag": "withdrawRequest", - "callback": str( - request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id - ) - ), - "k1": p, - "minWithdrawable": price_msat, - "maxWithdrawable": price_msat, - "defaultDescription": f"{device.title} ID: {lnurldevicepayment.id}", - } price_msat = int(price_msat * ((device.profit / 100) + 1)) lnurldevicepayment = await create_lnurldevicepayment( @@ -201,43 +207,35 @@ async def lnurl_callback( ): lnurldevicepayment = await get_lnurldevicepayment(paymentid) if not lnurldevicepayment: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="lnurldevicepayment not found." - ) + return {"status": "ERROR", "reason": "lnurldevicepayment not found."} device = await get_lnurldevice(lnurldevicepayment.deviceid, request) if not device: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="lnurldevice not found." - ) + await delete_atm_payment_link(paymentid) + return {"status": "ERROR", "reason": "lnurldevice not found."} if device.device == "atm": if lnurldevicepayment.payload == lnurldevicepayment.payhash: + await delete_atm_payment_link(paymentid) return {"status": "ERROR", "reason": "Payment already claimed"} if not pr: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="No payment request" - ) + await delete_atm_payment_link(paymentid) + return {"status": "ERROR", "reason": "No payment request."} invoice = bolt11.decode(pr) if not invoice.payment_hash: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" - ) + await delete_atm_payment_link(paymentid) + return {"status": "ERROR", "reason": "Not valid payment request."} if not invoice.payment_hash: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" - ) - if int(invoice.amount_msat / 1000) != lnurldevicepayment.sats: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Request is not the same as withdraw amount" - ) + await delete_atm_payment_link(paymentid) + return {"status": "ERROR", "reason": "Not valid payment request."} wallet = await get_wallet(device.wallet) assert wallet if wallet.balance_msat < (int(lnurldevicepayment.sats / 1000) + 100): - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not enough funds" - ) + await delete_atm_payment_link(paymentid) + return {"status": "ERROR", "reason": "Not enough funds."} if lnurldevicepayment.payload != k1: + await delete_atm_payment_link(paymentid) return {"status": "ERROR", "reason": "Bad K1"} if lnurldevicepayment.payhash != "payment_hash": + await delete_atm_payment_link(paymentid) return {"status": "ERROR", "reason": "Payment already claimed"} try: lnurldevicepayment.payhash = lnurldevicepayment.payload @@ -251,15 +249,14 @@ async def lnurl_callback( max_sat=int(lnurldevicepayment_updated.sats / 1000), extra={"tag": "lnurldevice_withdraw"}, ) - except Exception as exc: + except: lnurldevicepayment.payhash = "payment_hash" lnurldevicepayment_updated = await update_lnurldevicepayment( lnurldevicepayment ) assert lnurldevicepayment_updated - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Failed to make payment" - ) from exc + await delete_atm_payment_link(paymentid) + return {"status": "ERROR", "reason": "Failed to make payment."} return {"status": "OK"} if device.device == "switch": if not amount: From e5cc0003f2118b42f6a8b5f7fd938196ee32fee8 Mon Sep 17 00:00:00 2001 From: Arc Date: Mon, 7 Oct 2024 14:07:05 +0100 Subject: [PATCH 4/6] removed double claim --- views_lnurl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/views_lnurl.py b/views_lnurl.py index 8cbc3ca..2aa44c3 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -255,7 +255,6 @@ async def lnurl_callback( lnurldevicepayment ) assert lnurldevicepayment_updated - await delete_atm_payment_link(paymentid) return {"status": "ERROR", "reason": "Failed to make payment."} return {"status": "OK"} if device.device == "switch": From e4066b03240529e4d5a6d4484e4949054abe8fff Mon Sep 17 00:00:00 2001 From: Arc Date: Mon, 7 Oct 2024 14:15:37 +0100 Subject: [PATCH 5/6] fixed msat error --- views_lnurl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/views_lnurl.py b/views_lnurl.py index 2aa44c3..7305aaf 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -148,11 +148,11 @@ async def lnurl_params( if atm: try: - lnurldevicepayment, price_msat = await register_atm_payment(device, p) + lnurldevicepayment, price_msatl = await register_atm_payment(device, p) if not lnurldevicepayment: return {"status": "ERROR", "reason": "Could not create ATM payment."} - if not price_msat: - return {"status": "ERROR", "reason": "Could not create ATM payment."} + if price_msatl: + price_msat = price_msatl return { "tag": "withdrawRequest", "callback": str( From 7bb944617918a0c3e6e7ca711c81777fd3b46d9f Mon Sep 17 00:00:00 2001 From: Arc Date: Mon, 7 Oct 2024 15:07:29 +0100 Subject: [PATCH 6/6] fixed double spend --- helpers.py | 26 ++++++++++++++------------ views_lnurl.py | 33 ++++++++++++++------------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/helpers.py b/helpers.py index 31eb92f..629ef6c 100644 --- a/helpers.py +++ b/helpers.py @@ -8,7 +8,7 @@ from .crud import create_lnurldevicepayment, get_recent_lnurldevicepayment from .models import Lnurldevice, LnurldevicePayment - +from loguru import logger async def register_atm_payment( device: Lnurldevice, payload: str @@ -16,17 +16,8 @@ async def register_atm_payment( """ Register an ATM payment to avoid double pull. """ - lnurldevicepayment = await get_recent_lnurldevicepayment(payload) - # If the payment is already registered and been paid, return None - if lnurldevicepayment and lnurldevicepayment.payload == lnurldevicepayment.payhash: - return None, None - # If the payment is already registered and not been paid, return lnurlpayment record - elif ( - lnurldevicepayment and lnurldevicepayment.payload != lnurldevicepayment.payhash - ): - return lnurldevicepayment, None - # else create a new lnurlpayment record + # create a new lnurlpayment record data = base64.urlsafe_b64decode(payload) decrypted = xor_decrypt(device.key.encode(), data) price_msat = ( @@ -34,7 +25,18 @@ async def register_atm_payment( if device.currency != "sat" else decrypted[1] * 1000 ) - price_msat = int(price_msat * ((device.profit / 100) + 1)) + price_msat = int(price_msat - ((price_msat / 100) * device.profit)) + + lnurldevicepayment = await get_recent_lnurldevicepayment(payload) + # If the payment is already registered and been paid, return None + if lnurldevicepayment and lnurldevicepayment.payload == lnurldevicepayment.payhash: + return None, price_msat + # If the payment is already registered and not been paid, return lnurlpayment record + if ( + lnurldevicepayment and lnurldevicepayment.payload != lnurldevicepayment.payhash + ): + return lnurldevicepayment, price_msat + lnurldevicepayment = await create_lnurldevicepayment( deviceid=device.id, payload=payload, diff --git a/views_lnurl.py b/views_lnurl.py index 7305aaf..00aa7af 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -147,26 +147,21 @@ async def lnurl_params( ) if atm: - try: - lnurldevicepayment, price_msatl = await register_atm_payment(device, p) - if not lnurldevicepayment: - return {"status": "ERROR", "reason": "Could not create ATM payment."} - if price_msatl: - price_msat = price_msatl - return { - "tag": "withdrawRequest", - "callback": str( - request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id - ) - ), - "k1": p, - "minWithdrawable": price_msat, - "maxWithdrawable": price_msat, - "defaultDescription": f"{device.title} ID: {lnurldevicepayment.id}", - } - except: + lnurldevicepayment, price_msat = await register_atm_payment(device, p) + if not lnurldevicepayment: return {"status": "ERROR", "reason": "Could not create ATM payment."} + return { + "tag": "withdrawRequest", + "callback": str( + request.url_for( + "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id + ) + ), + "k1": p, + "minWithdrawable": price_msat, + "maxWithdrawable": price_msat, + "defaultDescription": f"{device.title} ID: {lnurldevicepayment.id}", + } price_msat = int(price_msat * ((device.profit / 100) + 1)) lnurldevicepayment = await create_lnurldevicepayment(