lnbits-legend/lnbits/wallets/cliche.py
calle 2ee10e28c5
Backend: Unstuck outgoing payments (#857)
* first attempts

* lndrest works

* fix details

* optional fee update

* use base64.urlsafe_b64encode

* return paymentstatus

* CLN: return pending for pending invoices

* grpc wip

* lndgrpc works

* cln: return pending for pending invoices really this time

* retry wallet out of exception

* wip eclair

* take all routines into try except

* cliche: return error

* rename payment.check_pending() to payment.check_status()

* rename payment.check_pending() to payment.check_status()

* eclair: works

* eclair: better error check

* opennode: works

* check payment.checking_id istead of payment.ok

* payment.ok check as well

* cln: works?

* cln: works

* lntxbot: works

* lnbits/wallets/lnpay.py

* cln: error handling

* make format

* lndhub full detail update

* spark: wip

* error to False

* wallets: return clean PaymentResponse

* opennode: strict error

* cliche: works

* lnbits: works

* cln: dont throw error

* preimage not error

* fix cln

* do not add duplicate payments

* revert cln

* extra safety for cln

* undo crud changes until tests work

* tasks: better logging and 0.5s sleep for regular status check

* 0.1 s

* check if wallet exists

* lnbits unhashed description

* remove sleep

* revert app.py

* cleanup

* add check

* clean error

* readd app.py

* fix eclaid
2022-08-30 13:28:58 +02:00

168 lines
5.7 KiB
Python

import asyncio
import hashlib
import json
from os import getenv
from typing import AsyncGenerator, Dict, Optional
import httpx
from loguru import logger
from websocket import create_connection
from .base import (
InvoiceResponse,
PaymentResponse,
PaymentStatus,
StatusResponse,
Wallet,
)
class ClicheWallet(Wallet):
"""https://github.com/fiatjaf/cliche"""
def __init__(self):
self.endpoint = getenv("CLICHE_ENDPOINT")
async def status(self) -> StatusResponse:
try:
ws = create_connection(self.endpoint)
ws.send("get-info")
r = ws.recv()
except Exception as exc:
return StatusResponse(
f"Failed to connect to {self.endpoint} due to: {exc}", 0
)
try:
data = json.loads(r)
except:
return StatusResponse(
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
)
return StatusResponse(None, data["result"]["wallets"][0]["balance"])
async def create_invoice(
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
) -> InvoiceResponse:
if unhashed_description or description_hash:
description_hash_str = (
description_hash.hex()
if description_hash
else hashlib.sha256(unhashed_description).hexdigest()
if unhashed_description
else None
)
ws = create_connection(self.endpoint)
ws.send(
f"create-invoice --msatoshi {amount*1000} --description_hash {description_hash_str}"
)
r = ws.recv()
else:
ws = create_connection(self.endpoint)
ws.send(f"create-invoice --msatoshi {amount*1000} --description {memo}")
r = ws.recv()
data = json.loads(r)
checking_id = None
payment_request = None
error_message = None
if data.get("error") is not None and data["error"].get("message"):
logger.error(data["error"]["message"])
error_message = data["error"]["message"]
return InvoiceResponse(False, checking_id, payment_request, error_message)
if data.get("result") is not None:
checking_id, payment_request = (
data["result"]["payment_hash"],
data["result"]["invoice"],
)
else:
return InvoiceResponse(False, None, None, "Could not get payment hash")
return InvoiceResponse(True, checking_id, payment_request, error_message)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
ws = create_connection(self.endpoint)
ws.send(f"pay-invoice --invoice {bolt11}")
for _ in range(2):
r = ws.recv()
data = json.loads(r)
checking_id, fee_msat, preimage, error_message, payment_ok = (
None,
None,
None,
None,
None,
)
if data.get("error") is not None:
error_message = data["error"].get("message")
return PaymentResponse(False, None, None, None, error_message)
if data.get("method") == "payment_succeeded":
payment_ok = True
checking_id = data["params"]["payment_hash"]
fee_msat = data["params"]["fee_msatoshi"]
preimage = data["params"]["preimage"]
continue
if data.get("result") is None:
return PaymentResponse(None)
return PaymentResponse(
payment_ok, checking_id, fee_msat, preimage, error_message
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
ws = create_connection(self.endpoint)
ws.send(f"check-payment --hash {checking_id}")
r = ws.recv()
data = json.loads(r)
if data.get("error") is not None and data["error"].get("message"):
logger.error(data["error"]["message"])
return PaymentStatus(None)
statuses = {"pending": None, "complete": True, "failed": False}
return PaymentStatus(statuses[data["result"]["status"]])
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
ws = create_connection(self.endpoint)
ws.send(f"check-payment --hash {checking_id}")
r = ws.recv()
data = json.loads(r)
if data.get("error") is not None and data["error"].get("message"):
logger.error(data["error"]["message"])
return PaymentStatus(None)
payment = data["result"]
statuses = {"pending": None, "complete": True, "failed": False}
return PaymentStatus(
statuses[payment["status"]],
payment.get("fee_msatoshi"),
payment.get("preimage"),
)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while True:
try:
ws = await create_connection(self.endpoint)
while True:
r = await ws.recv()
data = json.loads(r)
print(data)
try:
if data["result"]["status"]:
yield data["result"]["payment_hash"]
except:
continue
except Exception as exc:
logger.error(
f"lost connection to cliche's invoices stream: '{exc}', retrying in 5 seconds"
)
await asyncio.sleep(5)
continue