fix pyright lnbits/wallets

Co-authored-by: dni  <office@dnilabs.com>
This commit is contained in:
Pavol Rusnak 2023-02-02 12:57:36 +00:00 committed by dni ⚡
parent 0607cb1d6e
commit 9c8a79eb9b
No known key found for this signature in database
GPG Key ID: 886317704CC4E618
10 changed files with 163 additions and 130 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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