Browse Source

invoice listeners support on lnd and other fixes around wallets/

atmext
fiatjaf 4 years ago
parent
commit
b3c69ad49c
  1. 5
      .env.example
  2. 1
      lnbits/extensions/lnurlp/tasks.py
  3. 11
      lnbits/wallets/base.py
  4. 56
      lnbits/wallets/clightning.py
  5. 6
      lnbits/wallets/lnbits.py
  6. 112
      lnbits/wallets/lndgrpc.py
  7. 98
      lnbits/wallets/lndrest.py
  8. 18
      lnbits/wallets/lnpay.py
  9. 6
      lnbits/wallets/lntxbot.py
  10. 46
      lnbits/wallets/opennode.py
  11. 4
      lnbits/wallets/spark.py
  12. 5
      lnbits/wallets/void.py

5
.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/

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

11
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

56
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"]

6
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 ""

112
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,
)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
) -> InvoiceResponse:
lnd_rpc = lnd_grpc.Client(
self.invoices_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,
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:
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)
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,
)
checking_id = stringify_checking_id(resp.r_hash)
payment_request = str(resp.payment_request)
return InvoiceResponse(True, checking_id, payment_request, None)
payinvoice = lnd_rpc.pay_invoice(
payment_request=bolt11,
)
ok, checking_id, fee_msat, error_message = True, None, 0, None
def pay_invoice(self, bolt11: str) -> PaymentResponse:
resp = self.admin_rpc.pay_invoice(payment_request=bolt11)
if payinvoice.payment_error:
ok, error_message = False, payinvoice.payment_error
else:
checking_id = base64.b64encode(payinvoice.payment_hash).decode("utf-8").replace("/", "_")
if resp.payment_error:
return PaymentResponse(False, "", 0, resp.payment_error)
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

98
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"]
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
data = r.json()
payment_request = data["payment_request"]
payment_hash = base64.b64decode(data["r_hash"]).hex()
checking_id = payment_hash
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)
payment_hash = r.json()["payment_hash"]
checking_id = payment_hash
return PaymentResponse(ok, checking_id, fee_msat, error_message)
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

18
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):

6
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 ""

46
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):

4
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:"):

5
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 ""

Loading…
Cancel
Save