From e63effbcb3c1785628b45840b6148225271f28ab Mon Sep 17 00:00:00 2001 From: dni Date: Tue, 23 Jun 2026 06:38:16 +0200 Subject: [PATCH] add blocks --- lnbits/core/views/blockexplorer_api.py | 20 ++ lnbits/static/i18n/en.js | 1 + lnbits/static/js/pages/blockexplorer.js | 46 ++++- lnbits/templates/pages/blockexplorer.vue | 223 ++++++++++++----------- lnbits/utils/electrum.py | 33 ++++ 5 files changed, 216 insertions(+), 107 deletions(-) diff --git a/lnbits/core/views/blockexplorer_api.py b/lnbits/core/views/blockexplorer_api.py index 6f1040d40..d23a2a9f2 100644 --- a/lnbits/core/views/blockexplorer_api.py +++ b/lnbits/core/views/blockexplorer_api.py @@ -7,10 +7,12 @@ from lnbits.settings import settings from lnbits.utils.electrum import ( AddressResponse, BlockHeader, + BlockInfo, ElectrumClient, ElectrumError, FeeResponse, Transaction, + parse_block_header, parse_raw_tx, scripthash_from_address, ) @@ -33,6 +35,24 @@ def _client() -> ElectrumClient: return ElectrumClient(settings.lnbits_blockexplorer_electrum_url) +@blockexplorer_router.get("/blocks") +async def api_blocks() -> list[BlockInfo]: + _check_enabled() + try: + async with _client() as c: + 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: + raise HTTPException(HTTPStatus.SERVICE_UNAVAILABLE, detail=str(e)) from e + + @blockexplorer_router.get("/tip") async def api_tip() -> BlockHeader: _check_enabled() diff --git a/lnbits/static/i18n/en.js b/lnbits/static/i18n/en.js index 711da2669..7a8f066d6 100644 --- a/lnbits/static/i18n/en.js +++ b/lnbits/static/i18n/en.js @@ -852,6 +852,7 @@ window.localisation.en = { blockexplorer_search_label: 'Search by TXID or Address', blockexplorer_search_hint: '64-char hex = transaction ยท anything else = Bitcoin address', + recent_blocks: 'Recent Blocks', chain_tip: 'Chain Tip', block_height: 'Block Height', block_fee: 'block fee', diff --git a/lnbits/static/js/pages/blockexplorer.js b/lnbits/static/js/pages/blockexplorer.js index 263bddb43..7b702b0e7 100644 --- a/lnbits/static/js/pages/blockexplorer.js +++ b/lnbits/static/js/pages/blockexplorer.js @@ -6,6 +6,9 @@ window.PageBlockExplorer = { loading: false, tip: null, fees: null, + blocks: [], + selectedBlock: null, + blockDialog: false, txResult: null, addressResult: null, currentAddress: '' @@ -18,12 +21,48 @@ window.PageBlockExplorer = { label: blocks + '-block fee', rate: (rate * 100000).toFixed(1) + ' sat/vB' })) + }, + formattedBlocks() { + const now = Math.floor(Date.now() / 1000) + return this.blocks.map(b => ({ + ...b, + shortHash: b.hash.slice(0, 8) + '...' + b.hash.slice(-4), + timeAgo: this._timeAgo(now - b.timestamp), + utcTime: new Date(b.timestamp * 1000).toUTCString(), + difficulty: this._difficulty(b.bits) + })) } }, async created() { - await Promise.all([this.loadTip(), this.loadFees()]) + await Promise.all([this.loadTip(), this.loadFees(), this.loadBlocks()]) }, methods: { + _timeAgo(seconds) { + if (seconds < 60) return seconds + 's ago' + if (seconds < 3600) return Math.floor(seconds / 60) + 'm ago' + return Math.floor(seconds / 3600) + 'h ago' + }, + _difficulty(bitsHex) { + const exp = parseInt(bitsHex.slice(0, 2), 16) + const mantissa = parseInt(bitsHex.slice(2), 16) + const diff1 = 0xffff * Math.pow(2, 208) + const target = mantissa * Math.pow(2, 8 * (exp - 3)) + const d = diff1 / target + if (d >= 1e12) return (d / 1e12).toFixed(2) + 'T' + if (d >= 1e9) return (d / 1e9).toFixed(2) + 'G' + if (d >= 1e6) return (d / 1e6).toFixed(2) + 'M' + return d.toFixed(0) + }, + openBlock(b) { + this.selectedBlock = b + this.blockDialog = true + }, + async loadBlocks() { + try { + const r = await LNbits.api.request('GET', '/blockexplorer/api/v1/blocks') + this.blocks = r.data + } catch (_) {} + }, async loadTip() { try { const r = await LNbits.api.request('GET', '/blockexplorer/api/v1/tip') @@ -56,10 +95,7 @@ window.PageBlockExplorer = { }, async loadTx(txid) { try { - const r = await LNbits.api.request( - 'GET', - '/blockexplorer/api/v1/tx/' + txid - ) + const r = await LNbits.api.request('GET', '/blockexplorer/api/v1/tx/' + txid) this.txResult = r.data this.addressResult = null this.query = txid diff --git a/lnbits/templates/pages/blockexplorer.vue b/lnbits/templates/pages/blockexplorer.vue index efb790256..c913fe1cc 100644 --- a/lnbits/templates/pages/blockexplorer.vue +++ b/lnbits/templates/pages/blockexplorer.vue @@ -1,6 +1,78 @@