add service

This commit is contained in:
dni
2026-07-13 09:09:42 +02:00
parent 3f28d7a31a
commit 4dc2c779a2
3 changed files with 119 additions and 68 deletions
+12
View File
@@ -1,3 +1,10 @@
from .blockexplorer import (
fetch_fee_estimates,
fetch_onchain_balance,
fetch_recent_blocks,
fetch_tip,
fetch_transaction,
)
from .fiat_providers import check_fiat_status from .fiat_providers import check_fiat_status
from .funding_source import ( from .funding_source import (
get_balance_delta, get_balance_delta,
@@ -56,7 +63,12 @@ __all__ = [
"enqueue_admin_notification", "enqueue_admin_notification",
"fee_reserve", "fee_reserve",
"fee_reserve_total", "fee_reserve_total",
"fetch_fee_estimates",
"fetch_lnurl_pay_request", "fetch_lnurl_pay_request",
"fetch_onchain_balance",
"fetch_recent_blocks",
"fetch_tip",
"fetch_transaction",
"get_balance_delta", "get_balance_delta",
"get_payments_daily_stats", "get_payments_daily_stats",
"get_pr_from_lnurl", "get_pr_from_lnurl",
+92
View File
@@ -0,0 +1,92 @@
import asyncio
from lnbits.settings import settings
from lnbits.task_manager import OnchainAddressEvent
from lnbits.utils.electrum import (
AddressResponse,
Balance,
BlockHeader,
BlockInfo,
ElectrumClient,
FeeResponse,
Transaction,
network_from_name,
parse_block_header,
parse_raw_tx,
scripthash_from_address,
)
def _client() -> ElectrumClient:
return ElectrumClient(
settings.lnbits_blockexplorer_electrum_url,
network=network_from_name(settings.lnbits_blockexplorer_network),
)
async def fetch_recent_blocks(count: int = 5) -> list[BlockInfo]:
async with _client() as c:
tip = await c.get_tip()
start = max(0, tip.height - count + 1)
headers = await c.get_block_headers(start, tip.height - start + 1)
raw = bytes.fromhex(headers.hex)
blocks = [
parse_block_header(raw[i * 80 : (i + 1) * 80].hex(), start + i)
for i in range(headers.count)
]
return list(reversed(blocks))
async def fetch_tip() -> BlockHeader:
async with _client() as c:
return await c.get_tip()
async def fetch_fee_estimates() -> FeeResponse:
async with _client() as c:
estimates_raw = await asyncio.gather(
c.estimate_fee(1),
c.estimate_fee(3),
c.estimate_fee(6),
c.estimate_fee(144),
)
histogram = await c.fee_histogram()
estimates = {
str(blocks): fee
for blocks, fee in zip([1, 3, 6, 144], estimates_raw, strict=False)
if fee >= 0
}
return FeeResponse(estimates=estimates, histogram=histogram)
async def fetch_transaction(txid: str) -> Transaction:
async with _client() as c:
raw_hex = await c.get_transaction(txid)
return parse_raw_tx(raw_hex, network=c.network)
async def fetch_onchain_balance(onchain_address: str) -> AddressResponse:
scripthash = scripthash_from_address(onchain_address)
async with _client() as client:
balance_res, history_res = await asyncio.gather(
client.get_balance(scripthash),
client.get_history(scripthash),
return_exceptions=True,
)
if isinstance(balance_res, BaseException):
raise balance_res
history = [] if isinstance(history_res, BaseException) else history_res
history_error = (
str(history_res) if isinstance(history_res, BaseException) else None
)
return AddressResponse(
balance=balance_res, history=history, history_error=history_error
)
def address_event_to_response(event: OnchainAddressEvent) -> AddressResponse:
return AddressResponse(
balance=Balance(confirmed=event.confirmed, unconfirmed=event.unconfirmed),
history=event.history,
history_error=event.history_error,
)
+15 -68
View File
@@ -5,6 +5,14 @@ from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket
from pydantic.types import UUID4 from pydantic.types import UUID4
from lnbits.core.services.blockexplorer import (
address_event_to_response,
fetch_fee_estimates,
fetch_onchain_balance,
fetch_recent_blocks,
fetch_tip,
fetch_transaction,
)
from lnbits.decorators import check_access_token, check_user_exists from lnbits.decorators import check_access_token, check_user_exists
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.task_manager import ( from lnbits.task_manager import (
@@ -15,16 +23,11 @@ from lnbits.task_manager import (
) )
from lnbits.utils.electrum import ( from lnbits.utils.electrum import (
AddressResponse, AddressResponse,
Balance,
BlockHeader, BlockHeader,
BlockInfo, BlockInfo,
ElectrumClient,
ElectrumError, ElectrumError,
FeeResponse, FeeResponse,
Transaction, Transaction,
network_from_name,
parse_block_header,
parse_raw_tx,
scripthash_from_address, scripthash_from_address,
) )
@@ -52,29 +55,13 @@ async def _check_api_access(
await check_user_exists(r, access_token, usr) await check_user_exists(r, access_token, usr)
def _client() -> ElectrumClient:
return ElectrumClient(
settings.lnbits_blockexplorer_electrum_url,
network=network_from_name(settings.lnbits_blockexplorer_network),
)
# ---- REST ---- # ---- REST ----
@blockexplorer_router.get("/blocks", dependencies=[Depends(_check_api_access)]) @blockexplorer_router.get("/blocks", dependencies=[Depends(_check_api_access)])
async def api_blocks() -> list[BlockInfo]: async def api_blocks() -> list[BlockInfo]:
try: try:
async with _client() as c: return await fetch_recent_blocks()
tip = await c.get_tip()
start = max(0, tip.height - 4)
headers = await c.get_block_headers(start, tip.height - start + 1)
raw = bytes.fromhex(headers.hex)
blocks = [
parse_block_header(raw[i * 80 : (i + 1) * 80].hex(), start + i)
for i in range(headers.count)
]
return list(reversed(blocks))
except ElectrumError as e: except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e
@@ -82,8 +69,7 @@ async def api_blocks() -> list[BlockInfo]:
@blockexplorer_router.get("/tip", dependencies=[Depends(_check_api_access)]) @blockexplorer_router.get("/tip", dependencies=[Depends(_check_api_access)])
async def api_tip() -> BlockHeader: async def api_tip() -> BlockHeader:
try: try:
async with _client() as c: return await fetch_tip()
return await c.get_tip()
except ElectrumError as e: except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e
@@ -91,20 +77,7 @@ async def api_tip() -> BlockHeader:
@blockexplorer_router.get("/fees", dependencies=[Depends(_check_api_access)]) @blockexplorer_router.get("/fees", dependencies=[Depends(_check_api_access)])
async def api_fees() -> FeeResponse: async def api_fees() -> FeeResponse:
try: try:
async with _client() as c: return await fetch_fee_estimates()
estimates_raw = await asyncio.gather(
c.estimate_fee(1),
c.estimate_fee(3),
c.estimate_fee(6),
c.estimate_fee(144),
)
histogram = await c.fee_histogram()
estimates = {
str(blocks): fee
for blocks, fee in zip([1, 3, 6, 144], estimates_raw, strict=False)
if fee >= 0
}
return FeeResponse(estimates=estimates, histogram=histogram)
except ElectrumError as e: except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e
@@ -112,9 +85,7 @@ async def api_fees() -> FeeResponse:
@blockexplorer_router.get("/tx/{txid}", dependencies=[Depends(_check_api_access)]) @blockexplorer_router.get("/tx/{txid}", dependencies=[Depends(_check_api_access)])
async def api_tx(txid: str) -> Transaction: async def api_tx(txid: str) -> Transaction:
try: try:
async with _client() as c: return await fetch_transaction(txid)
raw_hex = await c.get_transaction(txid)
return parse_raw_tx(raw_hex, network=c.network)
except ElectrumError as e: except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e
@@ -124,27 +95,11 @@ async def api_tx(txid: str) -> Transaction:
) )
async def api_address(address: str) -> AddressResponse: async def api_address(address: str) -> AddressResponse:
try: try:
scripthash = scripthash_from_address(address) scripthash_from_address(address)
except ValueError as e: except ValueError as e:
raise HTTPException(HTTPStatus.BAD_REQUEST, detail=str(e)) from e raise HTTPException(HTTPStatus.BAD_REQUEST, detail=str(e)) from e
try: try:
async with _client() as c: return await fetch_onchain_balance(address)
balance_res, history_res = await asyncio.gather(
c.get_balance(scripthash),
c.get_history(scripthash),
return_exceptions=True,
)
if isinstance(balance_res, BaseException):
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(balance_res))
history = [] if isinstance(history_res, BaseException) else history_res
history_error = (
str(history_res) if isinstance(history_res, BaseException) else None
)
return AddressResponse(
balance=balance_res, history=history, history_error=history_error
)
except HTTPException:
raise
except ElectrumError as e: except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e
@@ -167,14 +122,6 @@ async def ws_blocks(websocket: WebSocket) -> None:
task_manager.unregister_ws_block_queue(queue) task_manager.unregister_ws_block_queue(queue)
def _address_event_to_response(event: OnchainAddressEvent) -> AddressResponse:
return AddressResponse(
balance=Balance(confirmed=event.confirmed, unconfirmed=event.unconfirmed),
history=event.history,
history_error=event.history_error,
)
@blockexplorer_router.websocket("/ws/address/{address}") @blockexplorer_router.websocket("/ws/address/{address}")
async def ws_address(websocket: WebSocket, address: str) -> None: async def ws_address(websocket: WebSocket, address: str) -> None:
if not settings.lnbits_blockexplorer_enabled: if not settings.lnbits_blockexplorer_enabled:
@@ -189,7 +136,7 @@ async def ws_address(websocket: WebSocket, address: str) -> None:
await websocket.close(code=1008, reason=str(e)) await websocket.close(code=1008, reason=str(e))
return return
try: try:
await relay_ws_queue(websocket, queue, serialize=_address_event_to_response) await relay_ws_queue(websocket, queue, serialize=address_event_to_response)
finally: finally:
task_manager.unregister_ws_address_queue(address, queue) task_manager.unregister_ws_address_queue(address, queue)