diff --git a/frontend/src/app/components/wallet/wallet.component.ts b/frontend/src/app/components/wallet/wallet.component.ts
index ce44250e9..43cc7ee80 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).sort((a, b) => b.status.block_height - a.status.block_height);
+ }),
+ 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.sort((a, b) => b.status.block_height - a.status.block_height));
+ } 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();
}
}
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/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 })),
);
}