diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 41cb6b99c..c50941f39 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -486,6 +486,7 @@ class WebsocketHandler { // pre-compute address transactions const addressCache = this.makeAddressCache(newTransactions); + const removedAddressCache = this.makeAddressCache(deletedTransactions); this.wss.clients.forEach(async (client) => { if (client.readyState !== WebSocket.OPEN) { @@ -526,11 +527,15 @@ class WebsocketHandler { } if (client['track-address']) { - const foundTransactions = Array.from(addressCache[client['track-address']]?.values() || []); + const newTransactions = Array.from(addressCache[client['track-address']]?.values() || []); + const removedTransactions = Array.from(removedAddressCache[client['track-address']]?.values() || []); // txs may be missing prevouts in non-esplora backends // so fetch the full transactions now - const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(foundTransactions) : foundTransactions; + const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(newTransactions) : newTransactions; + if (removedTransactions.length) { + response['address-removed-transactions'] = JSON.stringify(removedTransactions); + } if (fullTransactions.length) { response['address-transactions'] = JSON.stringify(fullTransactions); } diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index e9cd0189b..0e10b207f 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -174,6 +174,11 @@ export class AddressComponent implements OnInit, OnDestroy { this.addTransaction(tx); }); + this.stateService.mempoolRemovedTransactions$ + .subscribe(tx => { + this.removeTransaction(tx); + }); + this.stateService.blockTransactions$ .subscribe((transaction) => { const tx = this.transactions.find((t) => t.txid === transaction.txid); @@ -222,6 +227,30 @@ export class AddressComponent implements OnInit, OnDestroy { return true; } + removeTransaction(transaction: Transaction): boolean { + const index = this.transactions.findIndex(((tx) => tx.txid === transaction.txid)); + if (index === -1) { + return false; + } + + this.transactions.splice(index, 1); + this.transactions = this.transactions.slice(); + this.txCount--; + + transaction.vin.forEach((vin) => { + if (vin?.prevout?.scriptpubkey_address === this.address.address) { + this.sent -= vin.prevout.value; + } + }); + transaction.vout.forEach((vout) => { + if (vout?.scriptpubkey_address === this.address.address) { + this.received -= vout.value; + } + }); + + return true; + } + loadMore() { if (this.isLoadingTransactions || !this.totalConfirmedTxCount || this.loadedConfirmedTxCount >= this.totalConfirmedTxCount) { return; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 878edf359..db1268379 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -117,6 +117,7 @@ export class StateService { difficultyAdjustment$ = new ReplaySubject(1); mempoolTransactions$ = new Subject(); mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>(); + mempoolRemovedTransactions$ = new Subject(); blockTransactions$ = new Subject(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index af2a15e8c..22da49f06 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -358,6 +358,12 @@ export class WebsocketService { }); } + if (response['address-removed-transactions']) { + response['address-removed-transactions'].forEach((addressTransaction: Transaction) => { + this.stateService.mempoolRemovedTransactions$.next(addressTransaction); + }); + } + if (response['block-transactions']) { response['block-transactions'].forEach((addressTransaction: Transaction) => { this.stateService.blockTransactions$.next(addressTransaction);