diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index a2ae6d0c9..92abaab6d 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -332,11 +332,18 @@ class WebsocketHandler { } if (client['track-tx']) { - const utxoSpent = newTransactions.some((tx) => { - return tx.vin.some((vin) => vin.txid === client['track-tx']); - }); - if (utxoSpent) { - response['utxoSpent'] = true; + const outspends: object = {}; + newTransactions.forEach((tx) => tx.vin.forEach((vin, i) => { + if (vin.txid === client['track-tx']) { + outspends[vin.vout] = { + vin: i, + txid: tx.txid, + }; + } + })); + + if (Object.keys(outspends).length) { + response['utxoSpent'] = outspends; } if (rbfTransactions[client['track-tx']]) { @@ -414,7 +421,6 @@ class WebsocketHandler { } if (client['track-tx'] && txIds.indexOf(client['track-tx']) > -1) { - client['track-tx'] = null; response['txConfirmed'] = true; } 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 200356ffd..1470e6211 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -186,16 +186,16 @@ - - + + - + - + 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 fd3cf0241..7d3c5d0e4 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, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; import { StateService } from '../../services/state.service'; -import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge } from 'rxjs'; +import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge, of, Subject, Subscription } 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, share, switchMap } from 'rxjs/operators'; +import { map, share, switchMap, tap } from 'rxjs/operators'; import { BlockExtended } from 'src/app/interfaces/node-api.interface'; @Component({ @@ -27,41 +27,18 @@ export class TransactionsListComponent implements OnInit, OnChanges { @Output() loadMore = new EventEmitter(); latestBlock$: Observable; - outspends$: Observable; + outspendsSubscription: Subscription; refreshOutspends$: ReplaySubject = new ReplaySubject(); showDetails$ = new BehaviorSubject(false); - _outspends: Outspend[] = []; + outspends: Outspend[][] = []; assetsMinimal: any; constructor( public stateService: StateService, private electrsApiService: ElectrsApiService, private assetsService: AssetsService, - ) { - 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(), - ); - } + private ref: ChangeDetectorRef, + ) { } ngOnInit() { this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block)); @@ -72,6 +49,34 @@ export class TransactionsListComponent implements OnInit, OnChanges { this.assetsMinimal = assets; }); } + + this.outspendsSubscription = merge( + this.refreshOutspends$ + .pipe( + switchMap((observableObject) => forkJoin(observableObject)), + map((outspends: any) => { + const newOutspends: Outspend[] = []; + for (const i in outspends) { + if (outspends.hasOwnProperty(i)) { + newOutspends.push(outspends[i]); + } + } + this.outspends = this.outspends.concat(newOutspends); + }), + ), + this.stateService.utxoSpent$ + .pipe( + map((utxoSpent) => { + for (const i in utxoSpent) { + this.outspends[0][i] = { + spent: true, + txid: utxoSpent[i].txid, + vin: utxoSpent[i].vin, + }; + } + }), + ) + ).subscribe(() => this.ref.markForCheck()); } ngOnChanges() { @@ -90,7 +95,7 @@ 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); @@ -149,4 +154,8 @@ export class TransactionsListComponent implements OnInit, OnChanges { this.showDetails$.next(true); } } + + ngOnDestroy() { + this.outspendsSubscription.unsubscribe(); + } } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index cb867ffb3..d82d9a618 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -16,7 +16,7 @@ export interface WebsocketResponse { data?: string[]; tx?: Transaction; rbfTransaction?: Transaction; - utxoSpent?: boolean; + utxoSpent?: object; 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 238cf227c..85ce7b567 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -81,7 +81,7 @@ export class StateService { mempoolInfo$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); txReplaced$ = new Subject(); - utxoSpent$ = 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 4d294ae2d..f99524197 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -252,7 +252,7 @@ export class WebsocketService { } if (response.utxoSpent) { - this.stateService.utxoSpent$.next(); + this.stateService.utxoSpent$.next(response.utxoSpent); } if (response.backendInfo) {