From d20a35eddc0c15b0559d5661eba3b7e8376fb35a Mon Sep 17 00:00:00 2001 From: bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:36:17 -0800 Subject: [PATCH] add zbd to settings update api methods add zbd as funding source to settings and js fix statuses cast str into int for status method fix outbound payment hash issue restore create_invoice remove print stmts --- lnbits/settings.py | 3 + .../js/components/lnbits-funding-sources.js | 2 +- lnbits/wallets/__init__.py | 2 +- lnbits/wallets/zbd.py | 100 +++++++++++++----- 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/lnbits/settings.py b/lnbits/settings.py index 976718f37..b977904a5 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -210,6 +210,7 @@ class ZBDFundingSource(LNbitsSettings): zbd_api_endpoint: Optional[str] = Field(default="https://api.zebedee.io/v0/") zbd_api_key: Optional[str] = Field(default=None) + class AlbyFundingSource(LNbitsSettings): alby_api_endpoint: Optional[str] = Field(default="https://api.getalby.com/") alby_access_token: Optional[str] = Field(default=None) @@ -249,6 +250,7 @@ class FundingSourcesSettings( LndGrpcFundingSource, LnPayFundingSource, AlbyFundingSource, + ZBDFundingSource, OpenNodeFundingSource, SparkFundingSource, LnTipsFundingSource, @@ -403,6 +405,7 @@ class SuperUserSettings(LNbitsSettings): "LnTipsWallet", "LNPayWallet", "AlbyWallet", + "ZBDWallet", "LNbitsWallet", "OpenNodeWallet", ] diff --git a/lnbits/static/js/components/lnbits-funding-sources.js b/lnbits/static/js/components/lnbits-funding-sources.js index 668518a30..b83679bc4 100644 --- a/lnbits/static/js/components/lnbits-funding-sources.js +++ b/lnbits/static/js/components/lnbits-funding-sources.js @@ -110,7 +110,7 @@ Vue.component('lnbits-funding-sources', { 'ZBD', { zbd_api_endpoint: 'Endpoint', - zbd_access_token: 'Key' + zbd_api_key: 'Key' } ], [ diff --git a/lnbits/wallets/__init__.py b/lnbits/wallets/__init__.py index 3ac7d8491..6a4bbe102 100644 --- a/lnbits/wallets/__init__.py +++ b/lnbits/wallets/__init__.py @@ -8,7 +8,6 @@ from lnbits.settings import settings from lnbits.wallets.base import Wallet from .alby import AlbyWallet -from .zbd import ZBDWallet from .cliche import ClicheWallet from .corelightning import CoreLightningWallet @@ -26,6 +25,7 @@ from .lntips import LnTipsWallet from .opennode import OpenNodeWallet from .spark import SparkWallet from .void import VoidWallet +from .zbd import ZBDWallet def set_wallet_class(class_name: Optional[str] = None): diff --git a/lnbits/wallets/zbd.py b/lnbits/wallets/zbd.py index 2e108762a..ba1dfafb5 100644 --- a/lnbits/wallets/zbd.py +++ b/lnbits/wallets/zbd.py @@ -1,17 +1,18 @@ import asyncio -import hashlib from typing import AsyncGenerator, Dict, Optional import httpx from loguru import logger from lnbits.settings import settings +from lnbits.wallets.base import PaymentStatus from .base import ( InvoiceResponse, PaymentResponse, PaymentStatus, StatusResponse, + Unsupported, Wallet, ) @@ -27,7 +28,7 @@ class ZBDWallet(Wallet): self.endpoint = self.normalize_endpoint(settings.zbd_api_endpoint) self.auth = { - "Authorization": "Bearer " + settings.zbd_api_key, + "apikey": settings.zbd_api_key, "User-Agent": settings.user_agent, } self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.auth) @@ -40,16 +41,18 @@ class ZBDWallet(Wallet): async def status(self) -> StatusResponse: try: - r = await self.client.get("/balance", timeout=10) + r = await self.client.get("wallet", timeout=10) except (httpx.ConnectError, httpx.RequestError): return StatusResponse(f"Unable to connect to '{self.endpoint}'", 0) if r.is_error: error_message = r.json()["message"] return StatusResponse(error_message, 0) - data = r.json()["balance"] - # if no error, multiply balance by 1000 for msats representation in lnbits - return StatusResponse(None, data * 1000) + + data = int(r.json()["data"]["balance"]) + # ZBD returns everything as a str not int + # balance is returned in msats already in ZBD + return StatusResponse(None, data) async def create_invoice( self, @@ -60,16 +63,20 @@ class ZBDWallet(Wallet): **kwargs, ) -> InvoiceResponse: # https://api.zebedee.io/v0/charges - data: Dict = {"amount": f"{amount}"} - 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 "" + if description_hash or unhashed_description: + raise Unsupported("description_hash") + + msats_amount = amount * 1000 + data: Dict = { + "amount": f"{msats_amount}", + "description": memo, + "expiresIn": 3600, + "callbackUrl": "", + "internalId": "", + } r = await self.client.post( - "/invoices", + "charges", json=data, timeout=40, ) @@ -78,16 +85,22 @@ class ZBDWallet(Wallet): error_message = r.json()["message"] return InvoiceResponse(False, None, None, error_message) - data = r.json() - checking_id = data["payment_hash"] - payment_request = data["payment_request"] + data = r.json()["data"] + checking_id = data["id"] # this is a zbd id + payment_request = data["invoice"]["request"] return InvoiceResponse(True, checking_id, payment_request, None) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: # https://api.zebedee.io/v0/payments r = await self.client.post( - "/payments/bolt11", - json={"invoice": bolt11}, # assume never need amount in body + "payments", + json={ + "invoice": bolt11, + "description": "", + "amount": "", + "internalId": "", + "callbackUrl": "", + }, timeout=None, ) @@ -96,28 +109,57 @@ class ZBDWallet(Wallet): return PaymentResponse(False, None, None, None, error_message) data = r.json() - checking_id = data["payment_hash"] - fee_msat = -data["fee"] - preimage = data["payment_preimage"] + + # get the payment hash from the zbd api + decoded_request = await self.client.post( + "decode-invoice", + json={"invoice": bolt11}, + timeout=40, + ) + if decoded_request.is_error: + error_message = decoded_request.json()["message"] + return InvoiceResponse(False, None, None, error_message) + + decoded_data = decoded_request.json() + + checking_id = decoded_data["data"]["paymentHash"] + fee_msat = -int(data["data"]["fee"]) + preimage = data["data"]["preimage"] return PaymentResponse(True, checking_id, fee_msat, preimage, None) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: - return await self.get_payment_status(checking_id) + r = await self.client.get(f"charges/{checking_id}") + if r.is_error: + return PaymentStatus(None) + data = r.json()["data"] + + statuses = { + "pending": None, + "paid": True, + "unpaid": None, + "expired": False, + "completed": True, + } + return PaymentStatus(statuses[data.get("status")]) async def get_payment_status(self, checking_id: str) -> PaymentStatus: - r = await self.client.get(f"/invoices/{checking_id}") - + r = await self.client.get(f"payments/{checking_id}") if r.is_error: return PaymentStatus(None) - data = r.json() + data = r.json()["data"] statuses = { - "CREATED": None, - "SETTLED": True, + "initial": None, + "pending": None, + "completed": True, + "error": None, + "expired": False, + "failed": False, } - return PaymentStatus(statuses[data.get("state")], fee_msat=None, preimage=None) + + return PaymentStatus(statuses[data.get("status")], fee_msat=None, preimage=None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: self.queue: asyncio.Queue = asyncio.Queue(0)