From 8529b996750d27b8a5e34c6053711266945f2f29 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 25 Jul 2024 22:34:52 +0000 Subject: [PATCH 1/6] custom dashboard wallet widgets --- frontend/src/app/interfaces/node-api.interface.ts | 2 +- frontend/src/app/services/state.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index b39f8e0d3..4d85a938d 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -1,4 +1,4 @@ -import { AddressTxSummary, Block, ChainStats, Transaction } from "./electrs.interface"; +import { AddressTxSummary, Block, ChainStats } from "./electrs.interface"; export interface OptimizedMempoolStats { added: number; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 0d006b552..55f76d6b1 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -171,7 +171,7 @@ export class StateService { mempoolRemovedTransactions$ = new Subject(); multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); blockTransactions$ = new Subject(); - walletTransactions$ = new Subject(); + walletTransactions$ = new Subject>(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); vbytesPerSecond$ = new ReplaySubject(1); From c4ec50b7719f6e709582aae89aad25c6e901712b Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 4 Oct 2024 22:31:49 +0000 Subject: [PATCH 2/6] Restore transactions list to wallet page --- .../components/wallet/wallet.component.html | 30 ++++++++ .../app/components/wallet/wallet.component.ts | 77 ++++++++++++++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/wallet/wallet.component.html b/frontend/src/app/components/wallet/wallet.component.html index 52b7b02a5..ed6b82456 100644 --- a/frontend/src/app/components/wallet/wallet.component.html +++ b/frontend/src/app/components/wallet/wallet.component.html @@ -74,6 +74,36 @@ +
+ +
+

Transactions

+
+ + + +
+ +
+
+
+ +
+
+ +
+
+
+ +
+ + +
+ +
+
+ +
diff --git a/frontend/src/app/components/wallet/wallet.component.ts b/frontend/src/app/components/wallet/wallet.component.ts index ce44250e9..bf5b67ae7 100644 --- a/frontend/src/app/components/wallet/wallet.component.ts +++ b/frontend/src/app/components/wallet/wallet.component.ts @@ -9,6 +9,8 @@ import { of, Observable, Subscription } from 'rxjs'; import { SeoService } from '@app/services/seo.service'; import { seoDescriptionNetwork } from '@app/shared/common.utils'; import { WalletAddress } from '@interfaces/node-api.interface'; +import { ElectrsApiService } from '@app/services/electrs-api.service'; +import { AudioService } from '@app/services/audio.service'; class WalletStats implements ChainStats { addresses: string[]; @@ -24,6 +26,7 @@ class WalletStats implements ChainStats { acc.funded_txo_sum += stat.funded_txo_sum; acc.spent_txo_count += stat.spent_txo_count; acc.spent_txo_sum += stat.spent_txo_sum; + acc.tx_count += stat.tx_count; return acc; }, { funded_txo_count: 0, @@ -109,12 +112,17 @@ export class WalletComponent implements OnInit, OnDestroy { addressStrings: string[] = []; walletName: string; isLoadingWallet = true; + isLoadingTransactions = true; + transactions: Transaction[]; + totalTransactionCount: number; + retryLoadMore = false; wallet$: Observable>; walletAddresses$: Observable>; walletSummary$: Observable; walletStats$: Observable; error: any; walletSubscription: Subscription; + transactionSubscription: Subscription; collapseAddresses: boolean = true; @@ -129,6 +137,8 @@ export class WalletComponent implements OnInit, OnDestroy { private websocketService: WebsocketService, private stateService: StateService, private apiService: ApiService, + private electrsApiService: ElectrsApiService, + private audioService: AudioService, private seoService: SeoService, ) { } @@ -172,6 +182,21 @@ export class WalletComponent implements OnInit, OnDestroy { }), switchMap(initial => this.stateService.walletTransactions$.pipe( startWith(null), + tap((transactions) => { + if (!transactions?.length) { + return; + } + for (const transaction of transactions) { + const tx = this.transactions.find((t) => t.txid === transaction.txid); + if (tx) { + tx.status = transaction.status; + } else { + this.transactions.unshift(transaction); + } + } + this.transactions = this.transactions.slice(); + this.audioService.playSound('magic'); + }), scan((wallet, walletTransactions) => { for (const tx of (walletTransactions || [])) { const funded: Record = {}; @@ -267,8 +292,57 @@ export class WalletComponent implements OnInit, OnDestroy { return stats; }, walletStats), ); - }), + }) ); + + this.transactionSubscription = this.wallet$.pipe( + switchMap(wallet => { + const addresses = Object.keys(wallet).map(addr => this.normalizeAddress(addr)); + return this.electrsApiService.getAddressesTransactions$(addresses); + }), + map(transactions => { + // only confirmed transactions supported for now + return transactions.filter(tx => tx.status.confirmed); + }), + catchError((error) => { + console.log(error); + this.error = error; + this.seoService.logSoft404(); + this.isLoadingWallet = false; + return of([]); + }) + ).subscribe((transactions: Transaction[] | null) => { + if (!transactions) { + return; + } + this.transactions = transactions; + this.isLoadingTransactions = false; + }); + } + + loadMore(): void { + if (this.isLoadingTransactions || this.fullyLoaded) { + return; + } + this.isLoadingTransactions = true; + this.retryLoadMore = false; + this.electrsApiService.getAddressesTransactions$(this.addressStrings, this.transactions[this.transactions.length - 1].txid) + .subscribe((transactions: Transaction[]) => { + if (transactions && transactions.length) { + this.transactions = this.transactions.concat(transactions); + } else { + this.fullyLoaded = true; + } + this.isLoadingTransactions = false; + }, + (error) => { + this.isLoadingTransactions = false; + this.retryLoadMore = true; + // In the unlikely event of the txid wasn't found in the mempool anymore and we must reload the page. + if (error.status === 422) { + window.location.reload(); + } + }); } deduplicateWalletTransactions(walletTransactions: AddressTxSummary[]): AddressTxSummary[] { @@ -299,5 +373,6 @@ export class WalletComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.websocketService.stopTrackingWallet(); this.walletSubscription.unsubscribe(); + this.transactionSubscription.unsubscribe(); } } From f77dc68ec71c026c9b7390e6eda79bd9510d2f56 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 4 Oct 2024 22:43:52 +0000 Subject: [PATCH 3/6] Add link to wallet page from custom dashboard txs widget --- .../custom-dashboard/custom-dashboard.component.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html index 13cdd97ce..8ca1a5ac4 100644 --- a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html +++ b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html @@ -281,9 +281,11 @@
From b65d00f2897969cdc6d2aaf1601dbc62d00f23d7 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 6 Oct 2024 22:29:32 +0000 Subject: [PATCH 4/6] switch multi-address APIs to use POST --- frontend/src/app/services/electrs-api.service.ts | 14 +++++++++----- frontend/src/app/services/state.service.ts | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index 3cd5b5abd..6e9697f49 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -142,12 +142,16 @@ export class ElectrsApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs', { params }); } - getAddressesTransactions$(addresses: string[], txid?: string): Observable { + getAddressesTransactions$(addresses: string[], txid?: string): Observable { let params = new HttpParams(); if (txid) { params = params.append('after_txid', txid); } - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs?addresses=${addresses.join(',')}`, { params }); + return this.httpClient.post( + this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs', + addresses, + { params } + ); } getAddressSummary$(address: string, txid?: string): Observable { @@ -163,7 +167,7 @@ export class ElectrsApiService { if (txid) { params = params.append('after_txid', txid); } - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs/summary?addresses=${addresses.join(',')}`, { params }); + return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs/summary', addresses, { params }); } getScriptHashTransactions$(script: string, txid?: string): Observable { @@ -182,7 +186,7 @@ export class ElectrsApiService { params = params.append('after_txid', txid); } return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe( - switchMap(scriptHashes => this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs?scripthashes=${scriptHashes.join(',')}`, { params })), + switchMap(scriptHashes => this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs', scriptHashes, { params })), ); } @@ -212,7 +216,7 @@ export class ElectrsApiService { params = params.append('after_txid', txid); } return from(Promise.all(scripts.map(script => calcScriptHash$(script)))).pipe( - switchMap(scriptHashes => this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs/summary?scripthashes=${scriptHashes.join(',')}`, { params })), + switchMap(scriptHashes => this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs/summary', scriptHashes, { params })), ); } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 55f76d6b1..0d006b552 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -171,7 +171,7 @@ export class StateService { mempoolRemovedTransactions$ = new Subject(); multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); blockTransactions$ = new Subject(); - walletTransactions$ = new Subject>(); + walletTransactions$ = new Subject(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); vbytesPerSecond$ = new ReplaySubject(1); From c248544fe806f362c16ab6af69d809588c668523 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 18 Oct 2024 04:39:31 +0000 Subject: [PATCH 5/6] Update wallet page title --- frontend/src/app/components/wallet/wallet.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/wallet/wallet.component.html b/frontend/src/app/components/wallet/wallet.component.html index ed6b82456..9aa82b818 100644 --- a/frontend/src/app/components/wallet/wallet.component.html +++ b/frontend/src/app/components/wallet/wallet.component.html @@ -1,6 +1,6 @@
-

Wallet

+

{{ walletName }}

From 204d54b1896e0a3eeaaa927d565b3bc3fcc1770c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 22 Dec 2024 10:47:36 +0000 Subject: [PATCH 6/6] fix wallet transactions ordering --- frontend/src/app/components/wallet/wallet.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/wallet/wallet.component.ts b/frontend/src/app/components/wallet/wallet.component.ts index bf5b67ae7..43cc7ee80 100644 --- a/frontend/src/app/components/wallet/wallet.component.ts +++ b/frontend/src/app/components/wallet/wallet.component.ts @@ -302,7 +302,7 @@ export class WalletComponent implements OnInit, OnDestroy { }), map(transactions => { // only confirmed transactions supported for now - return transactions.filter(tx => tx.status.confirmed); + return transactions.filter(tx => tx.status.confirmed).sort((a, b) => b.status.block_height - a.status.block_height); }), catchError((error) => { console.log(error); @@ -329,7 +329,7 @@ export class WalletComponent implements OnInit, OnDestroy { this.electrsApiService.getAddressesTransactions$(this.addressStrings, this.transactions[this.transactions.length - 1].txid) .subscribe((transactions: Transaction[]) => { if (transactions && transactions.length) { - this.transactions = this.transactions.concat(transactions); + this.transactions = this.transactions.concat(transactions.sort((a, b) => b.status.block_height - a.status.block_height)); } else { this.fullyLoaded = true; }