Refactor address page component with AddressStats class
This commit is contained in:
parent
684ad9f0e6
commit
746a045c48
@ -28,16 +28,16 @@
|
|||||||
<ng-template [ngIf]="!address.electrum">
|
<ng-template [ngIf]="!address.electrum">
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-received">Total received</td>
|
<td i18n="address.total-received">Total received</td>
|
||||||
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="received" [noFiat]="true"></app-amount></td>
|
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="chainStats.funded_txo_sum + mempoolStats.funded_txo_sum" [noFiat]="true"></app-amount></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Total sent</td>
|
<td i18n="address.total-sent">Total sent</td>
|
||||||
<td *ngIf="address.chain_stats.spent_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="sent" [noFiat]="true"></app-amount></td>
|
<td *ngIf="address.chain_stats.spent_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="chainStats.spent_txo_sum + mempoolStats.spent_txo_sum" [noFiat]="true"></app-amount></td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.balance">Balance</td>
|
<td i18n="address.balance">Balance</td>
|
||||||
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="received - sent" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="received - sent"></app-fiat></span></td>
|
<td *ngIf="address.chain_stats.funded_txo_sum !== undefined; else confidentialTd"><app-amount [satoshis]="chainStats.balance + mempoolStats.balance" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="chainStats.balance + mempoolStats.balance"></app-fiat></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -76,8 +76,8 @@
|
|||||||
<div class="title-tx">
|
<div class="title-tx">
|
||||||
<h2 class="text-left">
|
<h2 class="text-left">
|
||||||
<ng-template [ngIf]="!transactions?.length"> </ng-template>
|
<ng-template [ngIf]="!transactions?.length"> </ng-template>
|
||||||
<ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction</ng-template>
|
<ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ mempoolStats.tx_count + chainStats.tx_count | number }} transaction</ng-template>
|
||||||
<ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions</ng-template>
|
<ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ mempoolStats.tx_count + chainStats.tx_count | number }} transactions</ng-template>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
|
|||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { switchMap, filter, catchError, map, tap } from 'rxjs/operators';
|
import { switchMap, filter, catchError, map, tap } from 'rxjs/operators';
|
||||||
import { Address, ScriptHash, Transaction } from '../../interfaces/electrs.interface';
|
import { Address, ChainStats, Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { AudioService } from '../../services/audio.service';
|
import { AudioService } from '../../services/audio.service';
|
||||||
@ -12,6 +12,78 @@ import { SeoService } from '../../services/seo.service';
|
|||||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||||
import { AddressInformation } from '../../interfaces/node-api.interface';
|
import { AddressInformation } from '../../interfaces/node-api.interface';
|
||||||
|
|
||||||
|
class AddressStats implements ChainStats {
|
||||||
|
address: string;
|
||||||
|
scriptpubkey?: string;
|
||||||
|
funded_txo_count: number;
|
||||||
|
funded_txo_sum: number;
|
||||||
|
spent_txo_count: number;
|
||||||
|
spent_txo_sum: number;
|
||||||
|
tx_count: number;
|
||||||
|
|
||||||
|
constructor (stats: ChainStats, address: string, scriptpubkey?: string) {
|
||||||
|
Object.assign(this, stats);
|
||||||
|
this.address = address;
|
||||||
|
this.scriptpubkey = scriptpubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTx(tx: Transaction): void {
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
if (vin.prevout?.scriptpubkey_address === this.address || (this.scriptpubkey === vin.prevout?.scriptpubkey)) {
|
||||||
|
this.spendTxo(vin.prevout.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const vout of tx.vout) {
|
||||||
|
if (vout.scriptpubkey_address === this.address || (this.scriptpubkey === vout.scriptpubkey)) {
|
||||||
|
this.fundTxo(vout.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.tx_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeTx(tx: Transaction): void {
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
if (vin.prevout?.scriptpubkey_address === this.address || (this.scriptpubkey === vin.prevout?.scriptpubkey)) {
|
||||||
|
this.unspendTxo(vin.prevout.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const vout of tx.vout) {
|
||||||
|
if (vout.scriptpubkey_address === this.address || (this.scriptpubkey === vout.scriptpubkey)) {
|
||||||
|
this.unfundTxo(vout.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.tx_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
private fundTxo(value: number): void {
|
||||||
|
this.funded_txo_sum += value;
|
||||||
|
this.funded_txo_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unfundTxo(value: number): void {
|
||||||
|
this.funded_txo_sum -= value;
|
||||||
|
this.funded_txo_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
private spendTxo(value: number): void {
|
||||||
|
this.spent_txo_sum += value;
|
||||||
|
this.spent_txo_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unspendTxo(value: number): void {
|
||||||
|
this.spent_txo_sum -= value;
|
||||||
|
this.spent_txo_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
get balance(): number {
|
||||||
|
return this.funded_txo_sum - this.spent_txo_sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
get utxos(): number {
|
||||||
|
return this.funded_txo_count - this.spent_txo_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-address',
|
selector: 'app-address',
|
||||||
templateUrl: './address.component.html',
|
templateUrl: './address.component.html',
|
||||||
@ -35,9 +107,9 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
addressInfo: null | AddressInformation = null;
|
addressInfo: null | AddressInformation = null;
|
||||||
|
|
||||||
fullyLoaded = false;
|
fullyLoaded = false;
|
||||||
txCount = 0;
|
chainStats: AddressStats;
|
||||||
received = 0;
|
mempoolStats: AddressStats;
|
||||||
sent = 0;
|
|
||||||
now = Date.now() / 1000;
|
now = Date.now() / 1000;
|
||||||
balancePeriod: 'all' | '1m' = 'all';
|
balancePeriod: 'all' | '1m' = 'all';
|
||||||
|
|
||||||
@ -55,7 +127,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
this.websocketService.want(['blocks']);
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
@ -175,7 +247,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.transactions = this.tempTransactions;
|
this.transactions = this.tempTransactions;
|
||||||
if (this.transactions.length === this.txCount) {
|
if (this.transactions.length === (this.mempoolStats.tx_count + this.chainStats.tx_count)) {
|
||||||
this.fullyLoaded = true;
|
this.fullyLoaded = true;
|
||||||
}
|
}
|
||||||
this.isLoadingTransactions = false;
|
this.isLoadingTransactions = false;
|
||||||
@ -196,11 +268,13 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
this.mempoolTxSubscription = this.stateService.mempoolTransactions$
|
this.mempoolTxSubscription = this.stateService.mempoolTransactions$
|
||||||
.subscribe(tx => {
|
.subscribe(tx => {
|
||||||
this.addTransaction(tx);
|
this.addTransaction(tx);
|
||||||
|
this.mempoolStats.addTx(tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mempoolRemovedTxSubscription = this.stateService.mempoolRemovedTransactions$
|
this.mempoolRemovedTxSubscription = this.stateService.mempoolRemovedTransactions$
|
||||||
.subscribe(tx => {
|
.subscribe(tx => {
|
||||||
this.removeTransaction(tx);
|
this.removeTransaction(tx);
|
||||||
|
this.mempoolStats.removeTx(tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.blockTxSubscription = this.stateService.blockTransactions$
|
this.blockTxSubscription = this.stateService.blockTransactions$
|
||||||
@ -209,12 +283,14 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
if (tx) {
|
if (tx) {
|
||||||
tx.status = transaction.status;
|
tx.status = transaction.status;
|
||||||
this.transactions = this.transactions.slice();
|
this.transactions = this.transactions.slice();
|
||||||
|
this.mempoolStats.removeTx(transaction);
|
||||||
this.audioService.playSound('magic');
|
this.audioService.playSound('magic');
|
||||||
} else {
|
} else {
|
||||||
if (this.addTransaction(transaction, false)) {
|
if (this.addTransaction(transaction, false)) {
|
||||||
this.audioService.playSound('magic');
|
this.audioService.playSound('magic');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.chainStats.addTx(transaction);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +301,6 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.transactions.unshift(transaction);
|
this.transactions.unshift(transaction);
|
||||||
this.transactions = this.transactions.slice();
|
this.transactions = this.transactions.slice();
|
||||||
this.txCount++;
|
|
||||||
|
|
||||||
if (playSound) {
|
if (playSound) {
|
||||||
if (transaction.vout.some((vout) => vout?.scriptpubkey_address === this.address.address)) {
|
if (transaction.vout.some((vout) => vout?.scriptpubkey_address === this.address.address)) {
|
||||||
@ -235,17 +310,6 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,23 +321,11 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.transactions.splice(index, 1);
|
this.transactions.splice(index, 1);
|
||||||
this.transactions = this.transactions.slice();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMore() {
|
loadMore(): void {
|
||||||
if (this.isLoadingTransactions || this.fullyLoaded) {
|
if (this.isLoadingTransactions || this.fullyLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -301,10 +353,9 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChainStats() {
|
updateChainStats(): void {
|
||||||
this.received = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum;
|
this.chainStats = new AddressStats(this.address.chain_stats, this.address.address);
|
||||||
this.sent = this.address.chain_stats.spent_txo_sum + this.address.mempool_stats.spent_txo_sum;
|
this.mempoolStats = new AddressStats(this.address.mempool_stats, this.address.address);
|
||||||
this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setBalancePeriod(period: 'all' | '1m'): boolean {
|
setBalancePeriod(period: 'all' | '1m'): boolean {
|
||||||
@ -319,7 +370,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy(): void {
|
||||||
this.mainSubscription.unsubscribe();
|
this.mainSubscription.unsubscribe();
|
||||||
this.mempoolTxSubscription.unsubscribe();
|
this.mempoolTxSubscription.unsubscribe();
|
||||||
this.mempoolRemovedTxSubscription.unsubscribe();
|
this.mempoolRemovedTxSubscription.unsubscribe();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user