From 265fec4639de4d5d371238f0c0e818a72cd259c6 Mon Sep 17 00:00:00 2001 From: mononaut Date: Thu, 25 Jun 2026 11:54:34 +0000 Subject: [PATCH 1/5] parse dmnd stratum v2 tags --- backend/src/api/blocks.ts | 4 +++- backend/src/repositories/BlocksRepository.ts | 4 +++- backend/src/utils/bitcoin-script.ts | 25 +++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 0d3631928..be247f571 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -37,7 +37,7 @@ import { calculateGoodBlockCpfp } from './cpfp'; import blockProcessor, { BlockProcessingResult, detectTemplateAlgorithm, saveCpfpDataToCpfpSummary } from './block-processor'; import mempool from './mempool'; import CpfpRepository from '../repositories/CpfpRepository'; -import { parseDATUMTemplateCreator } from '../utils/bitcoin-script'; +import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import database from '../database'; import { getBlockFirstSeenFromLogs, getOldestLogTimestampFromLogs, scanLogsForBlocksFirstSeen } from '../utils/file-read'; @@ -359,6 +359,8 @@ class Blocks { if (extras.pool.name === 'OCEAN') { extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw); + } else if (extras.pool.name === 'DMND') { + extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw); } } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index bb0e4f6c5..faa8fce74 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -14,7 +14,7 @@ import chainTips from '../api/chain-tips'; import blocks from '../api/blocks'; import BlocksAuditsRepository from './BlocksAuditsRepository'; import transactionUtils from '../api/transaction-utils'; -import { parseDATUMTemplateCreator } from '../utils/bitcoin-script'; +import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script'; import poolsUpdater from '../tasks/pools-updater'; interface DatabaseBlock { @@ -1374,6 +1374,8 @@ class BlocksRepository { if (extras.pool.name === 'OCEAN') { extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw); + } else if (extras.pool.name === 'DMND') { + extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw); } blk.extras = extras; diff --git a/backend/src/utils/bitcoin-script.ts b/backend/src/utils/bitcoin-script.ts index 117fa61a7..2a88477b8 100644 --- a/backend/src/utils/bitcoin-script.ts +++ b/backend/src/utils/bitcoin-script.ts @@ -224,4 +224,27 @@ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null tagString = tagString.replace('\x00', ''); return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, '')); -} \ No newline at end of file +} + +/** Extracts miner names from a DMND coinbase transaction */ +export function parseDMNDTemplateCreator(coinbaseRaw: string): string[] { + try { + if (!coinbaseRaw || coinbaseRaw.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(coinbaseRaw)) { + return []; + } + + const bytes = Buffer.from(coinbaseRaw, 'hex'); + const blockHeightLength = bytes[0]; + const tagDelimiterIndex = 1 + blockHeightLength; + if (bytes.length <= tagDelimiterIndex || bytes[tagDelimiterIndex] !== 0x00) { + return []; + } + + const tagStart = tagDelimiterIndex + 1; + const tagEnd = bytes.indexOf(0x00, tagStart); + const tags = bytes.subarray(tagStart, tagEnd === -1 ? undefined : tagEnd); + return tags.toString('utf8').split('/').slice(1, -1); + } catch { + return []; + } +} From 2f767446349b91d97bbd7aad5c77b7fd729b824e Mon Sep 17 00:00:00 2001 From: mononaut Date: Fri, 26 Jun 2026 09:17:02 +0000 Subject: [PATCH 2/5] add miner json --- .../block/block-preview.component.html | 26 ++++--- .../app/components/block/block.component.html | 23 +++--- .../blockchain-blocks.component.html | 10 ++- .../stale-list/stale-list.component.html | 20 ++++-- .../transaction-details.component.html | 23 +++--- .../transaction/transaction.component.ts | 2 + .../src/app/interfaces/node-api.interface.ts | 2 + frontend/src/app/services/api.service.ts | 18 +++-- frontend/src/app/services/miners.service.ts | 71 +++++++++++++++++++ .../src/app/services/websocket.service.ts | 18 +++-- frontend/src/resources/miners.json | 8 +++ 11 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 frontend/src/app/services/miners.service.ts create mode 100644 frontend/src/resources/miners.json diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html index d7998c856..6b59974df 100644 --- a/frontend/src/app/components/block/block-preview.component.html +++ b/frontend/src/app/components/block/block-preview.component.html @@ -53,15 +53,23 @@ Miner - - @if (block.extras.pool.minerNames[1].length > 16) { - {{ block.extras.pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ block.extras.pool.minerNames[1] }} - } - - - {{ block.extras.pool.name }} + + + {{ block.extras.pool.minerName }} + + {{ block.extras.pool.name }} + + + + @if (block.extras.pool.minerNames[1].length > 16) { + {{ block.extras.pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ block.extras.pool.minerNames[1] }} + } + + + {{ block.extras.pool.name }} + diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index be0a094ac..f076debc5 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -188,14 +188,21 @@ Miner - - @if (block.extras.pool.minerNames[1].length > 16) { - {{ block.extras.pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ block.extras.pool.minerNames[1] }} - } - - + + + {{ block.extras.pool.minerName }} + + + + + @if (block.extras.pool.minerNames[1].length > 16) { + {{ block.extras.pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ block.extras.pool.minerNames[1] }} + } + + + diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index fee6516aa..82138d98e 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -79,8 +79,14 @@
- - {{ block.extras.pool.minerNames[1] }} + + + {{ block.extras.pool.minerName }} + + + + {{ block.extras.pool.minerNames[1] }} + diff --git a/frontend/src/app/components/stale-list/stale-list.component.html b/frontend/src/app/components/stale-list/stale-list.component.html index d761e7c5d..5394d677c 100644 --- a/frontend/src/app/components/stale-list/stale-list.component.html +++ b/frontend/src/app/components/stale-list/stale-list.component.html @@ -57,8 +57,14 @@
- - {{ chainTip.stale.extras.pool.minerNames[1] }} + + + {{ chainTip.stale.extras.pool.minerName }} + + + + {{ chainTip.stale.extras.pool.minerNames[1] }} + @@ -103,8 +109,14 @@
- - {{ chainTip.canonical.extras.pool.minerNames[1] }} + + + {{ chainTip.canonical.extras.pool.minerName }} + + + + {{ chainTip.canonical.extras.pool.minerNames[1] }} + diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index 7d1ede2d8..001e57bc9 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -333,14 +333,21 @@ @if (pool) { - - @if (pool.minerNames[1].length > 16) { - {{ pool.minerNames[1].slice(0, 15) }}… - } @else { - {{ pool.minerNames[1] }} - } - - + + + {{ pool.minerName }} + + + + + @if (pool.minerNames[1].length > 16) { + {{ pool.minerNames[1].slice(0, 15) }}… + } @else { + {{ pool.minerNames[1] }} + } + + + } @else { diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index de4936f12..8c7892c37 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -45,6 +45,8 @@ export interface Pool { id: number; name: string; slug: string; + minerName?: string; + minerSlug?: string; minerNames: string[] | null; } diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index a76a2d2be..92450246a 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -224,6 +224,8 @@ export interface BlockExtension { id: number; name: string; slug: string; + minerName?: string; + minerSlug?: string; minerNames: string[] | null; } orphans?: { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 7d1cc77bb..d05b02c66 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -2,13 +2,14 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult, WalletAddress, Treasury, SubmitPackageResult, ChainTip, StaleTip } from '@interfaces/node-api.interface'; -import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs'; +import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, switchMap, take, tap } from 'rxjs'; import { StateService } from '@app/services/state.service'; import { Transaction } from '@interfaces/electrs.interface'; import { Conversion } from '@app/services/price.service'; import { StorageService } from '@app/services/storage.service'; import { WebsocketResponse } from '@interfaces/websocket.interface'; import { TxAuditStatus } from '@components/transaction/transaction.component'; +import { MinersService } from '@app/services/miners.service'; @Injectable({ providedIn: 'root' @@ -24,7 +25,8 @@ export class ApiService { constructor( private httpClient: HttpClient, private stateService: StateService, - private storageService: StorageService + private storageService: StorageService, + private minersService: MinersService, ) { this.apiBaseUrl = ''; // use relative URL by default if (!stateService.isBrowser) { // except when inside AU SSR process @@ -173,7 +175,9 @@ export class ApiService { } getStaleTips$(height?: number): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``)); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``)).pipe( + switchMap((staleTips) => this.minersService.applyStaleTipsMinerDetails$(staleTips)) + ); } liquidPegs$(): Observable { @@ -308,6 +312,8 @@ export class ApiService { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/blocks` + (fromHeight !== undefined ? `/${fromHeight}` : '') + ).pipe( + switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks)) ); } @@ -315,11 +321,15 @@ export class ApiService { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks` + (from !== undefined ? `/${from}` : ``) + ).pipe( + switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks)) ); } getBlock$(hash: string): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash).pipe( + switchMap((block) => this.minersService.applyBlockMinerDetails$(block)) + ); } getBlockDataFromTimestamp$(timestamp: number): Observable { diff --git a/frontend/src/app/services/miners.service.ts b/frontend/src/app/services/miners.service.ts new file mode 100644 index 000000000..53b8a52b5 --- /dev/null +++ b/frontend/src/app/services/miners.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { map, shareReplay } from 'rxjs/operators'; +import { BlockExtended, StaleTip } from '@interfaces/node-api.interface'; +import { StateService } from '@app/services/state.service'; + +interface Miner { + name: string; + slug: string; + tags: string[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class MinersService { + private apiBaseUrl = ''; + private miners$: Observable; + + constructor( + private httpClient: HttpClient, + private stateService: StateService, + ) { + if (!stateService.isBrowser) { + this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; + } + this.miners$ = this.httpClient.get(this.apiBaseUrl + '/resources/miners.json').pipe(shareReplay(1)); + } + + public applyBlockMinerDetails$(block: BlockExtended): Observable { + if (!block?.extras?.pool?.minerNames?.length) { + return of(block); + } + return this.miners$.pipe(map((miners) => this.applyBlockMinerDetails(block, miners))); + } + + public applyBlocksMinerDetails$(blocks: BlockExtended[]): Observable { + if (!blocks?.some((block) => block?.extras?.pool?.minerNames?.length)) { + return of(blocks); + } + return this.miners$.pipe(map((miners) => blocks.map((block) => this.applyBlockMinerDetails(block, miners)))); + } + + public applyStaleTipsMinerDetails$(staleTips: StaleTip[]): Observable { + if (!staleTips?.some((staleTip) => staleTip.stale?.extras?.pool?.minerNames?.length || staleTip.canonical?.extras?.pool?.minerNames?.length)) { + return of(staleTips); + } + return this.miners$.pipe( + map((miners) => staleTips.map((staleTip) => { + this.applyBlockMinerDetails(staleTip.stale, miners); + this.applyBlockMinerDetails(staleTip.canonical, miners); + return staleTip; + })) + ); + } + + private applyBlockMinerDetails(block: BlockExtended, miners: Miner[]): BlockExtended { + const minerNames = block?.extras?.pool?.minerNames; + if (!minerNames?.length) { + return block; + } + + const miner = miners.find((minerEntry) => minerEntry.tags.some((tag) => minerNames.includes(tag))); + if (miner) { + block.extras.pool.minerName = miner.name; + block.extras.pool.minerSlug = miner.slug; + } + return block; + } +} diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 8979ce4cf..dcb59f3d8 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -9,6 +9,7 @@ import { take } from 'rxjs/operators'; import { TransferState, makeStateKey } from '@angular/core'; import { CacheService } from '@app/services/cache.service'; import { uncompressDeltaChange, uncompressTx } from '@app/shared/common.utils'; +import { MinersService } from '@app/services/miners.service'; const OFFLINE_RETRY_AFTER_MS = 2000; const OFFLINE_PING_CHECK_AFTER_MS = 30000; @@ -51,6 +52,7 @@ export class WebsocketService { private apiService: ApiService, private transferState: TransferState, private cacheService: CacheService, + private minersService: MinersService, ) { if (!this.stateService.isBrowser) { // @ts-ignore @@ -356,9 +358,11 @@ export class WebsocketService { if (response.blocks && response.blocks.length) { const blocks = response.blocks; - this.stateService.resetBlocks(blocks); - const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); - this.stateService.updateChainTip(maxHeight); + this.minersService.applyBlocksMinerDetails$(blocks).subscribe((mappedBlocks) => { + this.stateService.resetBlocks(mappedBlocks); + const maxHeight = mappedBlocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); + this.stateService.updateChainTip(maxHeight); + }); } if (response.tx) { @@ -371,9 +375,11 @@ export class WebsocketService { if (response.block) { if (response.block.height === this.stateService.latestBlockHeight + 1) { - this.stateService.updateChainTip(response.block.height); - this.stateService.addBlock(response.block); - this.stateService.txConfirmed$.next([response.txConfirmed, response.block]); + this.minersService.applyBlockMinerDetails$(response.block).subscribe((block) => { + this.stateService.updateChainTip(block.height); + this.stateService.addBlock(block); + this.stateService.txConfirmed$.next([response.txConfirmed, block]); + }); } else if (response.block.height > this.stateService.latestBlockHeight + 1) { reinitBlocks = true; } diff --git a/frontend/src/resources/miners.json b/frontend/src/resources/miners.json new file mode 100644 index 000000000..7034597c8 --- /dev/null +++ b/frontend/src/resources/miners.json @@ -0,0 +1,8 @@ +[ + { + "name": "GoMining", + "tags": ["GM", "GoMining"], + "link": "https://gomining.com/", + "slug": "gomining" + } +] \ No newline at end of file From 7ca809a074e23a91381675dd2989eff491387ebd Mon Sep 17 00:00:00 2001 From: mononaut Date: Fri, 26 Jun 2026 12:40:33 +0000 Subject: [PATCH 3/5] miner json fixes --- .../components/block/block-preview.component.html | 2 +- .../src/app/components/block/block.component.html | 2 +- .../blockchain-blocks.component.html | 2 +- .../stale-list/stale-list.component.html | 4 ++-- .../transaction-details.component.html | 2 +- frontend/src/app/services/miners.service.ts | 9 ++++++--- frontend/src/app/services/websocket.service.ts | 14 ++++++++++---- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html index 6b59974df..d844f5181 100644 --- a/frontend/src/app/components/block/block-preview.component.html +++ b/frontend/src/app/components/block/block-preview.component.html @@ -53,7 +53,7 @@ Miner - + {{ block.extras.pool.minerName }} diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index f076debc5..7e5c7af72 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -188,7 +188,7 @@ Miner - + {{ block.extras.pool.minerName }} diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index 82138d98e..0ba86cff1 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -79,7 +79,7 @@
- + {{ block.extras.pool.minerName }} diff --git a/frontend/src/app/components/stale-list/stale-list.component.html b/frontend/src/app/components/stale-list/stale-list.component.html index 5394d677c..381159c6e 100644 --- a/frontend/src/app/components/stale-list/stale-list.component.html +++ b/frontend/src/app/components/stale-list/stale-list.component.html @@ -57,7 +57,7 @@
- + {{ chainTip.stale.extras.pool.minerName }} @@ -109,7 +109,7 @@
- + {{ chainTip.canonical.extras.pool.minerName }} diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html index 001e57bc9..a1729dfec 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.html @@ -333,7 +333,7 @@ @if (pool) { - + {{ pool.minerName }} diff --git a/frontend/src/app/services/miners.service.ts b/frontend/src/app/services/miners.service.ts index 53b8a52b5..7e82668de 100644 --- a/frontend/src/app/services/miners.service.ts +++ b/frontend/src/app/services/miners.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; -import { map, shareReplay } from 'rxjs/operators'; +import { catchError, map, shareReplay } from 'rxjs/operators'; import { BlockExtended, StaleTip } from '@interfaces/node-api.interface'; import { StateService } from '@app/services/state.service'; @@ -25,7 +25,10 @@ export class MinersService { if (!stateService.isBrowser) { this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT; } - this.miners$ = this.httpClient.get(this.apiBaseUrl + '/resources/miners.json').pipe(shareReplay(1)); + this.miners$ = this.httpClient.get(this.apiBaseUrl + '/resources/miners.json').pipe( + catchError(() => of([])), + shareReplay(1) + ); } public applyBlockMinerDetails$(block: BlockExtended): Observable { @@ -62,7 +65,7 @@ export class MinersService { } const miner = miners.find((minerEntry) => minerEntry.tags.some((tag) => minerNames.includes(tag))); - if (miner) { + if (miner?.name && miner.slug) { block.extras.pool.minerName = miner.name; block.extras.pool.minerSlug = miner.slug; } diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index dcb59f3d8..0db69de1d 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -3,9 +3,9 @@ import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; import { WebsocketResponse } from '@interfaces/websocket.interface'; import { StateService } from '@app/services/state.service'; import { Transaction } from '@interfaces/electrs.interface'; -import { firstValueFrom, Subscription } from 'rxjs'; +import { firstValueFrom, of, Subscription } from 'rxjs'; import { ApiService } from '@app/services/api.service'; -import { take } from 'rxjs/operators'; +import { catchError, take } from 'rxjs/operators'; import { TransferState, makeStateKey } from '@angular/core'; import { CacheService } from '@app/services/cache.service'; import { uncompressDeltaChange, uncompressTx } from '@app/shared/common.utils'; @@ -358,7 +358,10 @@ export class WebsocketService { if (response.blocks && response.blocks.length) { const blocks = response.blocks; - this.minersService.applyBlocksMinerDetails$(blocks).subscribe((mappedBlocks) => { + this.minersService.applyBlocksMinerDetails$(blocks).pipe( + take(1), + catchError(() => of(blocks)) + ).subscribe((mappedBlocks) => { this.stateService.resetBlocks(mappedBlocks); const maxHeight = mappedBlocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); this.stateService.updateChainTip(maxHeight); @@ -375,7 +378,10 @@ export class WebsocketService { if (response.block) { if (response.block.height === this.stateService.latestBlockHeight + 1) { - this.minersService.applyBlockMinerDetails$(response.block).subscribe((block) => { + this.minersService.applyBlockMinerDetails$(response.block).pipe( + take(1), + catchError(() => of(response.block)) + ).subscribe((block) => { this.stateService.updateChainTip(block.height); this.stateService.addBlock(block); this.stateService.txConfirmed$.next([response.txConfirmed, block]); From 446b5e84c3e2e89377982262cdd229592bcb0dd1 Mon Sep 17 00:00:00 2001 From: wiz Date: Sat, 27 Jun 2026 14:52:21 +0900 Subject: [PATCH 4/5] Remove 'see more details here' link from FAQ --- frontend/src/app/docs/api-docs/api-docs.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index c0b2bc38b..1eeb01f87 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -330,7 +330,7 @@ -

Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it. See more details here.

As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.

+

Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it.

As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.

From f802817adc6fd0f7e1343f201bd7ccdb2afd4926 Mon Sep 17 00:00:00 2001 From: mononaut Date: Mon, 29 Jun 2026 04:56:20 +0000 Subject: [PATCH 5/5] only show accelerator savings msg on mainnet --- .../transaction-details/transaction-details.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts index 4b3c7806e..9a1b0e2a2 100644 --- a/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts +++ b/frontend/src/app/components/transaction/transaction-details/transaction-details.component.ts @@ -92,6 +92,10 @@ export class TransactionDetailsComponent implements OnChanges { } calculateAcceleratorSavings(block: BlockExtended): void { + if (this.network !== '') { + this.acceleratorSavingsSats = 0; + return; + } const minBlockRate = block?.extras?.feeRange?.[0]; if (minBlockRate !== undefined) { const vsize = this.tx.weight / 4; @@ -100,7 +104,7 @@ export class TransactionDetailsComponent implements OnChanges { } get showAcceleratorSavingsMsg(): boolean { - return this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp; + return this.network === '' && this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp; } onAccelerateClicked(): void {