diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 64db8d72c..1fd8a5932 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -172,6 +172,8 @@ async def pay_invoice( ) await delete_payment(temp_id, conn=conn) else: + async with db.connect() as conn: + await delete_payment(temp_id, conn=conn) raise PaymentFailure( payment.error_message or "Payment failed, but backend didn't give us an error message." diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index b0ea56098..c919821ff 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -72,7 +72,7 @@ async def api_update_wallet( @core_app.get("/api/v1/payments") async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) - pendingPayments = await get_payments(wallet_id=wallet.wallet.id, pending=True) + pendingPayments = await get_payments(wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True) for payment in pendingPayments: await check_invoice_status( wallet_id=payment.wallet_id, payment_hash=payment.payment_hash @@ -193,7 +193,8 @@ async def api_payments_create( invoiceData: CreateInvoiceData = Body(...), ): if wallet.wallet_type < 0 or wallet.wallet_type > 2: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid") + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid") if invoiceData.out is True and wallet.wallet_type == 0: if not invoiceData.bolt11: @@ -204,7 +205,8 @@ async def api_payments_create( return await api_payments_pay_invoice( invoiceData.bolt11, wallet.wallet ) # admin key - return await api_payments_create_invoice(invoiceData, wallet.wallet) # invoice key + # invoice key + return await api_payments_create_invoice(invoiceData, wallet.wallet) class CreateLNURLData(BaseModel): @@ -372,14 +374,16 @@ async def api_lnurlscan(code: str): params.update(callback=url) # with k1 already in it lnurlauth_key = g().wallet.lnurlauth_key(domain) - params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex()) + params.update( + pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex()) else: async with httpx.AsyncClient() as client: r = await client.get(url, timeout=5) if r.is_error: raise HTTPException( status_code=HTTPStatus.SERVICE_UNAVAILABLE, - detail={"domain": domain, "message": "failed to get parameters"}, + detail={"domain": domain, + "message": "failed to get parameters"}, ) try: @@ -409,7 +413,8 @@ async def api_lnurlscan(code: str): if tag == "withdrawRequest": params.update(kind="withdraw") - params.update(fixed=data["minWithdrawable"] == data["maxWithdrawable"]) + params.update(fixed=data["minWithdrawable"] + == data["maxWithdrawable"]) # callback with k1 already in it parsed_callback: ParseResult = urlparse(data["callback"]) diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 0f5c74e35..5f8be4e2f 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -5,6 +5,7 @@ from urllib.parse import urlparse from fastapi import HTTPException from starlette.requests import Request +from starlette.responses import HTMLResponse from lnbits import bolt11 diff --git a/lnbits/extensions/lnaddress/crud.py b/lnbits/extensions/lnaddress/crud.py index 4cfff2f9e..3bd636f19 100644 --- a/lnbits/extensions/lnaddress/crud.py +++ b/lnbits/extensions/lnaddress/crud.py @@ -176,7 +176,7 @@ async def purge_addresses(domain_id: str): now = datetime.now().timestamp() for row in rows: - r = Addresses(**row)._asdict() + r = Addresses(**row).dict() start = datetime.fromtimestamp(r["time"]) paid = r["paid"] diff --git a/lnbits/extensions/lnaddress/lnurl.py b/lnbits/extensions/lnaddress/lnurl.py index 30b8fc5a4..471403153 100644 --- a/lnbits/extensions/lnaddress/lnurl.py +++ b/lnbits/extensions/lnaddress/lnurl.py @@ -1,4 +1,5 @@ import hashlib +import json from datetime import datetime, timedelta import httpx @@ -9,6 +10,7 @@ from lnurl import ( # type: ignore LnurlPayResponse, ) from starlette.requests import Request +from starlette.responses import HTMLResponse from . import lnaddress_ext from .crud import get_address, get_address_by_username, get_domain @@ -28,27 +30,29 @@ async def lnurl_response(username: str, domain: str, request: Request): if now > expiration: return LnurlErrorResponse(reason="Address has expired.").dict() - resp = LnurlPayResponse( - callback=request.url_for("lnaddress.lnurl_callback", address_id=address.id), - min_sendable=1000, - max_sendable=1000000000, - metadata=await address.lnurlpay_metadata(), - ) + resp = { + "tag": "payRequest", + "callback": request.url_for("lnaddress.lnurl_callback", address_id=address.id), + "metadata": await address.lnurlpay_metadata(domain=domain), + "minSendable": 1000, + "maxSendable": 1000000000, + } - return resp.dict() + print("RESP", resp) + return resp @lnaddress_ext.get("/lnurl/cb/{address_id}", name="lnaddress.lnurl_callback") async def lnurl_callback(address_id, amount: int = Query(...)): + print("PING") address = await get_address(address_id) - if not address: return LnurlErrorResponse(reason=f"Address not found").dict() amount_received = amount domain = await get_domain(address.domain) - + base_url = ( address.wallet_endpoint[:-1] if address.wallet_endpoint.endswith("/") @@ -67,7 +71,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)): "out": False, "amount": int(amount_received / 1000), "description_hash": hashlib.sha256( - (await address.lnurlpay_metadata()).encode("utf-8") + (await address.lnurlpay_metadata(domain=domain.domain)).encode("utf-8") ).hexdigest(), "extra": {"tag": f"Payment to {address.username}@{domain.domain}"}, }, @@ -78,6 +82,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)): except AssertionError as e: return LnurlErrorResponse(reason="ERROR") - resp = LnurlPayActionResponse(pr=r["payment_request"], routes=[]) + # resp = LnurlPayActionResponse(pr=r["payment_request"], routes=[]) + resp = {"pr": r["payment_request"], "routes": []} - return resp.dict() + return resp diff --git a/lnbits/extensions/lnaddress/models.py b/lnbits/extensions/lnaddress/models.py index eb0984572..6f21278ef 100644 --- a/lnbits/extensions/lnaddress/models.py +++ b/lnbits/extensions/lnaddress/models.py @@ -3,7 +3,7 @@ from typing import Optional from fastapi.params import Query from lnurl.types import LnurlPayMetadata -from pydantic.main import BaseModel # type: ignore +from pydantic.main import BaseModel class CreateDomain(BaseModel): @@ -49,8 +49,9 @@ class Addresses(BaseModel): paid: bool time: int - async def lnurlpay_metadata(self) -> LnurlPayMetadata: + async def lnurlpay_metadata(self, domain) -> LnurlPayMetadata: text = f"Payment to {self.username}" - metadata = [["text/plain", text]] - + identifier = f"{self.username}@{domain}" + metadata = [["text/plain", text], ["text/identifier", identifier]] + return LnurlPayMetadata(json.dumps(metadata)) diff --git a/lnbits/extensions/lnaddress/tasks.py b/lnbits/extensions/lnaddress/tasks.py index f962f4abb..9702c70b6 100644 --- a/lnbits/extensions/lnaddress/tasks.py +++ b/lnbits/extensions/lnaddress/tasks.py @@ -47,7 +47,7 @@ async def on_invoice_paid(payment: Payment) -> None: await payment.set_pending(False) await set_address_paid(payment_hash=payment.payment_hash) - await call_webhook_on_paid(payment.payment_hash) + await call_webhook_on_paid(payment_hash=payment.payment_hash) elif "renew lnaddress" == payment.extra.get("tag"): @@ -55,7 +55,7 @@ async def on_invoice_paid(payment: Payment) -> None: await set_address_renewed( address_id=payment.extra["id"], duration=payment.extra["duration"] ) - await call_webhook_on_paid(payment.payment_hash) + await call_webhook_on_paid(payment_hash=payment.payment_hash) else: return diff --git a/lnbits/extensions/lnaddress/templates/lnaddress/display.html b/lnbits/extensions/lnaddress/templates/lnaddress/display.html index 67745fa85..7164752c0 100644 --- a/lnbits/extensions/lnaddress/templates/lnaddress/display.html +++ b/lnbits/extensions/lnaddress/templates/lnaddress/display.html @@ -370,10 +370,9 @@ if (data.wallet_endpoint == '') { data.wallet_endpoint = null } - data.wallet_endpoint = data.wallet_endpoint ?? '{{ request.url_root }}' + data.wallet_endpoint = data.wallet_endpoint ?? '{{ root_url }}' data.duration = parseInt(data.duration) - console.log('data', data) - + axios .post('/lnaddress/api/v1/address/{{ domain_id }}', data) .then(response => { diff --git a/lnbits/extensions/lnaddress/templates/lnaddress/index.html b/lnbits/extensions/lnaddress/templates/lnaddress/index.html index ef04b5937..dffef837a 100644 --- a/lnbits/extensions/lnaddress/templates/lnaddress/index.html +++ b/lnbits/extensions/lnaddress/templates/lnaddress/index.html @@ -186,10 +186,14 @@ + Your API key in cloudflare @@ -489,18 +493,6 @@ this.getDomains() this.getAddresses() } - // var self = this - // - // // axios is available for making requests - // axios({ - // method: 'GET', - // url: '/example/api/v1/tools', - // headers: { - // 'X-example-header': 'not-used' - // } - // }).then(function (response) { - // self.tools = response.data - // }) } }) diff --git a/lnbits/extensions/lnaddress/views.py b/lnbits/extensions/lnaddress/views.py index cec12bf89..ef6d2b766 100644 --- a/lnbits/extensions/lnaddress/views.py +++ b/lnbits/extensions/lnaddress/views.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from urllib.parse import urlparse from fastapi import Request from fastapi.params import Depends @@ -34,7 +35,8 @@ async def display(domain_id, request: Request): await purge_addresses(domain_id) wallet = await get_wallet(domain.wallet) - + url = urlparse(str(request.url)) + return lnaddress_renderer().TemplateResponse( "lnaddress/display.html", { @@ -43,5 +45,6 @@ async def display(domain_id, request: Request): "domain_domain": domain.domain, "domain_cost": domain.cost, "domain_wallet_inkey": wallet.inkey, + "root_url": f"{url.scheme}://{url.netloc}" }, ) diff --git a/lnbits/extensions/usermanager/models.py b/lnbits/extensions/usermanager/models.py index e65bdb52c..67facec68 100644 --- a/lnbits/extensions/usermanager/models.py +++ b/lnbits/extensions/usermanager/models.py @@ -2,6 +2,7 @@ from sqlite3 import Row from fastapi.param_functions import Query from pydantic import BaseModel +from typing import Optional class CreateUserData(BaseModel): @@ -22,8 +23,8 @@ class Users(BaseModel): id: str name: str admin: str - email: str - password: str + email: Optional[str] = None + password: Optional[str] = None class Wallets(BaseModel): diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py index ecbfcb25b..8c6523859 100644 --- a/lnbits/extensions/usermanager/views_api.py +++ b/lnbits/extensions/usermanager/views_api.py @@ -23,7 +23,7 @@ from .crud import ( ) from .models import CreateUserData, CreateUserWallet -### Users +# Users @usermanager_ext.get("/api/v1/users", status_code=HTTPStatus.OK) @@ -63,7 +63,7 @@ async def api_usermanager_users_delete( raise HTTPException(status_code=HTTPStatus.NO_CONTENT) -###Activate Extension +# Activate Extension @usermanager_ext.post("/api/v1/extensions") @@ -79,7 +79,7 @@ async def api_usermanager_activate_extension( return {"extension": "updated"} -###Wallets +# Wallets @usermanager_ext.post("/api/v1/wallets") @@ -98,7 +98,7 @@ async def api_usermanager_wallets(wallet: WalletTypeInfo = Depends(get_key_type) return [wallet.dict() for wallet in await get_usermanager_wallets(admin_id)] -@usermanager_ext.get("/api/v1/wallets/{wallet_id}") +@usermanager_ext.get("/api/v1/transactions/{wallet_id}") async def api_usermanager_wallet_transactions( wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) ): diff --git a/lnbits/jinja2_templating.py b/lnbits/jinja2_templating.py index f74f05c05..5abcd0bff 100644 --- a/lnbits/jinja2_templating.py +++ b/lnbits/jinja2_templating.py @@ -23,7 +23,7 @@ class Jinja2Templates(templating.Jinja2Templates): def get_environment(self, loader: "jinja2.BaseLoader") -> "jinja2.Environment": @jinja2.contextfunction def url_for(context: dict, name: str, **path_params: typing.Any) -> str: - request: Request = context["request"] # type: starlette.requests.Request + request: Request = context["request"] return request.app.url_path_for(name, **path_params) def url_params_update(init: QueryParams, **new: typing.Any) -> QueryParams: diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index f0824dacc..e3addfd68 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -5,6 +5,8 @@ import base64 from os import getenv from typing import Optional, Dict, AsyncGenerator +from lnbits import bolt11 as lnbits_bolt11 + from .base import ( StatusResponse, InvoiceResponse, @@ -21,7 +23,8 @@ class LndRestWallet(Wallet): endpoint = getenv("LND_REST_ENDPOINT") endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint endpoint = ( - "https://" + endpoint if not endpoint.startswith("http") else endpoint + "https://" + + endpoint if not endpoint.startswith("http") else endpoint ) self.endpoint = endpoint @@ -89,10 +92,21 @@ class LndRestWallet(Wallet): async def pay_invoice(self, bolt11: str) -> PaymentResponse: async with httpx.AsyncClient(verify=self.cert) as client: + # set the fee limit for the payment + invoice = lnbits_bolt11.decode(bolt11) + lnrpcFeeLimit = dict() + if invoice.amount_msat > 1000_000: + lnrpcFeeLimit["percent"] = "1" # in percent + else: + lnrpcFeeLimit["fixed"] = "10" # in sat + r = await client.post( url=f"{self.endpoint}/v1/channels/transactions", headers=self.auth, - json={"payment_request": bolt11}, + json={ + "payment_request": bolt11, + "fee_limit": lnrpcFeeLimit, + }, timeout=180, ) @@ -168,7 +182,8 @@ class LndRestWallet(Wallet): except: continue - payment_hash = base64.b64decode(inv["r_hash"]).hex() + payment_hash = base64.b64decode( + inv["r_hash"]).hex() yield payment_hash except (OSError, httpx.ConnectError, httpx.ReadError): pass diff --git a/mypy.ini b/mypy.ini index b991c7cc2..f8b1844b6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,2 +1 @@ [mypy] -plugins = trio_typing.plugin