feat: adds blockexplorer api, frontend via electrum

- settings
- frontend page component
- api
- models

models
This commit is contained in:
dni
2026-07-13 09:09:41 +02:00
parent cabb58f8fe
commit 730d1e2aaf
15 changed files with 695 additions and 4 deletions
+2
View File
@@ -3,6 +3,7 @@ from fastapi import APIRouter, FastAPI
from .db import core_app_extra, db
from .views.admin_api import admin_router
from .views.api import api_router
from .views.blockexplorer_api import blockexplorer_router
from .views.asset_api import asset_router
from .views.audit_api import audit_router
from .views.auth_api import auth_router
@@ -48,6 +49,7 @@ def init_core_routers(app: FastAPI):
app.include_router(asset_router)
app.include_router(fiat_router)
app.include_router(lnurl_router)
app.include_router(blockexplorer_router)
__all__ = ["core_app", "core_app_extra", "db"]
+94
View File
@@ -0,0 +1,94 @@
import asyncio
from http import HTTPStatus
from fastapi import APIRouter, HTTPException
from lnbits.settings import settings
from lnbits.utils.electrum import (
AddressResponse,
BlockHeader,
ElectrumClient,
ElectrumError,
FeeResponse,
Transaction,
parse_raw_tx,
scripthash_from_address,
)
blockexplorer_router = APIRouter(
tags=["Block Explorer"],
prefix="/blockexplorer/api/v1",
)
def _check_enabled() -> None:
if not settings.lnbits_blockexplorer_enabled:
raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
detail="Block explorer is not enabled.",
)
def _client() -> ElectrumClient:
return ElectrumClient(settings.lnbits_blockexplorer_electrum_url)
@blockexplorer_router.get("/tip")
async def api_tip() -> BlockHeader:
_check_enabled()
try:
async with _client() as c:
return await c.get_tip()
except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e))
@blockexplorer_router.get("/fees")
async def api_fees() -> FeeResponse:
_check_enabled()
try:
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)
if fee >= 0
}
return FeeResponse(estimates=estimates, histogram=histogram)
except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e))
@blockexplorer_router.get("/tx/{txid}")
async def api_tx(txid: str) -> Transaction:
_check_enabled()
try:
async with _client() as c:
raw_hex = await c.get_transaction(txid, verbose=False)
return parse_raw_tx(raw_hex)
except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e))
@blockexplorer_router.get("/address/{address}")
async def api_address(address: str) -> AddressResponse:
_check_enabled()
try:
scripthash = scripthash_from_address(address)
except ValueError as e:
raise HTTPException(HTTPStatus.BAD_REQUEST, detail=str(e))
try:
async with _client() as c:
balance, history = await asyncio.gather(
c.get_balance(scripthash),
c.get_history(scripthash),
)
return AddressResponse(balance=balance, history=history)
except ElectrumError as e:
raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e))
+1
View File
@@ -209,6 +209,7 @@ async def index(
@generic_router.get("/")
@generic_router.get("/node/public")
@generic_router.get("/blockexplorer")
@generic_router.get("/first_install", dependencies=[Depends(check_first_install)])
async def index_public(request: Request) -> HTMLResponse:
return template_renderer().TemplateResponse(request, "base.html", {"public": True})