mirror of
https://github.com/mempool/mempool.git
synced 2025-04-08 11:58:31 +02:00
Merge pull request #5562 from mempool/mononaut/wallet-transactions
Wallet page transactions
This commit is contained in:
commit
803b005880
@ -281,9 +281,11 @@
|
||||
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<span class="title-link">
|
||||
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/wallet/' + widget.props.wallet | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.treasury-transactions">Treasury Transactions</h5>
|
||||
</span>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||
</a>
|
||||
<app-address-transactions-widget [addressSummary$]="walletSummary$"></app-address-transactions-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
|
||||
<div class="title-address">
|
||||
<h1 i18n="shared.wallet">Wallet</h1>
|
||||
<h1>{{ walletName }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
@ -74,6 +74,36 @@
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="title-tx">
|
||||
<h2 class="text-left" i18n="address.transactions">Transactions</h2>
|
||||
</div>
|
||||
|
||||
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" [addresses]="addressStrings" (loadMore)="loadMore()"></app-transactions-list>
|
||||
|
||||
<div class="text-center">
|
||||
<ng-template [ngIf]="isLoadingTransactions">
|
||||
<div class="header-bg box">
|
||||
<div class="row" style="height: 107px;">
|
||||
<div class="col-sm">
|
||||
<span class="skeleton-loader"></span>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<span class="skeleton-loader"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
|
||||
<ng-template [ngIf]="retryLoadMore">
|
||||
<br>
|
||||
<button type="button" class="btn btn-outline-info btn-sm" (click)="loadMore()"><fa-icon [icon]="['fas', 'redo-alt']" [fixedWidth]="true"></fa-icon></button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
|
||||
<ng-template #loadingTemplate>
|
||||
|
||||
<div class="box" *ngIf="!error; else errorTemplate">
|
||||
|
@ -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<Record<string, WalletAddress>>;
|
||||
walletAddresses$: Observable<Record<string, Address>>;
|
||||
walletSummary$: Observable<AddressTxSummary[]>;
|
||||
walletStats$: Observable<WalletStats>;
|
||||
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<string, number> = {};
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AddressTxSummary, Block, ChainStats, Transaction } from "./electrs.interface";
|
||||
import { AddressTxSummary, Block, ChainStats } from "./electrs.interface";
|
||||
|
||||
export interface OptimizedMempoolStats {
|
||||
added: number;
|
||||
|
@ -142,12 +142,16 @@ export class ElectrsApiService {
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs', { params });
|
||||
}
|
||||
|
||||
getAddressesTransactions$(addresses: string[], txid?: string): Observable<Transaction[]> {
|
||||
getAddressesTransactions$(addresses: string[], txid?: string): Observable<Transaction[]> {
|
||||
let params = new HttpParams();
|
||||
if (txid) {
|
||||
params = params.append('after_txid', txid);
|
||||
}
|
||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs?addresses=${addresses.join(',')}`, { params });
|
||||
return this.httpClient.post<Transaction[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs',
|
||||
addresses,
|
||||
{ params }
|
||||
);
|
||||
}
|
||||
|
||||
getAddressSummary$(address: string, txid?: string): Observable<AddressTxSummary[]> {
|
||||
@ -163,7 +167,7 @@ export class ElectrsApiService {
|
||||
if (txid) {
|
||||
params = params.append('after_txid', txid);
|
||||
}
|
||||
return this.httpClient.get<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + `/api/addresses/txs/summary?addresses=${addresses.join(',')}`, { params });
|
||||
return this.httpClient.post<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + '/api/addresses/txs/summary', addresses, { params });
|
||||
}
|
||||
|
||||
getScriptHashTransactions$(script: string, txid?: string): Observable<Transaction[]> {
|
||||
@ -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<Transaction[]>(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs?scripthashes=${scriptHashes.join(',')}`, { params })),
|
||||
switchMap(scriptHashes => this.httpClient.post<Transaction[]>(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<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + `/api/scripthashes/txs/summary?scripthashes=${scriptHashes.join(',')}`, { params })),
|
||||
switchMap(scriptHashes => this.httpClient.post<AddressTxSummary[]>(this.apiBaseUrl + this.apiBasePath + '/api/scripthashes/txs/summary', scriptHashes, { params })),
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user