initial scaffolding for zbd wallet

This commit is contained in:
bitkarrot 2024-02-01 16:10:15 -08:00 committed by Pavol Rusnak
parent ddab4075d2
commit d69946db8a
7 changed files with 157 additions and 1 deletions

View file

@ -28,7 +28,7 @@ PORT=5000
###################################### ######################################
# which fundingsources are allowed in the admin ui # which fundingsources are allowed in the admin ui
LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, AlbyWallet, OpenNodeWallet" LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, AlbyWallet, ZBDWallet, OpenNodeWallet"
LNBITS_BACKEND_WALLET_CLASS=VoidWallet LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities, # VoidWallet is just a fallback that works without any actual Lightning capabilities,
@ -84,6 +84,10 @@ LNPAY_WALLET_KEY=LNPAY_ADMIN_KEY
ALBY_API_ENDPOINT=https://api.getalby.com/ ALBY_API_ENDPOINT=https://api.getalby.com/
ALBY_ACCESS_TOKEN=ALBY_ACCESS_TOKEN ALBY_ACCESS_TOKEN=ALBY_ACCESS_TOKEN
# ZBDWallet
ZBD_API_ENDPOINT=https://api.zebedee.io/v0/
ZBD_API_KEY=ZBD_ACCESS_TOKEN
# OpenNodeWallet # OpenNodeWallet
OPENNODE_API_ENDPOINT=https://api.opennode.com/ OPENNODE_API_ENDPOINT=https://api.opennode.com/
OPENNODE_KEY=OPENNODE_ADMIN_KEY OPENNODE_KEY=OPENNODE_ADMIN_KEY

View file

@ -29,6 +29,7 @@ LNbits can run on top of any Lightning funding source. It currently supports the
- LNbits - LNbits
- OpenNode - OpenNode
- Alby - Alby
- ZBD
- LightningTipBot - LightningTipBot
See [LNbits manual](https://docs.lnbits.org/guide/wallets.html) for more detailed documentation about each funding source. See [LNbits manual](https://docs.lnbits.org/guide/wallets.html) for more detailed documentation about each funding source.

View file

@ -87,6 +87,14 @@ For the invoice to work you must have a publicly accessible URL in your LNbits.
- `ALBY_API_ENDPOINT`: https://api.getalby.com/ - `ALBY_API_ENDPOINT`: https://api.getalby.com/
- `ALBY_ACCESS_TOKEN`: AlbyAccessToken - `ALBY_ACCESS_TOKEN`: AlbyAccessToken
### ZBD
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate an ZBD API Key here: https://zbd.dev/docs/dashboard/projects/api
- `LNBITS_BACKEND_WALLET_CLASS`: **ZBDWallet**
- `ZBD_API_ENDPOINT`: https://api.zebedee.io/v0/
- `ZBD_API_KEY`: ZBDApiKey
### Cliche Wallet ### Cliche Wallet
- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000 - `CLICHE_ENDPOINT`: ws://127.0.0.1:12000

View file

@ -206,6 +206,10 @@ class LnPayFundingSource(LNbitsSettings):
lnpay_admin_key: Optional[str] = Field(default=None) lnpay_admin_key: Optional[str] = Field(default=None)
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): class AlbyFundingSource(LNbitsSettings):
alby_api_endpoint: Optional[str] = Field(default="https://api.getalby.com/") alby_api_endpoint: Optional[str] = Field(default="https://api.getalby.com/")
alby_access_token: Optional[str] = Field(default=None) alby_access_token: Optional[str] = Field(default=None)

View file

@ -105,6 +105,14 @@ Vue.component('lnbits-funding-sources', {
alby_access_token: 'Key' alby_access_token: 'Key'
} }
], ],
[
'ZBDWallet',
'ZBD',
{
zbd_api_endpoint: 'Endpoint',
zbd_access_token: 'Key'
}
],
[ [
'OpenNodeWallet', 'OpenNodeWallet',
'OpenNode', 'OpenNode',

View file

@ -8,6 +8,7 @@ from lnbits.settings import settings
from lnbits.wallets.base import Wallet from lnbits.wallets.base import Wallet
from .alby import AlbyWallet from .alby import AlbyWallet
from .zbd import ZBDWallet
from .cliche import ClicheWallet from .cliche import ClicheWallet
from .corelightning import CoreLightningWallet from .corelightning import CoreLightningWallet

130
lnbits/wallets/zbd.py Normal file
View file

@ -0,0 +1,130 @@
import asyncio
import hashlib
from typing import AsyncGenerator, Dict, Optional
import httpx
from loguru import logger
from lnbits.settings import settings
from .base import (
InvoiceResponse,
PaymentResponse,
PaymentStatus,
StatusResponse,
Wallet,
)
class ZBDWallet(Wallet):
"""https://zbd.dev/api-reference/"""
def __init__(self):
if not settings.zbd_api_endpoint:
raise ValueError("cannot initialize ZBDWallet: missing zbd_api_endpoint")
if not settings.zbd_api_key:
raise ValueError("cannot initialize ZBDWallet: missing zbd_api_key")
self.endpoint = self.normalize_endpoint(settings.zbd_api_endpoint)
self.auth = {
"Authorization": "Bearer " + settings.zbd_api_key,
"User-Agent": settings.user_agent,
}
self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.auth)
async def cleanup(self):
try:
await self.client.aclose()
except RuntimeError as e:
logger.warning(f"Error closing wallet connection: {e}")
async def status(self) -> StatusResponse:
try:
r = await self.client.get("/balance", 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)
async def create_invoice(
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**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 ""
r = await self.client.post(
"/invoices",
json=data,
timeout=40,
)
if r.is_error:
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"]
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
timeout=None,
)
if r.is_error:
error_message = r.json()["message"]
return PaymentResponse(False, None, None, None, error_message)
data = r.json()
checking_id = data["payment_hash"]
fee_msat = -data["fee"]
preimage = data["payment_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)
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = await self.client.get(f"/invoices/{checking_id}")
if r.is_error:
return PaymentStatus(None)
data = r.json()
statuses = {
"CREATED": None,
"SETTLED": True,
}
return PaymentStatus(statuses[data.get("state")], fee_msat=None, preimage=None)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value = await self.queue.get()
yield value
async def webhook_listener(self):
logger.error("ZBD webhook listener disabled")
return