From 9c8a79eb9bbcabcbde2f0ad82872f4f5b1344395 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Thu, 2 Feb 2023 12:57:36 +0000 Subject: [PATCH] fix pyright lnbits/wallets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dni ⚡ --- lnbits/wallets/cliche.py | 15 +++++++-- lnbits/wallets/eclair.py | 26 ++++++++-------- lnbits/wallets/fake.py | 27 ++++++++-------- lnbits/wallets/lnbits.py | 7 ++--- lnbits/wallets/lndgrpc.py | 35 ++++++++++++--------- lnbits/wallets/lndrest.py | 13 ++++++-- lnbits/wallets/lnpay.py | 56 ++++++++++++++++----------------- lnbits/wallets/lntips.py | 10 +++--- lnbits/wallets/opennode.py | 41 +++++++++++++------------ lnbits/wallets/spark.py | 63 +++++++++++++++++++++++--------------- 10 files changed, 163 insertions(+), 130 deletions(-) diff --git a/lnbits/wallets/cliche.py b/lnbits/wallets/cliche.py index 211ba4f3..cb11f520 100644 --- a/lnbits/wallets/cliche.py +++ b/lnbits/wallets/cliche.py @@ -22,6 +22,8 @@ class ClicheWallet(Wallet): def __init__(self): self.endpoint = settings.cliche_endpoint + if not self.endpoint: + raise Exception("cannot initialize cliche") async def status(self) -> StatusResponse: try: @@ -36,7 +38,7 @@ class ClicheWallet(Wallet): data = json.loads(r) except: return StatusResponse( - f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0 + f"Failed to connect to {self.endpoint}, got: '{r[:200]}...'", 0 ) return StatusResponse(None, data["result"]["wallets"][0]["balance"]) @@ -89,6 +91,13 @@ class ClicheWallet(Wallet): async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: ws = create_connection(self.endpoint) ws.send(f"pay-invoice --invoice {bolt11}") + checking_id, fee_msat, preimage, error_message, payment_ok = ( + None, + None, + None, + None, + None, + ) for _ in range(2): r = ws.recv() data = json.loads(r) @@ -151,9 +160,9 @@ class ClicheWallet(Wallet): async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: while True: try: - ws = await create_connection(self.endpoint) + ws = create_connection(self.endpoint) while True: - r = await ws.recv() + r = ws.recv() data = json.loads(r) print(data) try: diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py index a45123b1..073f486e 100644 --- a/lnbits/wallets/eclair.py +++ b/lnbits/wallets/eclair.py @@ -7,10 +7,7 @@ from typing import AsyncGenerator, Dict, Optional import httpx from loguru import logger - -# TODO: https://github.com/lnbits/lnbits/issues/764 -# mypy https://github.com/aaugustin/websockets/issues/940 -from websockets import connect # type: ignore +from websockets.client import connect from lnbits.settings import settings @@ -34,11 +31,13 @@ class UnknownError(Exception): class EclairWallet(Wallet): def __init__(self): url = settings.eclair_url - self.url = url[:-1] if url.endswith("/") else url + passw = settings.eclair_pass + if not url or not passw: + raise Exception("cannot initialize eclair") + self.url = url[:-1] if url.endswith("/") else url self.ws_url = f"ws://{urllib.parse.urlsplit(self.url).netloc}/ws" - passw = settings.eclair_pass encodedAuth = base64.b64encode(f":{passw}".encode()) auth = str(encodedAuth, "utf-8") self.auth = {"Authorization": f"Basic {auth}"} @@ -71,7 +70,11 @@ class EclairWallet(Wallet): **kwargs, ) -> InvoiceResponse: - data: Dict = {"amountMsat": amount * 1000} + data: Dict = { + "amountMsat": amount * 1000, + "description_hash": b"", + "description": memo, + } if kwargs.get("expiry"): data["expireIn"] = kwargs["expiry"] @@ -79,8 +82,6 @@ class EclairWallet(Wallet): data["descriptionHash"] = description_hash.hex() elif unhashed_description: data["descriptionHash"] = hashlib.sha256(unhashed_description).hexdigest() - else: - data["description"] = memo or "" async with httpx.AsyncClient() as client: r = await client.post( @@ -149,6 +150,7 @@ class EclairWallet(Wallet): } data = r.json()[-1] + fee_msat = 0 if data["status"]["type"] == "sent": fee_msat = -data["status"]["feesPaid"] preimage = data["status"]["paymentPreimage"] @@ -223,10 +225,10 @@ class EclairWallet(Wallet): ) as ws: while True: message = await ws.recv() - message = json.loads(message) + message_json = json.loads(message) - if message and message["type"] == "payment-received": - yield message["paymentHash"] + if message_json and message_json["type"] == "payment-received": + yield message_json["paymentHash"] except Exception as exc: logger.error( diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 93e7d3f0..85a0dfb1 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -45,19 +45,18 @@ class FakeWallet(Wallet): ) -> InvoiceResponse: data: Dict = { "out": False, - "amount": amount, + "amount": amount * 1000, "currency": "bc", "privkey": self.privkey, - "memo": None, - "description_hash": None, + "memo": memo, + "description_hash": b"", "description": "", "fallback": None, - "expires": None, + "expires": kwargs.get("expiry"), + "timestamp": datetime.now().timestamp(), "route": None, + "tags_set": [], } - data["expires"] = kwargs.get("expiry") - data["amount"] = amount * 1000 - data["timestamp"] = datetime.now().timestamp() if description_hash: data["tags_set"] = ["h"] data["description_hash"] = description_hash @@ -69,7 +68,7 @@ class FakeWallet(Wallet): data["memo"] = memo data["description"] = memo randomHash = ( - data["privkey"][:6] + self.privkey[:6] + hashlib.sha256(str(random.getrandbits(256)).encode()).hexdigest()[6:] ) data["paymenthash"] = randomHash @@ -78,12 +77,10 @@ class FakeWallet(Wallet): return InvoiceResponse(True, checking_id, payment_request) - async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: + async def pay_invoice(self, bolt11: str, _: int) -> PaymentResponse: invoice = decode(bolt11) - if ( - hasattr(invoice, "checking_id") - and invoice.checking_id[:6] == self.privkey[:6] # type: ignore - ): + + if invoice.checking_id and invoice.checking_id[:6] == self.privkey[:6]: await self.queue.put(invoice) return PaymentResponse(True, invoice.payment_hash, 0) else: @@ -91,10 +88,10 @@ class FakeWallet(Wallet): ok=False, error_message="Only internal invoices can be used!" ) - async def get_invoice_status(self, checking_id: str) -> PaymentStatus: + async def get_invoice_status(self, _: str) -> PaymentStatus: return PaymentStatus(None) - async def get_payment_status(self, checking_id: str) -> PaymentStatus: + async def get_payment_status(self, _: str) -> PaymentStatus: return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: diff --git a/lnbits/wallets/lnbits.py b/lnbits/wallets/lnbits.py index 74c9efcc..902711d6 100644 --- a/lnbits/wallets/lnbits.py +++ b/lnbits/wallets/lnbits.py @@ -21,12 +21,13 @@ class LNbitsWallet(Wallet): def __init__(self): self.endpoint = settings.lnbits_endpoint - key = ( settings.lnbits_key or settings.lnbits_admin_key or settings.lnbits_invoice_key ) + if not self.endpoint or not key: + raise Exception("cannot initialize lnbits wallet") self.key = {"X-Api-Key": key} async def status(self) -> StatusResponse: @@ -60,7 +61,7 @@ class LNbitsWallet(Wallet): unhashed_description: Optional[bytes] = None, **kwargs, ) -> InvoiceResponse: - data: Dict = {"out": False, "amount": amount} + data: Dict = {"out": False, "amount": amount, "memo": memo or ""} if kwargs.get("expiry"): data["expiry"] = kwargs["expiry"] if description_hash: @@ -68,8 +69,6 @@ class LNbitsWallet(Wallet): if unhashed_description: data["unhashed_description"] = unhashed_description.hex() - data["memo"] = memo or "" - async with httpx.AsyncClient() as client: r = await client.post( url=f"{self.endpoint}/api/v1/payments", headers=self.key, json=data diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index 4173e79e..5cb90a7c 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -105,9 +105,6 @@ class LndWallet(Wallet): ) endpoint = settings.lnd_grpc_endpoint - self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - self.port = int(settings.lnd_grpc_port) - self.cert_path = settings.lnd_grpc_cert or settings.lnd_cert macaroon = ( settings.lnd_grpc_macaroon @@ -122,8 +119,17 @@ class LndWallet(Wallet): macaroon = AESCipher(description="macaroon decryption").decrypt( encrypted_macaroon ) - self.macaroon = load_macaroon(macaroon) + cert_path = settings.lnd_grpc_cert or settings.lnd_cert + if not endpoint or not macaroon or not cert_path or not settings.lnd_grpc_port: + raise Exception("cannot initialize lndrest") + + self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint + self.port = int(settings.lnd_grpc_port) + self.cert_path = settings.lnd_grpc_cert or settings.lnd_cert + + self.macaroon = load_macaroon(macaroon) + self.cert_path = cert_path cert = open(self.cert_path, "rb").read() creds = grpc.ssl_channel_credentials(cert) auth_creds = grpc.metadata_call_credentials(self.metadata_callback) @@ -140,8 +146,6 @@ class LndWallet(Wallet): async def status(self) -> StatusResponse: try: resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest()) - except RpcError as exc: - return StatusResponse(str(exc._details), 0) except Exception as exc: return StatusResponse(str(exc), 0) @@ -155,20 +159,23 @@ class LndWallet(Wallet): unhashed_description: Optional[bytes] = None, **kwargs, ) -> InvoiceResponse: - params: Dict = {"value": amount, "private": True} + data: Dict = { + "description_hash": b"", + "value": amount, + "private": True, + "memo": memo or "", + } if kwargs.get("expiry"): - params["expiry"] = kwargs["expiry"] + data["expiry"] = kwargs["expiry"] if description_hash: - params["description_hash"] = description_hash + data["description_hash"] = description_hash elif unhashed_description: - params["description_hash"] = hashlib.sha256( + data["description_hash"] = hashlib.sha256( unhashed_description ).digest() # as bytes directly - else: - params["memo"] = memo or "" try: - req = ln.Invoice(**params) + req = ln.Invoice(**data) resp = await self.rpc.AddInvoice(req) except Exception as exc: error_message = str(exc) @@ -188,8 +195,6 @@ class LndWallet(Wallet): ) try: resp = await self.routerpc.SendPaymentV2(req).read() - except RpcError as exc: - return PaymentResponse(False, None, None, None, exc._details) except Exception as exc: return PaymentResponse(False, None, None, None, str(exc)) diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index c0e344ae..44c86850 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -43,6 +43,15 @@ class LndRestWallet(Wallet): macaroon = AESCipher(description="macaroon decryption").decrypt( encrypted_macaroon ) + + if not endpoint or not macaroon or not settings.lnd_rest_cert: + raise Exception("cannot initialize lndrest") + + endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint + endpoint = ( + "https://" + endpoint if not endpoint.startswith("http") else endpoint + ) + self.endpoint = endpoint self.macaroon = load_macaroon(macaroon) self.auth = {"Grpc-Metadata-macaroon": self.macaroon} @@ -74,7 +83,7 @@ class LndRestWallet(Wallet): unhashed_description: Optional[bytes] = None, **kwargs, ) -> InvoiceResponse: - data: Dict = {"value": amount, "private": True} + data: Dict = {"value": amount, "private": True, "memo": memo or ""} if kwargs.get("expiry"): data["expiry"] = kwargs["expiry"] if description_hash: @@ -85,8 +94,6 @@ class LndRestWallet(Wallet): data["description_hash"] = base64.b64encode( hashlib.sha256(unhashed_description).digest() ).decode("ascii") - else: - data["memo"] = memo or "" async with httpx.AsyncClient(verify=self.cert) as client: r = await client.post( diff --git a/lnbits/wallets/lnpay.py b/lnbits/wallets/lnpay.py index ccc5254c..d75e169c 100644 --- a/lnbits/wallets/lnpay.py +++ b/lnbits/wallets/lnpay.py @@ -1,12 +1,8 @@ import asyncio import hashlib -import json -from http import HTTPStatus from typing import AsyncGenerator, Dict, Optional import httpx -from fastapi.exceptions import HTTPException -from loguru import logger from lnbits.settings import settings @@ -24,8 +20,13 @@ class LNPayWallet(Wallet): def __init__(self): endpoint = settings.lnpay_api_endpoint + wallet_key = settings.lnpay_wallet_key or settings.lnpay_admin_key + + if not endpoint or not wallet_key or not settings.lnpay_api_key: + raise Exception("cannot initialize lnpay") + + self.wallet_key = wallet_key self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - self.wallet_key = settings.lnpay_wallet_key or settings.lnpay_admin_key self.auth = {"X-Api-Key": settings.lnpay_api_key} async def status(self) -> StatusResponse: @@ -54,7 +55,6 @@ class LNPayWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, ) -> InvoiceResponse: data: Dict = {"num_satoshis": f"{amount}"} if description_hash: @@ -133,27 +133,27 @@ class LNPayWallet(Wallet): value = await self.queue.get() yield value - async def webhook_listener(self): - text: str = await request.get_data() - try: - data = json.loads(text) - except json.decoder.JSONDecodeError: - logger.error(f"got something wrong on lnpay webhook endpoint: {text[:200]}") - data = None - if ( - type(data) is not dict - or "event" not in data - or data["event"].get("name") != "wallet_receive" - ): - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + # async def webhook_listener(self): + # text: str = await request.get_data() + # try: + # data = json.loads(text) + # except json.decoder.JSONDecodeError: + # logger.error(f"got something wrong on lnpay webhook endpoint: {text[:200]}") + # data = None + # if ( + # type(data) is not dict + # or "event" not in data + # or data["event"].get("name") != "wallet_receive" + # ): + # raise HTTPException(status_code=HTTPStatus.NO_CONTENT) - lntx_id = data["data"]["wtx"]["lnTx"]["id"] - async with httpx.AsyncClient() as client: - r = await client.get( - f"{self.endpoint}/lntx/{lntx_id}?fields=settled", headers=self.auth - ) - data = r.json() - if data["settled"]: - await self.queue.put(lntx_id) + # lntx_id = data["data"]["wtx"]["lnTx"]["id"] + # async with httpx.AsyncClient() as client: + # r = await client.get( + # f"{self.endpoint}/lntx/{lntx_id}?fields=settled", headers=self.auth + # ) + # data = r.json() + # if data["settled"]: + # await self.queue.put(lntx_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + # raise HTTPException(status_code=HTTPStatus.NO_CONTENT) diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py index 4551a207..23c9856a 100644 --- a/lnbits/wallets/lntips.py +++ b/lnbits/wallets/lntips.py @@ -21,13 +21,14 @@ from .base import ( class LnTipsWallet(Wallet): def __init__(self): endpoint = settings.lntips_api_endpoint - self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - key = ( settings.lntips_api_key or settings.lntips_admin_key or settings.lntips_invoice_key ) + if not endpoint or not key: + raise Exception("cannot initialize lntxbod") + self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.auth = {"Authorization": f"Basic {key}"} async def status(self) -> StatusResponse: @@ -53,15 +54,12 @@ class LnTipsWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, ) -> InvoiceResponse: - data: Dict = {"amount": amount} + data: Dict = {"amount": amount, "description_hash": "", "memo": memo or ""} if description_hash: data["description_hash"] = description_hash.hex() elif unhashed_description: data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest() - else: - data["memo"] = memo or "" async with httpx.AsyncClient() as client: r = await client.post( diff --git a/lnbits/wallets/opennode.py b/lnbits/wallets/opennode.py index 89c7f1d5..2aa3b837 100644 --- a/lnbits/wallets/opennode.py +++ b/lnbits/wallets/opennode.py @@ -1,11 +1,10 @@ import asyncio -import hmac -from http import HTTPStatus + +# import hmac +# from http import HTTPStatus from typing import AsyncGenerator, Optional import httpx -from fastapi.exceptions import HTTPException -from loguru import logger from lnbits.settings import settings @@ -18,19 +17,24 @@ from .base import ( Wallet, ) +# from fastapi import Request, HTTPException +# from loguru import logger + class OpenNodeWallet(Wallet): """https://developers.opennode.com/""" def __init__(self): endpoint = settings.opennode_api_endpoint - self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - key = ( settings.opennode_key or settings.opennode_admin_key or settings.opennode_invoice_key ) + if not endpoint or not key: + raise Exception("cannot initialize opennode") + + self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.auth = {"Authorization": key} async def status(self) -> StatusResponse: @@ -54,7 +58,6 @@ class OpenNodeWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, ) -> InvoiceResponse: if description_hash or unhashed_description: raise Unsupported("description_hash") @@ -139,17 +142,17 @@ class OpenNodeWallet(Wallet): value = await self.queue.get() yield value - async def webhook_listener(self): - data = await request.form - if "status" not in data or data["status"] != "paid": - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + # async def webhook_listener(self): + # data = await request.form + # if "status" not in data or data["status"] != "paid": + # raise HTTPException(status_code=HTTPStatus.NO_CONTENT) - charge_id = data["id"] - x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256") - x.update(charge_id.encode("ascii")) - if x.hexdigest() != data["hashed_order"]: - logger.error("invalid webhook, not from opennode") - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + # charge_id = data["id"] + # x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256") + # x.update(charge_id.encode("ascii")) + # if x.hexdigest() != data["hashed_order"]: + # logger.error("invalid webhook, not from opennode") + # raise HTTPException(status_code=HTTPStatus.NO_CONTENT) - await self.queue.put(charge_id) - raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + # await self.queue.put(charge_id) + # raise HTTPException(status_code=HTTPStatus.NO_CONTENT) diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py index 66cfba36..a130becb 100644 --- a/lnbits/wallets/spark.py +++ b/lnbits/wallets/spark.py @@ -28,6 +28,7 @@ class UnknownError(Exception): class SparkWallet(Wallet): def __init__(self): + assert settings.spark_url self.url = settings.spark_url.replace("/rpc", "") self.token = settings.spark_token @@ -46,6 +47,7 @@ class SparkWallet(Wallet): try: async with httpx.AsyncClient() as client: + assert self.token r = await client.post( self.url + "/rpc", headers={"X-Access": self.token}, @@ -133,38 +135,49 @@ class SparkWallet(Wallet): bolt11=bolt11, maxfee=fee_limit_msat, ) + fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"]) + preimage = r["payment_preimage"] + return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None) + except (SparkError, UnknownError) as exc: listpays = await self.listpays(bolt11) - if listpays: - pays = listpays["pays"] + if not listpays: + return PaymentResponse(False, None, None, None, str(exc)) - if len(pays) == 0: - return PaymentResponse(False, None, None, None, str(exc)) + pays = listpays["pays"] - pay = pays[0] - payment_hash = pay["payment_hash"] + if len(pays) == 0: + return PaymentResponse(False, None, None, None, str(exc)) - if len(pays) > 1: - raise SparkError( - f"listpays({payment_hash}) returned an unexpected response: {listpays}" - ) + pay = pays[0] + payment_hash = pay["payment_hash"] - if pay["status"] == "failed": - return PaymentResponse(False, None, None, None, str(exc)) - elif pay["status"] == "pending": - return PaymentResponse(None, payment_hash, None, None, None) - elif pay["status"] == "complete": - r = pay - r["payment_preimage"] = pay["preimage"] - r["msatoshi"] = int(pay["amount_msat"][0:-4]) - r["msatoshi_sent"] = int(pay["amount_sent_msat"][0:-4]) - # this may result in an error if it was paid previously - # our database won't allow the same payment_hash to be added twice - # this is good + if len(pays) > 1: + raise SparkError( + f"listpays({payment_hash}) returned an unexpected response: {listpays}" + ) - fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"]) - preimage = r["payment_preimage"] - return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None) + if pay["status"] == "failed": + return PaymentResponse(False, None, None, None, str(exc)) + + elif pay["status"] == "pending": + return PaymentResponse(None, payment_hash, None, None, None) + + elif pay["status"] == "complete": + r = pay + r["payment_preimage"] = pay["preimage"] + r["msatoshi"] = int(pay["amount_msat"][0:-4]) + r["msatoshi_sent"] = int(pay["amount_sent_msat"][0:-4]) + # this may result in an error if it was paid previously + # our database won't allow the same payment_hash to be added twice + # this is good + fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"]) + preimage = r["payment_preimage"] + return PaymentResponse( + True, r["payment_hash"], fee_msat, preimage, None + ) + else: + return PaymentResponse(False, None, None, None, str(exc)) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: try: