fix pyright lnbits/wallets
Co-authored-by: dni ⚡ <office@dnilabs.com>
This commit is contained in:
parent
0607cb1d6e
commit
9c8a79eb9b
10 changed files with 163 additions and 130 deletions
|
|
@ -22,6 +22,8 @@ class ClicheWallet(Wallet):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.endpoint = settings.cliche_endpoint
|
self.endpoint = settings.cliche_endpoint
|
||||||
|
if not self.endpoint:
|
||||||
|
raise Exception("cannot initialize cliche")
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
try:
|
try:
|
||||||
|
|
@ -36,7 +38,7 @@ class ClicheWallet(Wallet):
|
||||||
data = json.loads(r)
|
data = json.loads(r)
|
||||||
except:
|
except:
|
||||||
return StatusResponse(
|
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"])
|
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:
|
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||||
ws = create_connection(self.endpoint)
|
ws = create_connection(self.endpoint)
|
||||||
ws.send(f"pay-invoice --invoice {bolt11}")
|
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):
|
for _ in range(2):
|
||||||
r = ws.recv()
|
r = ws.recv()
|
||||||
data = json.loads(r)
|
data = json.loads(r)
|
||||||
|
|
@ -151,9 +160,9 @@ class ClicheWallet(Wallet):
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
ws = await create_connection(self.endpoint)
|
ws = create_connection(self.endpoint)
|
||||||
while True:
|
while True:
|
||||||
r = await ws.recv()
|
r = ws.recv()
|
||||||
data = json.loads(r)
|
data = json.loads(r)
|
||||||
print(data)
|
print(data)
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,7 @@ from typing import AsyncGenerator, Dict, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from websockets.client import connect
|
||||||
# TODO: https://github.com/lnbits/lnbits/issues/764
|
|
||||||
# mypy https://github.com/aaugustin/websockets/issues/940
|
|
||||||
from websockets import connect # type: ignore
|
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
@ -34,11 +31,13 @@ class UnknownError(Exception):
|
||||||
class EclairWallet(Wallet):
|
class EclairWallet(Wallet):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
url = settings.eclair_url
|
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"
|
self.ws_url = f"ws://{urllib.parse.urlsplit(self.url).netloc}/ws"
|
||||||
|
|
||||||
passw = settings.eclair_pass
|
|
||||||
encodedAuth = base64.b64encode(f":{passw}".encode())
|
encodedAuth = base64.b64encode(f":{passw}".encode())
|
||||||
auth = str(encodedAuth, "utf-8")
|
auth = str(encodedAuth, "utf-8")
|
||||||
self.auth = {"Authorization": f"Basic {auth}"}
|
self.auth = {"Authorization": f"Basic {auth}"}
|
||||||
|
|
@ -71,7 +70,11 @@ class EclairWallet(Wallet):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
|
|
||||||
data: Dict = {"amountMsat": amount * 1000}
|
data: Dict = {
|
||||||
|
"amountMsat": amount * 1000,
|
||||||
|
"description_hash": b"",
|
||||||
|
"description": memo,
|
||||||
|
}
|
||||||
if kwargs.get("expiry"):
|
if kwargs.get("expiry"):
|
||||||
data["expireIn"] = kwargs["expiry"]
|
data["expireIn"] = kwargs["expiry"]
|
||||||
|
|
||||||
|
|
@ -79,8 +82,6 @@ class EclairWallet(Wallet):
|
||||||
data["descriptionHash"] = description_hash.hex()
|
data["descriptionHash"] = description_hash.hex()
|
||||||
elif unhashed_description:
|
elif unhashed_description:
|
||||||
data["descriptionHash"] = hashlib.sha256(unhashed_description).hexdigest()
|
data["descriptionHash"] = hashlib.sha256(unhashed_description).hexdigest()
|
||||||
else:
|
|
||||||
data["description"] = memo or ""
|
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
|
|
@ -149,6 +150,7 @@ class EclairWallet(Wallet):
|
||||||
}
|
}
|
||||||
|
|
||||||
data = r.json()[-1]
|
data = r.json()[-1]
|
||||||
|
fee_msat = 0
|
||||||
if data["status"]["type"] == "sent":
|
if data["status"]["type"] == "sent":
|
||||||
fee_msat = -data["status"]["feesPaid"]
|
fee_msat = -data["status"]["feesPaid"]
|
||||||
preimage = data["status"]["paymentPreimage"]
|
preimage = data["status"]["paymentPreimage"]
|
||||||
|
|
@ -223,10 +225,10 @@ class EclairWallet(Wallet):
|
||||||
) as ws:
|
) as ws:
|
||||||
while True:
|
while True:
|
||||||
message = await ws.recv()
|
message = await ws.recv()
|
||||||
message = json.loads(message)
|
message_json = json.loads(message)
|
||||||
|
|
||||||
if message and message["type"] == "payment-received":
|
if message_json and message_json["type"] == "payment-received":
|
||||||
yield message["paymentHash"]
|
yield message_json["paymentHash"]
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
|
||||||
|
|
@ -45,19 +45,18 @@ class FakeWallet(Wallet):
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: Dict = {
|
data: Dict = {
|
||||||
"out": False,
|
"out": False,
|
||||||
"amount": amount,
|
"amount": amount * 1000,
|
||||||
"currency": "bc",
|
"currency": "bc",
|
||||||
"privkey": self.privkey,
|
"privkey": self.privkey,
|
||||||
"memo": None,
|
"memo": memo,
|
||||||
"description_hash": None,
|
"description_hash": b"",
|
||||||
"description": "",
|
"description": "",
|
||||||
"fallback": None,
|
"fallback": None,
|
||||||
"expires": None,
|
"expires": kwargs.get("expiry"),
|
||||||
|
"timestamp": datetime.now().timestamp(),
|
||||||
"route": None,
|
"route": None,
|
||||||
|
"tags_set": [],
|
||||||
}
|
}
|
||||||
data["expires"] = kwargs.get("expiry")
|
|
||||||
data["amount"] = amount * 1000
|
|
||||||
data["timestamp"] = datetime.now().timestamp()
|
|
||||||
if description_hash:
|
if description_hash:
|
||||||
data["tags_set"] = ["h"]
|
data["tags_set"] = ["h"]
|
||||||
data["description_hash"] = description_hash
|
data["description_hash"] = description_hash
|
||||||
|
|
@ -69,7 +68,7 @@ class FakeWallet(Wallet):
|
||||||
data["memo"] = memo
|
data["memo"] = memo
|
||||||
data["description"] = memo
|
data["description"] = memo
|
||||||
randomHash = (
|
randomHash = (
|
||||||
data["privkey"][:6]
|
self.privkey[:6]
|
||||||
+ hashlib.sha256(str(random.getrandbits(256)).encode()).hexdigest()[6:]
|
+ hashlib.sha256(str(random.getrandbits(256)).encode()).hexdigest()[6:]
|
||||||
)
|
)
|
||||||
data["paymenthash"] = randomHash
|
data["paymenthash"] = randomHash
|
||||||
|
|
@ -78,12 +77,10 @@ class FakeWallet(Wallet):
|
||||||
|
|
||||||
return InvoiceResponse(True, checking_id, payment_request)
|
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)
|
invoice = decode(bolt11)
|
||||||
if (
|
|
||||||
hasattr(invoice, "checking_id")
|
if invoice.checking_id and invoice.checking_id[:6] == self.privkey[:6]:
|
||||||
and invoice.checking_id[:6] == self.privkey[:6] # type: ignore
|
|
||||||
):
|
|
||||||
await self.queue.put(invoice)
|
await self.queue.put(invoice)
|
||||||
return PaymentResponse(True, invoice.payment_hash, 0)
|
return PaymentResponse(True, invoice.payment_hash, 0)
|
||||||
else:
|
else:
|
||||||
|
|
@ -91,10 +88,10 @@ class FakeWallet(Wallet):
|
||||||
ok=False, error_message="Only internal invoices can be used!"
|
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)
|
return PaymentStatus(None)
|
||||||
|
|
||||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
async def get_payment_status(self, _: str) -> PaymentStatus:
|
||||||
return PaymentStatus(None)
|
return PaymentStatus(None)
|
||||||
|
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,13 @@ class LNbitsWallet(Wallet):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.endpoint = settings.lnbits_endpoint
|
self.endpoint = settings.lnbits_endpoint
|
||||||
|
|
||||||
key = (
|
key = (
|
||||||
settings.lnbits_key
|
settings.lnbits_key
|
||||||
or settings.lnbits_admin_key
|
or settings.lnbits_admin_key
|
||||||
or settings.lnbits_invoice_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}
|
self.key = {"X-Api-Key": key}
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
|
|
@ -60,7 +61,7 @@ class LNbitsWallet(Wallet):
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: Optional[bytes] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: Dict = {"out": False, "amount": amount}
|
data: Dict = {"out": False, "amount": amount, "memo": memo or ""}
|
||||||
if kwargs.get("expiry"):
|
if kwargs.get("expiry"):
|
||||||
data["expiry"] = kwargs["expiry"]
|
data["expiry"] = kwargs["expiry"]
|
||||||
if description_hash:
|
if description_hash:
|
||||||
|
|
@ -68,8 +69,6 @@ class LNbitsWallet(Wallet):
|
||||||
if unhashed_description:
|
if unhashed_description:
|
||||||
data["unhashed_description"] = unhashed_description.hex()
|
data["unhashed_description"] = unhashed_description.hex()
|
||||||
|
|
||||||
data["memo"] = memo or ""
|
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
url=f"{self.endpoint}/api/v1/payments", headers=self.key, json=data
|
url=f"{self.endpoint}/api/v1/payments", headers=self.key, json=data
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,6 @@ class LndWallet(Wallet):
|
||||||
)
|
)
|
||||||
|
|
||||||
endpoint = settings.lnd_grpc_endpoint
|
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 = (
|
macaroon = (
|
||||||
settings.lnd_grpc_macaroon
|
settings.lnd_grpc_macaroon
|
||||||
|
|
@ -122,8 +119,17 @@ class LndWallet(Wallet):
|
||||||
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
||||||
encrypted_macaroon
|
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()
|
cert = open(self.cert_path, "rb").read()
|
||||||
creds = grpc.ssl_channel_credentials(cert)
|
creds = grpc.ssl_channel_credentials(cert)
|
||||||
auth_creds = grpc.metadata_call_credentials(self.metadata_callback)
|
auth_creds = grpc.metadata_call_credentials(self.metadata_callback)
|
||||||
|
|
@ -140,8 +146,6 @@ class LndWallet(Wallet):
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
try:
|
try:
|
||||||
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
|
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
|
||||||
except RpcError as exc:
|
|
||||||
return StatusResponse(str(exc._details), 0)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return StatusResponse(str(exc), 0)
|
return StatusResponse(str(exc), 0)
|
||||||
|
|
||||||
|
|
@ -155,20 +159,23 @@ class LndWallet(Wallet):
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: Optional[bytes] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
params: Dict = {"value": amount, "private": True}
|
data: Dict = {
|
||||||
|
"description_hash": b"",
|
||||||
|
"value": amount,
|
||||||
|
"private": True,
|
||||||
|
"memo": memo or "",
|
||||||
|
}
|
||||||
if kwargs.get("expiry"):
|
if kwargs.get("expiry"):
|
||||||
params["expiry"] = kwargs["expiry"]
|
data["expiry"] = kwargs["expiry"]
|
||||||
if description_hash:
|
if description_hash:
|
||||||
params["description_hash"] = description_hash
|
data["description_hash"] = description_hash
|
||||||
elif unhashed_description:
|
elif unhashed_description:
|
||||||
params["description_hash"] = hashlib.sha256(
|
data["description_hash"] = hashlib.sha256(
|
||||||
unhashed_description
|
unhashed_description
|
||||||
).digest() # as bytes directly
|
).digest() # as bytes directly
|
||||||
else:
|
|
||||||
params["memo"] = memo or ""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = ln.Invoice(**params)
|
req = ln.Invoice(**data)
|
||||||
resp = await self.rpc.AddInvoice(req)
|
resp = await self.rpc.AddInvoice(req)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
error_message = str(exc)
|
error_message = str(exc)
|
||||||
|
|
@ -188,8 +195,6 @@ class LndWallet(Wallet):
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
resp = await self.routerpc.SendPaymentV2(req).read()
|
resp = await self.routerpc.SendPaymentV2(req).read()
|
||||||
except RpcError as exc:
|
|
||||||
return PaymentResponse(False, None, None, None, exc._details)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return PaymentResponse(False, None, None, None, str(exc))
|
return PaymentResponse(False, None, None, None, str(exc))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,15 @@ class LndRestWallet(Wallet):
|
||||||
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
||||||
encrypted_macaroon
|
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.macaroon = load_macaroon(macaroon)
|
||||||
|
|
||||||
self.auth = {"Grpc-Metadata-macaroon": self.macaroon}
|
self.auth = {"Grpc-Metadata-macaroon": self.macaroon}
|
||||||
|
|
@ -74,7 +83,7 @@ class LndRestWallet(Wallet):
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: Optional[bytes] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: Dict = {"value": amount, "private": True}
|
data: Dict = {"value": amount, "private": True, "memo": memo or ""}
|
||||||
if kwargs.get("expiry"):
|
if kwargs.get("expiry"):
|
||||||
data["expiry"] = kwargs["expiry"]
|
data["expiry"] = kwargs["expiry"]
|
||||||
if description_hash:
|
if description_hash:
|
||||||
|
|
@ -85,8 +94,6 @@ class LndRestWallet(Wallet):
|
||||||
data["description_hash"] = base64.b64encode(
|
data["description_hash"] = base64.b64encode(
|
||||||
hashlib.sha256(unhashed_description).digest()
|
hashlib.sha256(unhashed_description).digest()
|
||||||
).decode("ascii")
|
).decode("ascii")
|
||||||
else:
|
|
||||||
data["memo"] = memo or ""
|
|
||||||
|
|
||||||
async with httpx.AsyncClient(verify=self.cert) as client:
|
async with httpx.AsyncClient(verify=self.cert) as client:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import AsyncGenerator, Dict, Optional
|
from typing import AsyncGenerator, Dict, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi.exceptions import HTTPException
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
@ -24,8 +20,13 @@ class LNPayWallet(Wallet):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
endpoint = settings.lnpay_api_endpoint
|
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.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}
|
self.auth = {"X-Api-Key": settings.lnpay_api_key}
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
|
|
@ -54,7 +55,6 @@ class LNPayWallet(Wallet):
|
||||||
memo: Optional[str] = None,
|
memo: Optional[str] = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: Optional[bytes] = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: Optional[bytes] = None,
|
||||||
**kwargs,
|
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: Dict = {"num_satoshis": f"{amount}"}
|
data: Dict = {"num_satoshis": f"{amount}"}
|
||||||
if description_hash:
|
if description_hash:
|
||||||
|
|
@ -133,27 +133,27 @@ class LNPayWallet(Wallet):
|
||||||
value = await self.queue.get()
|
value = await self.queue.get()
|
||||||
yield value
|
yield value
|
||||||
|
|
||||||
async def webhook_listener(self):
|
# async def webhook_listener(self):
|
||||||
text: str = await request.get_data()
|
# text: str = await request.get_data()
|
||||||
try:
|
# try:
|
||||||
data = json.loads(text)
|
# data = json.loads(text)
|
||||||
except json.decoder.JSONDecodeError:
|
# except json.decoder.JSONDecodeError:
|
||||||
logger.error(f"got something wrong on lnpay webhook endpoint: {text[:200]}")
|
# logger.error(f"got something wrong on lnpay webhook endpoint: {text[:200]}")
|
||||||
data = None
|
# data = None
|
||||||
if (
|
# if (
|
||||||
type(data) is not dict
|
# type(data) is not dict
|
||||||
or "event" not in data
|
# or "event" not in data
|
||||||
or data["event"].get("name") != "wallet_receive"
|
# or data["event"].get("name") != "wallet_receive"
|
||||||
):
|
# ):
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
lntx_id = data["data"]["wtx"]["lnTx"]["id"]
|
# lntx_id = data["data"]["wtx"]["lnTx"]["id"]
|
||||||
async with httpx.AsyncClient() as client:
|
# async with httpx.AsyncClient() as client:
|
||||||
r = await client.get(
|
# r = await client.get(
|
||||||
f"{self.endpoint}/lntx/{lntx_id}?fields=settled", headers=self.auth
|
# f"{self.endpoint}/lntx/{lntx_id}?fields=settled", headers=self.auth
|
||||||
)
|
# )
|
||||||
data = r.json()
|
# data = r.json()
|
||||||
if data["settled"]:
|
# if data["settled"]:
|
||||||
await self.queue.put(lntx_id)
|
# await self.queue.put(lntx_id)
|
||||||
|
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,14 @@ from .base import (
|
||||||
class LnTipsWallet(Wallet):
|
class LnTipsWallet(Wallet):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
endpoint = settings.lntips_api_endpoint
|
endpoint = settings.lntips_api_endpoint
|
||||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
|
||||||
|
|
||||||
key = (
|
key = (
|
||||||
settings.lntips_api_key
|
settings.lntips_api_key
|
||||||
or settings.lntips_admin_key
|
or settings.lntips_admin_key
|
||||||
or settings.lntips_invoice_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}"}
|
self.auth = {"Authorization": f"Basic {key}"}
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
|
|
@ -53,15 +54,12 @@ class LnTipsWallet(Wallet):
|
||||||
memo: Optional[str] = None,
|
memo: Optional[str] = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: Optional[bytes] = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: Optional[bytes] = None,
|
||||||
**kwargs,
|
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
data: Dict = {"amount": amount}
|
data: Dict = {"amount": amount, "description_hash": "", "memo": memo or ""}
|
||||||
if description_hash:
|
if description_hash:
|
||||||
data["description_hash"] = description_hash.hex()
|
data["description_hash"] = description_hash.hex()
|
||||||
elif unhashed_description:
|
elif unhashed_description:
|
||||||
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
|
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
|
||||||
else:
|
|
||||||
data["memo"] = memo or ""
|
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import hmac
|
|
||||||
from http import HTTPStatus
|
# import hmac
|
||||||
|
# from http import HTTPStatus
|
||||||
from typing import AsyncGenerator, Optional
|
from typing import AsyncGenerator, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi.exceptions import HTTPException
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
@ -18,19 +17,24 @@ from .base import (
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# from fastapi import Request, HTTPException
|
||||||
|
# from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
class OpenNodeWallet(Wallet):
|
class OpenNodeWallet(Wallet):
|
||||||
"""https://developers.opennode.com/"""
|
"""https://developers.opennode.com/"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
endpoint = settings.opennode_api_endpoint
|
endpoint = settings.opennode_api_endpoint
|
||||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
|
||||||
|
|
||||||
key = (
|
key = (
|
||||||
settings.opennode_key
|
settings.opennode_key
|
||||||
or settings.opennode_admin_key
|
or settings.opennode_admin_key
|
||||||
or settings.opennode_invoice_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}
|
self.auth = {"Authorization": key}
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
|
|
@ -54,7 +58,6 @@ class OpenNodeWallet(Wallet):
|
||||||
memo: Optional[str] = None,
|
memo: Optional[str] = None,
|
||||||
description_hash: Optional[bytes] = None,
|
description_hash: Optional[bytes] = None,
|
||||||
unhashed_description: Optional[bytes] = None,
|
unhashed_description: Optional[bytes] = None,
|
||||||
**kwargs,
|
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
if description_hash or unhashed_description:
|
if description_hash or unhashed_description:
|
||||||
raise Unsupported("description_hash")
|
raise Unsupported("description_hash")
|
||||||
|
|
@ -139,17 +142,17 @@ class OpenNodeWallet(Wallet):
|
||||||
value = await self.queue.get()
|
value = await self.queue.get()
|
||||||
yield value
|
yield value
|
||||||
|
|
||||||
async def webhook_listener(self):
|
# async def webhook_listener(self):
|
||||||
data = await request.form
|
# data = await request.form
|
||||||
if "status" not in data or data["status"] != "paid":
|
# if "status" not in data or data["status"] != "paid":
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
charge_id = data["id"]
|
# charge_id = data["id"]
|
||||||
x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256")
|
# x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256")
|
||||||
x.update(charge_id.encode("ascii"))
|
# x.update(charge_id.encode("ascii"))
|
||||||
if x.hexdigest() != data["hashed_order"]:
|
# if x.hexdigest() != data["hashed_order"]:
|
||||||
logger.error("invalid webhook, not from opennode")
|
# logger.error("invalid webhook, not from opennode")
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
await self.queue.put(charge_id)
|
# await self.queue.put(charge_id)
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ class UnknownError(Exception):
|
||||||
|
|
||||||
class SparkWallet(Wallet):
|
class SparkWallet(Wallet):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
assert settings.spark_url
|
||||||
self.url = settings.spark_url.replace("/rpc", "")
|
self.url = settings.spark_url.replace("/rpc", "")
|
||||||
self.token = settings.spark_token
|
self.token = settings.spark_token
|
||||||
|
|
||||||
|
|
@ -46,6 +47,7 @@ class SparkWallet(Wallet):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
|
assert self.token
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
self.url + "/rpc",
|
self.url + "/rpc",
|
||||||
headers={"X-Access": self.token},
|
headers={"X-Access": self.token},
|
||||||
|
|
@ -133,38 +135,49 @@ class SparkWallet(Wallet):
|
||||||
bolt11=bolt11,
|
bolt11=bolt11,
|
||||||
maxfee=fee_limit_msat,
|
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:
|
except (SparkError, UnknownError) as exc:
|
||||||
listpays = await self.listpays(bolt11)
|
listpays = await self.listpays(bolt11)
|
||||||
if listpays:
|
if not listpays:
|
||||||
pays = listpays["pays"]
|
return PaymentResponse(False, None, None, None, str(exc))
|
||||||
|
|
||||||
if len(pays) == 0:
|
pays = listpays["pays"]
|
||||||
return PaymentResponse(False, None, None, None, str(exc))
|
|
||||||
|
|
||||||
pay = pays[0]
|
if len(pays) == 0:
|
||||||
payment_hash = pay["payment_hash"]
|
return PaymentResponse(False, None, None, None, str(exc))
|
||||||
|
|
||||||
if len(pays) > 1:
|
pay = pays[0]
|
||||||
raise SparkError(
|
payment_hash = pay["payment_hash"]
|
||||||
f"listpays({payment_hash}) returned an unexpected response: {listpays}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if pay["status"] == "failed":
|
if len(pays) > 1:
|
||||||
return PaymentResponse(False, None, None, None, str(exc))
|
raise SparkError(
|
||||||
elif pay["status"] == "pending":
|
f"listpays({payment_hash}) returned an unexpected response: {listpays}"
|
||||||
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"])
|
if pay["status"] == "failed":
|
||||||
preimage = r["payment_preimage"]
|
return PaymentResponse(False, None, None, None, str(exc))
|
||||||
return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None)
|
|
||||||
|
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:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue