diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 53d74925d..a2ae6d0c9 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -331,21 +331,30 @@ class WebsocketHandler { } } - if (client['track-tx'] && rbfTransactions[client['track-tx']]) { - for (const rbfTransaction in rbfTransactions) { - if (client['track-tx'] === rbfTransaction) { - const rbfTx = rbfTransactions[rbfTransaction]; - if (config.MEMPOOL.BACKEND !== 'esplora') { - try { - const fullTx = await transactionUtils.$getTransactionExtended(rbfTransaction, true); - response['rbfTransaction'] = fullTx; - } catch (e) { - logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e)); + if (client['track-tx']) { + const utxoSpent = newTransactions.some((tx) => { + return tx.vin.some((vin) => vin.txid === client['track-tx']); + }); + if (utxoSpent) { + response['utxoSpent'] = true; + } + + if (rbfTransactions[client['track-tx']]) { + for (const rbfTransaction in rbfTransactions) { + if (client['track-tx'] === rbfTransaction) { + const rbfTx = rbfTransactions[rbfTransaction]; + if (config.MEMPOOL.BACKEND !== 'esplora') { + try { + const fullTx = await transactionUtils.$getTransactionExtended(rbfTransaction, true); + response['rbfTransaction'] = fullTx; + } catch (e) { + logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e)); + } + } else { + response['rbfTransaction'] = rbfTx; } - } else { - response['rbfTransaction'] = rbfTx; + break; } - break; } } } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index bd77c564f..8b8526336 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -185,15 +185,12 @@ export class TransactionComponent implements OnInit, OnDestroy { this.error = undefined; this.waitingForTransaction = false; this.setMempoolBlocksSubscription(); + this.websocketService.startTrackTransaction(tx.txid); - if (!tx.status.confirmed) { - this.websocketService.startTrackTransaction(tx.txid); - - if (tx.firstSeen) { - this.transactionTime = tx.firstSeen; - } else { - this.getTransactionTime(); - } + if (!tx.status.confirmed && tx.firstSeen) { + this.transactionTime = tx.firstSeen; + } else { + this.getTransactionTime(); } if (this.tx.status.confirmed) { diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index f80953738..85b91cf66 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -82,7 +82,7 @@ - + @@ -183,16 +183,16 @@ - - +
- + + - + - + @@ -204,7 +204,7 @@
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index 2740075b8..fd3cf0241 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -1,11 +1,11 @@ -import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter } from '@angular/core'; import { StateService } from '../../services/state.service'; -import { Observable, forkJoin } from 'rxjs'; +import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge } from 'rxjs'; import { Outspend, Transaction } from '../../interfaces/electrs.interface'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { environment } from 'src/environments/environment'; import { AssetsService } from 'src/app/services/assets.service'; -import { map } from 'rxjs/operators'; +import { map, share, switchMap } from 'rxjs/operators'; import { BlockExtended } from 'src/app/interfaces/node-api.interface'; @Component({ @@ -17,7 +17,6 @@ import { BlockExtended } from 'src/app/interfaces/node-api.interface'; export class TransactionsListComponent implements OnInit, OnChanges { network = ''; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; - displayDetails = false; @Input() transactions: Transaction[]; @Input() showConfirmations = false; @@ -28,15 +27,41 @@ export class TransactionsListComponent implements OnInit, OnChanges { @Output() loadMore = new EventEmitter(); latestBlock$: Observable; - outspends: Outspend[] = []; + outspends$: Observable; + refreshOutspends$: ReplaySubject = new ReplaySubject(); + showDetails$ = new BehaviorSubject(false); + _outspends: Outspend[] = []; assetsMinimal: any; constructor( public stateService: StateService, private electrsApiService: ElectrsApiService, private assetsService: AssetsService, - private ref: ChangeDetectorRef, - ) { } + ) { + this.outspends$ = merge( + this.refreshOutspends$, + this.stateService.utxoSpent$ + .pipe( + map(() => { + this._outspends = []; + return { 0: this.electrsApiService.getOutspends$(this.transactions[0].txid) }; + }), + ) + ).pipe( + switchMap((observableObject) => forkJoin(observableObject)), + map((outspends: any) => { + const newOutspends = []; + for (const i in outspends) { + if (outspends.hasOwnProperty(i)) { + newOutspends.push(outspends[i]); + } + } + this._outspends = this._outspends.concat(newOutspends); + return this._outspends; + }), + share(), + ); + } ngOnInit() { this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block)); @@ -65,23 +90,12 @@ export class TransactionsListComponent implements OnInit, OnChanges { this.transactions.forEach((tx, i) => { tx['@voutLimit'] = true; tx['@vinLimit'] = true; - if (this.outspends[i]) { + if (this._outspends[i]) { return; } observableObject[i] = this.electrsApiService.getOutspends$(tx.txid); }); - - forkJoin(observableObject) - .subscribe((outspends: any) => { - const newOutspends = []; - for (const i in outspends) { - if (outspends.hasOwnProperty(i)) { - newOutspends.push(outspends[i]); - } - } - this.outspends = this.outspends.concat(newOutspends); - this.ref.markForCheck(); - }); + this.refreshOutspends$.next(observableObject); } onScroll() { @@ -129,7 +143,10 @@ export class TransactionsListComponent implements OnInit, OnChanges { } toggleDetails() { - this.displayDetails = !this.displayDetails; - this.ref.markForCheck(); + if (this.showDetails$.value === true) { + this.showDetails$.next(false); + } else { + this.showDetails$.next(true); + } } } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 1dbaa90af..cb867ffb3 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -16,6 +16,7 @@ export interface WebsocketResponse { data?: string[]; tx?: Transaction; rbfTransaction?: Transaction; + utxoSpent?: boolean; transactions?: TransactionStripped[]; loadingIndicators?: ILoadingIndicators; backendInfo?: IBackendInfo; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 14d67e765..238cf227c 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -81,6 +81,7 @@ export class StateService { mempoolInfo$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); txReplaced$ = new Subject(); + utxoSpent$ = new Subject(); mempoolTransactions$ = new Subject(); blockTransactions$ = new Subject(); isLoadingWebSocket$ = new ReplaySubject(1); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index b50cbc494..4d294ae2d 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -251,6 +251,10 @@ export class WebsocketService { this.stateService.bsqPrice$.next(response['bsq-price']); } + if (response.utxoSpent) { + this.stateService.utxoSpent$.next(); + } + if (response.backendInfo) { this.stateService.backendInfo$.next(response.backendInfo);