From b3c69ad49c6683466b63cf3a4d9ffc7fa8febf29 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Fri, 2 Oct 2020 17:13:33 -0300 Subject: [PATCH] invoice listeners support on lnd and other fixes around wallets/ --- .env.example | 5 +- lnbits/extensions/lnurlp/tasks.py | 1 + lnbits/wallets/base.py | 11 ++- lnbits/wallets/clightning.py | 56 +++++++++++---- lnbits/wallets/lnbits.py | 6 +- lnbits/wallets/lndgrpc.py | 112 ++++++++++++++++-------------- lnbits/wallets/lndrest.py | 98 +++++++++++++++----------- lnbits/wallets/lnpay.py | 18 ++--- lnbits/wallets/lntxbot.py | 6 +- lnbits/wallets/opennode.py | 46 ++++++------ lnbits/wallets/spark.py | 4 +- lnbits/wallets/void.py | 5 +- 12 files changed, 217 insertions(+), 151 deletions(-) diff --git a/.env.example b/.env.example index 61815555..c67baa7b 100644 --- a/.env.example +++ b/.env.example @@ -35,24 +35,21 @@ LNBITS_ADMIN_MACAROON=LNBITS_ADMIN_MACAROON # LndWallet LND_GRPC_ENDPOINT=127.0.0.1 LND_GRPC_PORT=11009 -LND_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" +LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_ADMIN_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon" LND_INVOICE_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon" -LND_READ_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/read.macaroon" # LndRestWallet LND_REST_ENDPOINT=https://localhost:8080/ LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_ADMIN_MACAROON="HEXSTRING" LND_REST_INVOICE_MACAROON="HEXSTRING" -LND_REST_READ_MACAROON="HEXSTRING" # LNPayWallet LNPAY_API_ENDPOINT=https://lnpay.co/v1/ LNPAY_API_KEY=LNPAY_API_KEY LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY -LNPAY_READ_KEY=LNPAY_READ_KEY # LntxbotWallet LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/ diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 37d24559..49fb61b0 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -6,6 +6,7 @@ from .crud import get_pay_link_by_invoice, mark_webhook_sent async def on_invoice_paid(payment: Payment) -> None: + print(payment) islnurlp = "lnurlp" == payment.extra.get("tag") if islnurlp: pay_link = get_pay_link_by_invoice(payment.payment_hash) diff --git a/lnbits/wallets/base.py b/lnbits/wallets/base.py index 06c3979b..126283ee 100644 --- a/lnbits/wallets/base.py +++ b/lnbits/wallets/base.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import NamedTuple, Optional +from typing import NamedTuple, Optional, AsyncGenerator class InvoiceResponse(NamedTuple): @@ -43,6 +43,15 @@ class Wallet(ABC): def get_payment_status(self, checking_id: str) -> PaymentStatus: pass + @abstractmethod + def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + """ + this is an async function, but here it is noted without the 'async' + prefix because mypy has a bug identifying the signature of abstract + methods. + """ + pass + class Unsupported(Exception): pass diff --git a/lnbits/wallets/clightning.py b/lnbits/wallets/clightning.py index a8e4b1f2..37f90214 100644 --- a/lnbits/wallets/clightning.py +++ b/lnbits/wallets/clightning.py @@ -1,12 +1,12 @@ try: - from lightning import LightningRpc # type: ignore + from lightning import LightningRpc, RpcError # type: ignore except ImportError: # pragma: nocover LightningRpc = None import random from os import getenv -from typing import Optional +from typing import Optional, AsyncGenerator from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported @@ -15,26 +15,52 @@ class CLightningWallet(Wallet): if LightningRpc is None: # pragma: nocover raise ImportError("The `pylightning` library must be installed to use `CLightningWallet`.") - self.l1 = LightningRpc(getenv("CLIGHTNING_RPC")) + self.ln = LightningRpc(getenv("CLIGHTNING_RPC")) + + # check description_hash support (could be provided by a plugin) + self.supports_description_hash = False + try: + answer = self.ln.help("invoicewithdescriptionhash") + if answer["help"][0]["command"].startswith( + "invoicewithdescriptionhash msatoshi label description_hash", + ): + self.supports_description_hash = True + except: + pass + + # check last payindex so we can listen from that point on + self.last_pay_index = 0 + invoices = self.ln.listinvoices() + if len(invoices["invoices"]): + self.last_pay_index = invoices["invoices"][-1]["pay_index"] def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None ) -> InvoiceResponse: - if description_hash: - raise Unsupported("description_hash") - label = "lbl{}".format(random.random()) - r = self.l1.invoice(amount * 1000, label, memo, exposeprivatechannels=True) - ok, checking_id, payment_request, error_message = True, r["payment_hash"], r["bolt11"], None - return InvoiceResponse(ok, checking_id, payment_request, error_message) + msat = amount * 1000 + + try: + if description_hash: + if not self.supports_description_hash: + raise Unsupported("description_hash") + + r = self.ln.call("invoicewithdescriptionhash", [msat, label, memo]) + return InvoiceResponse(True, label, r["bolt11"], "") + else: + r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True) + return InvoiceResponse(True, label, r["bolt11"], "") + except RpcError as exc: + error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." + return InvoiceResponse(False, label, None, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: - r = self.l1.pay(bolt11) + r = self.ln.pay(bolt11) ok, checking_id, fee_msat, error_message = True, r["payment_hash"], r["msatoshi_sent"] - r["msatoshi"], None return PaymentResponse(ok, checking_id, fee_msat, error_message) def get_invoice_status(self, checking_id: str) -> PaymentStatus: - r = self.l1.listinvoices(checking_id) + r = self.ln.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: @@ -42,7 +68,7 @@ class CLightningWallet(Wallet): raise KeyError("supplied an invalid checking_id") def get_payment_status(self, checking_id: str) -> PaymentStatus: - r = self.l1.listpays(payment_hash=checking_id) + r = self.ln.listpays(payment_hash=checking_id) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: @@ -53,3 +79,9 @@ class CLightningWallet(Wallet): return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id") + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + while True: + paid = self.ln.waitanyinvoice(self.last_pay_index) + self.last_pay_index = paid["pay_index"] + yield paid["label"] diff --git a/lnbits/wallets/lnbits.py b/lnbits/wallets/lnbits.py index d7dfca0f..2e405ee6 100644 --- a/lnbits/wallets/lnbits.py +++ b/lnbits/wallets/lnbits.py @@ -1,5 +1,5 @@ from os import getenv -from typing import Optional, Dict +from typing import Optional, Dict, AsyncGenerator from requests import get, post from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet @@ -64,3 +64,7 @@ class LNbitsWallet(Wallet): return PaymentStatus(None) return PaymentStatus(r.json()["paid"]) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + print("lnbits does not support paid invoices stream yet") + yield "" diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index a2b836fa..eebb7541 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -5,88 +5,94 @@ except ImportError: # pragma: nocover import base64 from os import getenv -from typing import Optional, Dict +from typing import Optional, Dict, AsyncGenerator from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet +def parse_checking_id(checking_id: str) -> bytes: + return base64.b64decode( + checking_id.replace("_", "/"), + ) + + +def stringify_checking_id(r_hash: bytes) -> str: + return ( + base64.b64encode( + r_hash, + ) + .decode("utf-8") + .replace("/", "_") + ) + + class LndWallet(Wallet): def __init__(self): if lnd_grpc is None: # pragma: nocover raise ImportError("The `lnd-grpc` library must be installed to use `LndWallet`.") endpoint = getenv("LND_GRPC_ENDPOINT") - self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - self.port = getenv("LND_GRPC_PORT") - self.auth_admin = getenv("LND_ADMIN_MACAROON") - self.auth_invoice = getenv("LND_INVOICE_MACAROON") - self.auth_read = getenv("LND_READ_MACAROON") - self.auth_cert = getenv("LND_CERT") + endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint + port = getenv("LND_GRPC_PORT") + cert = getenv("LND_GRPC_CERT") or getenv("LND_CERT") + auth_admin = getenv("LND_ADMIN_MACAROON") + auth_invoices = getenv("LND_INVOICE_MACAROON") + network = getenv("LND_GRPC_NETWORK", "mainnet") + + self.admin_rpc = lnd_grpc.Client( + lnd_dir=None, + macaroon_path=auth_admin, + tls_cert_path=cert, + network=network, + grpc_host=endpoint, + grpc_port=port, + ) + + self.invoices_rpc = lnd_grpc.Client( + lnd_dir=None, + macaroon_path=auth_invoices, + tls_cert_path=cert, + network=network, + grpc_host=endpoint, + grpc_port=port, + ) def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None ) -> InvoiceResponse: - lnd_rpc = lnd_grpc.Client( - lnd_dir=None, - macaroon_path=self.auth_invoice, - tls_cert_path=self.auth_cert, - network="mainnet", - grpc_host=self.endpoint, - grpc_port=self.port, - ) - params: Dict = {"value": amount, "expiry": 600, "private": True} if description_hash: params["description_hash"] = description_hash # as bytes directly else: params["memo"] = memo or "" - lndResponse = lnd_rpc.add_invoice(**params) - decoded_hash = base64.b64encode(lndResponse.r_hash).decode("utf-8").replace("/", "_") - ok, checking_id, payment_request, error_message = True, decoded_hash, str(lndResponse.payment_request), None - return InvoiceResponse(ok, checking_id, payment_request, error_message) + resp = self.invoices_rpc.add_invoice(**params) + + checking_id = stringify_checking_id(resp.r_hash) + payment_request = str(resp.payment_request) + return InvoiceResponse(True, checking_id, payment_request, None) def pay_invoice(self, bolt11: str) -> PaymentResponse: - lnd_rpc = lnd_grpc.Client( - lnd_dir=None, - macaroon_path=self.auth_admin, - tls_cert_path=self.auth_cert, - network="mainnet", - grpc_host=self.endpoint, - grpc_port=self.port, - ) + resp = self.admin_rpc.pay_invoice(payment_request=bolt11) - payinvoice = lnd_rpc.pay_invoice( - payment_request=bolt11, - ) + if resp.payment_error: + return PaymentResponse(False, "", 0, resp.payment_error) - ok, checking_id, fee_msat, error_message = True, None, 0, None - - if payinvoice.payment_error: - ok, error_message = False, payinvoice.payment_error - else: - checking_id = base64.b64encode(payinvoice.payment_hash).decode("utf-8").replace("/", "_") - - return PaymentResponse(ok, checking_id, fee_msat, error_message) + checking_id = stringify_checking_id(resp.payment_hash) + return PaymentResponse(True, checking_id, 0, None) def get_invoice_status(self, checking_id: str) -> PaymentStatus: - - check_id = base64.b64decode(checking_id.replace("_", "/")) - print(check_id) - lnd_rpc = lnd_grpc.Client( - lnd_dir=None, - macaroon_path=self.auth_invoice, - tls_cert_path=self.auth_cert, - network="mainnet", - grpc_host=self.endpoint, - grpc_port=self.port, - ) - - for _response in lnd_rpc.subscribe_single_invoice(check_id): + r_hash = parse_checking_id(checking_id) + for _response in self.invoices_rpc.subscribe_single_invoice(r_hash): if _response.state == 1: return PaymentStatus(True) return PaymentStatus(None) def get_payment_status(self, checking_id: str) -> PaymentStatus: - return PaymentStatus(True) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + for paid in self.invoices_rpc.SubscribeInvoices(): + print("PAID", paid) + checking_id = stringify_checking_id(paid.r_hash) + yield checking_id diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index a76a8995..ef45a8f8 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -1,7 +1,10 @@ -from os import getenv -from typing import Optional, Dict +import httpx +import json import base64 -from requests import get, post +from os import getenv +from typing import Optional, Dict, AsyncGenerator + +from lnbits import bolt11 from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet @@ -12,10 +15,8 @@ class LndRestWallet(Wallet): endpoint = getenv("LND_REST_ENDPOINT") self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint - print(self.endpoint) self.auth_admin = {"Grpc-Metadata-macaroon": getenv("LND_REST_ADMIN_MACAROON")} self.auth_invoice = {"Grpc-Metadata-macaroon": getenv("LND_REST_INVOICE_MACAROON")} - self.auth_read = {"Grpc-Metadata-macaroon": getenv("LND_REST_READ_MACAROON")} self.auth_cert = getenv("LND_REST_CERT") def create_invoice( @@ -30,84 +31,97 @@ class LndRestWallet(Wallet): else: data["memo"] = memo or "" - r = post( + r = httpx.post( url=f"{self.endpoint}/v1/invoices", headers=self.auth_invoice, verify=self.auth_cert, json=data, ) - ok, checking_id, payment_request, error_message = r.ok, None, None, None + if r.is_error: + error_message = r.text + try: + error_message = r.json()["error"] + except Exception: + pass + return InvoiceResponse(False, None, None, error_message) - if r.ok: - data = r.json() - payment_request = data["payment_request"] + data = r.json() + payment_request = data["payment_request"] + payment_hash = base64.b64decode(data["r_hash"]).hex() + checking_id = payment_hash - r = get( - url=f"{self.endpoint}/v1/payreq/{payment_request}", - headers=self.auth_read, - verify=self.auth_cert, - ) - print(r) - if r.ok: - checking_id = r.json()["payment_hash"].replace("/", "_") - print(checking_id) - error_message = None - ok = True - - return InvoiceResponse(ok, checking_id, payment_request, error_message) + return InvoiceResponse(True, checking_id, payment_request, None) def pay_invoice(self, bolt11: str) -> PaymentResponse: - r = post( + r = httpx.post( url=f"{self.endpoint}/v1/channels/transactions", headers=self.auth_admin, verify=self.auth_cert, json={"payment_request": bolt11}, ) - ok, checking_id, fee_msat, error_message = r.ok, None, 0, None - r = get( - url=f"{self.endpoint}/v1/payreq/{bolt11}", - headers=self.auth_admin, - verify=self.auth_cert, - ) - if r.ok: - checking_id = r.json()["payment_hash"] - else: - error_message = r.json()["error"] + if r.is_error: + error_message = r.text + try: + error_message = r.json()["error"] + except: + pass + return PaymentResponse(False, None, 0, error_message) - return PaymentResponse(ok, checking_id, fee_msat, error_message) + payment_hash = r.json()["payment_hash"] + checking_id = payment_hash + + return PaymentResponse(True, checking_id, 0, None) def get_invoice_status(self, checking_id: str) -> PaymentStatus: checking_id = checking_id.replace("_", "/") - print(checking_id) - r = get( + r = httpx.get( url=f"{self.endpoint}/v1/invoice/{checking_id}", headers=self.auth_invoice, verify=self.auth_cert, ) - print(r.json()["settled"]) if not r or r.json()["settled"] == False: return PaymentStatus(None) return PaymentStatus(r.json()["settled"]) def get_payment_status(self, checking_id: str) -> PaymentStatus: - r = get( + r = httpx.get( url=f"{self.endpoint}/v1/payments", headers=self.auth_admin, verify=self.auth_cert, params={"include_incomplete": "True", "max_payments": "20"}, ) - if not r.ok: + if r.is_error: return PaymentStatus(None) payments = [p for p in r.json()["payments"] if p["payment_hash"] == checking_id] - print(checking_id) payment = payments[0] if payments else None - # check payment.status: https://api.lightning.community/rest/index.html?python#peersynctype + # check payment.status: + # https://api.lightning.community/rest/index.html?python#peersynctype statuses = {"UNKNOWN": None, "IN_FLIGHT": None, "SUCCEEDED": True, "FAILED": False} return PaymentStatus(statuses[payment["status"]]) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + url = self.endpoint + "/v1/invoices/subscribe" + + async with httpx.AsyncClient(timeout=None, headers=self.auth_admin, verify=self.auth_cert) as client: + async with client.stream("GET", url) as r: + print("ok") + print(r) + print(r.is_error) + print("ok") + async for line in r.aiter_lines(): + print("line", line) + try: + event = json.loads(line)["result"] + print(event) + except: + continue + + payment_hash = bolt11.decode(event["payment_request"]).payment_hash + yield payment_hash diff --git a/lnbits/wallets/lnpay.py b/lnbits/wallets/lnpay.py index 428717a6..1f1a660b 100644 --- a/lnbits/wallets/lnpay.py +++ b/lnbits/wallets/lnpay.py @@ -4,7 +4,6 @@ import httpx from os import getenv from http import HTTPStatus from typing import Optional, Dict, AsyncGenerator -from requests import get, post from quart import request from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet @@ -14,11 +13,9 @@ class LNPayWallet(Wallet): """https://docs.lnpay.co/""" def __init__(self): - endpoint = getenv("LNPAY_API_ENDPOINT") + endpoint = getenv("LNPAY_API_ENDPOINT", "https://lnpay.co/v1") self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.auth_admin = getenv("LNPAY_ADMIN_KEY") - self.auth_invoice = getenv("LNPAY_INVOICE_KEY") - self.auth_read = getenv("LNPAY_READ_KEY") self.auth_api = {"X-Api-Key": getenv("LNPAY_API_KEY")} def create_invoice( @@ -33,8 +30,8 @@ class LNPayWallet(Wallet): else: data["memo"] = memo or "" - r = post( - url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice", + r = httpx.post( + url=f"{self.endpoint}/user/wallet/{self.auth_admin}/invoice", headers=self.auth_api, json=data, ) @@ -52,7 +49,7 @@ class LNPayWallet(Wallet): return InvoiceResponse(ok, checking_id, payment_request, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: - r = post( + r = httpx.post( url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw", headers=self.auth_api, json={"payment_request": bolt11}, @@ -68,12 +65,12 @@ class LNPayWallet(Wallet): return self.get_payment_status(checking_id) def get_payment_status(self, checking_id: str) -> PaymentStatus: - r = get( + r = httpx.get( url=f"{self.endpoint}/user/lntx/{checking_id}?fields=settled", headers=self.auth_api, ) - if not r.ok: + if r.is_error: return PaymentStatus(None) statuses = {0: None, 1: True, -1: False} @@ -82,8 +79,7 @@ class LNPayWallet(Wallet): async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: self.queue: asyncio.Queue = asyncio.Queue() while True: - item = await self.queue.get() - yield item + yield await self.queue.get() self.queue.task_done() async def webhook_listener(self): diff --git a/lnbits/wallets/lntxbot.py b/lnbits/wallets/lntxbot.py index 0c419da9..a8e7527d 100644 --- a/lnbits/wallets/lntxbot.py +++ b/lnbits/wallets/lntxbot.py @@ -1,5 +1,5 @@ from os import getenv -from typing import Optional, Dict +from typing import Optional, Dict, AsyncGenerator from requests import post from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet @@ -75,3 +75,7 @@ class LntxbotWallet(Wallet): statuses = {"complete": True, "failed": False, "pending": None, "unknown": None} return PaymentStatus(statuses[r.json().get("status", "unknown")]) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + print("lntxbot does not support paid invoices stream yet") + yield "" diff --git a/lnbits/wallets/opennode.py b/lnbits/wallets/opennode.py index 2337d8c1..5cd33b6f 100644 --- a/lnbits/wallets/opennode.py +++ b/lnbits/wallets/opennode.py @@ -1,10 +1,10 @@ import json import asyncio import hmac +import httpx from http import HTTPStatus from os import getenv from typing import Optional, AsyncGenerator -from requests import get, post from quart import request, url_for from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported @@ -25,8 +25,8 @@ class OpenNodeWallet(Wallet): if description_hash: raise Unsupported("description_hash") - r = post( - url=f"{self.endpoint}/v1/charges", + r = httpx.post( + f"{self.endpoint}/v1/charges", headers=self.auth_invoice, json={ "amount": amount, @@ -34,42 +34,43 @@ class OpenNodeWallet(Wallet): "callback_url": url_for("webhook_listener", _external=True), }, ) - ok, checking_id, payment_request, error_message = r.ok, None, None, None - if r.ok: - data = r.json()["data"] - checking_id = data["id"] - payment_request = data["lightning_invoice"]["payreq"] - else: + if r.is_error: error_message = r.json()["message"] + return InvoiceResponse(False, None, None, error_message) - return InvoiceResponse(ok, checking_id, payment_request, error_message) + data = r.json()["data"] + checking_id = data["id"] + payment_request = data["lightning_invoice"]["payreq"] + return InvoiceResponse(True, checking_id, payment_request, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: - r = post(url=f"{self.endpoint}/v2/withdrawals", headers=self.auth_admin, json={"type": "ln", "address": bolt11}) - ok, checking_id, fee_msat, error_message = r.ok, None, 0, None + r = httpx.post( + f"{self.endpoint}/v2/withdrawals", headers=self.auth_admin, json={"type": "ln", "address": bolt11} + ) - if r.ok: - data = r.json()["data"] - checking_id, fee_msat = data["id"], data["fee"] * 1000 - else: + if r.is_error: error_message = r.json()["message"] + return PaymentResponse(False, None, 0, error_message) - return PaymentResponse(ok, checking_id, fee_msat, error_message) + data = r.json()["data"] + checking_id, fee_msat = data["id"] + fee_msat = data["fee"] * 1000 + return PaymentResponse(True, checking_id, fee_msat, error_message) def get_invoice_status(self, checking_id: str) -> PaymentStatus: - r = get(url=f"{self.endpoint}/v1/charge/{checking_id}", headers=self.auth_invoice) + r = httpx.get(f"{self.endpoint}/v1/charge/{checking_id}", headers=self.auth_invoice) - if not r.ok: + if r.is_error: return PaymentStatus(None) statuses = {"processing": None, "paid": True, "unpaid": False} return PaymentStatus(statuses[r.json()["data"]["status"]]) def get_payment_status(self, checking_id: str) -> PaymentStatus: - r = get(url=f"{self.endpoint}/v1/withdrawal/{checking_id}", headers=self.auth_admin) + r = httpx.get(f"{self.endpoint}/v1/withdrawal/{checking_id}", headers=self.auth_admin) - if not r.ok: + if r.is_error: return PaymentStatus(None) statuses = {"initial": None, "pending": None, "confirmed": True, "error": False, "failed": False} @@ -78,8 +79,7 @@ class OpenNodeWallet(Wallet): async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: self.queue: asyncio.Queue = asyncio.Queue() while True: - item = await self.queue.get() - yield item + yield await self.queue.get() self.queue.task_done() async def webhook_listener(self): diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py index 3c450c5c..eaca0728 100644 --- a/lnbits/wallets/spark.py +++ b/lnbits/wallets/spark.py @@ -34,7 +34,7 @@ class SparkWallet(Wallet): data = r.json() except: raise UnknownError(r.text) - if not r.ok: + if r.is_error: raise SparkError(data["message"]) return data @@ -96,7 +96,7 @@ class SparkWallet(Wallet): async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: url = self.url + "/stream?access-key=" + self.token - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(timeout=None) as client: async with client.stream("GET", url) as r: async for line in r.aiter_lines(): if line.startswith("data:"): diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py index 49f1eaee..044d7efa 100644 --- a/lnbits/wallets/void.py +++ b/lnbits/wallets/void.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, AsyncGenerator from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported @@ -17,3 +17,6 @@ class VoidWallet(Wallet): def get_payment_status(self, checking_id: str) -> PaymentStatus: raise Unsupported("") + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + yield ""