Merge branch 'master' into mononaut/sliding-difficulty

This commit is contained in:
softsimon 2024-02-15 22:57:21 +08:00 committed by GitHub
commit 5f14b32a06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 603 additions and 1138 deletions

View File

@ -398,39 +398,24 @@ class ElementsParser {
return rows;
}
// Get all of the federation addresses one month ago, most balances first
public async $getFederationAddressesOneMonthAgo(): Promise<any> {
const query = `
SELECT COUNT(*) AS addresses_count_one_month FROM (
SELECT bitcoinaddress, SUM(amount) AS balance
FROM federation_txos
WHERE
(blocktime < UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -30, CURRENT_TIMESTAMP())))
AND
((unspent = 1) OR (unspent = 0 AND lasttimeupdate > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -30, CURRENT_TIMESTAMP()))))
GROUP BY bitcoinaddress
) AS result;`;
// Get the total number of federation addresses
public async $getFederationAddressesNumber(): Promise<any> {
const query = `SELECT COUNT(DISTINCT bitcoinaddress) AS address_count FROM federation_txos WHERE unspent = 1;`;
const [rows] = await DB.query(query);
return rows[0];
}
// Get all of the UTXOs held by the federation one month ago, most recent first
public async $getFederationUtxosOneMonthAgo(): Promise<any> {
const query = `
SELECT COUNT(*) AS utxos_count_one_month FROM federation_txos
WHERE
(blocktime < UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -30, CURRENT_TIMESTAMP())))
AND
((unspent = 1) OR (unspent = 0 AND lasttimeupdate > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -30, CURRENT_TIMESTAMP()))))
ORDER BY blocktime DESC;`;
// Get the total number of federation utxos
public async $getFederationUtxosNumber(): Promise<any> {
const query = `SELECT COUNT(*) AS utxo_count FROM federation_txos WHERE unspent = 1;`;
const [rows] = await DB.query(query);
return rows[0];
}
// Get recent pegouts from the federation (3 months old)
public async $getRecentPegouts(): Promise<any> {
const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs WHERE amount < 0 AND datetime > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -90, CURRENT_TIMESTAMP())) ORDER BY blocktime;`;
const [rows] = await DB.query(query);
// Get recent pegs in / out
public async $getPegsList(count: number = 0): Promise<any> {
const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs ORDER BY block DESC LIMIT 15 OFFSET ?;`;
const [rows] = await DB.query(query, [count]);
return rows;
}
@ -443,6 +428,12 @@ class ElementsParser {
pegOutQuery[0][0]
];
}
// Get the total pegs number
public async $getPegsCount(): Promise<any> {
const [rows] = await DB.query(`SELECT COUNT(*) AS pegs_count FROM elements_pegs;`);
return rows[0];
}
}
export default new ElementsParser();

View File

@ -17,14 +17,15 @@ class LiquidRoutes {
app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', this.$getElementsPegsByMonth)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/list/:count', this.$getPegsList)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/volume', this.$getPegsVolumeDaily)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/count', this.$getPegsCount)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves', this.$getFederationReserves)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/month', this.$getFederationReservesByMonth)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegouts', this.$getPegOuts)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses', this.$getFederationAddresses)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses/previous-month', this.$getFederationAddressesOneMonthAgo)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses/total', this.$getFederationAddressesNumber)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos', this.$getFederationUtxos)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos/previous-month', this.$getFederationUtxosOneMonthAgo)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos/total', this.$getFederationUtxosNumber)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/status', this.$getFederationAuditStatus)
;
}
@ -142,12 +143,12 @@ class LiquidRoutes {
}
}
private async $getFederationAddressesOneMonthAgo(req: Request, res: Response) {
private async $getFederationAddressesNumber(req: Request, res: Response) {
try {
const federationAddresses = await elementsParser.$getFederationAddressesOneMonthAgo();
const federationAddresses = await elementsParser.$getFederationAddressesNumber();
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60 * 60 * 24).toUTCString());
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
res.json(federationAddresses);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
@ -166,25 +167,25 @@ class LiquidRoutes {
}
}
private async $getFederationUtxosOneMonthAgo(req: Request, res: Response) {
private async $getFederationUtxosNumber(req: Request, res: Response) {
try {
const federationUtxos = await elementsParser.$getFederationUtxosOneMonthAgo();
const federationUtxos = await elementsParser.$getFederationUtxosNumber();
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 60 * 60 * 24).toUTCString());
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
res.json(federationUtxos);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
private async $getPegOuts(req: Request, res: Response) {
private async $getPegsList(req: Request, res: Response) {
try {
const recentPegOuts = await elementsParser.$getRecentPegouts();
const recentPegs = await elementsParser.$getPegsList(parseInt(req.params?.count));
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
res.json(recentPegOuts);
res.json(recentPegs);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
@ -202,6 +203,18 @@ class LiquidRoutes {
}
}
private async $getPegsCount(req: Request, res: Response) {
try {
const pegsCount = await elementsParser.$getPegsCount();
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
res.json(pegsCount);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
}
export default new LiquidRoutes();

View File

@ -285,7 +285,7 @@ class StatisticsApi {
public async $list2H(): Promise<OptimizedStatistic[]> {
try {
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 120`;
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL 2 HOUR) AND NOW() ORDER BY statistics.added DESC`;
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
@ -296,7 +296,7 @@ class StatisticsApi {
public async $list24H(): Promise<OptimizedStatistic[]> {
try {
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 1440`;
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL 24 HOUR) AND NOW() ORDER BY statistics.added DESC`;
const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {

View File

@ -6,6 +6,7 @@ import statisticsApi from './statistics-api';
class Statistics {
protected intervalTimer: NodeJS.Timer | undefined;
protected lastRun: number = 0;
protected newStatisticsEntryCallback: ((stats: OptimizedStatistic) => void) | undefined;
public setNewStatisticsEntryCallback(fn: (stats: OptimizedStatistic) => void) {
@ -23,15 +24,21 @@ class Statistics {
setTimeout(() => {
this.runStatistics();
this.intervalTimer = setInterval(() => {
this.runStatistics();
this.runStatistics(true);
}, 1 * 60 * 1000);
}, difference);
}
private async runStatistics(): Promise<void> {
public async runStatistics(skipIfRecent = false): Promise<void> {
if (!memPool.isInSync()) {
return;
}
if (skipIfRecent && new Date().getTime() / 1000 - this.lastRun < 30) {
return;
}
this.lastRun = new Date().getTime() / 1000;
const currentMempool = memPool.getMempool();
const txPerSecond = memPool.getTxPerSecond();
const vBytesPerSecond = memPool.getVBytesPerSecond();

View File

@ -23,6 +23,7 @@ import priceUpdater from '../tasks/price-updater';
import { ApiPrice } from '../repositories/PricesRepository';
import accelerationApi from './services/acceleration';
import mempool from './mempool';
import statistics from './statistics/statistics';
interface AddressTransactions {
mempool: MempoolTransactionExtended[],
@ -723,6 +724,7 @@ class WebsocketHandler {
}
this.printLogs();
await statistics.runStatistics();
const _memPool = memPool.getMempool();
@ -1014,6 +1016,8 @@ class WebsocketHandler {
client.send(this.serializeResponse(response));
}
});
await statistics.runStatistics();
}
// takes a dictionary of JSON serialized values

View File

@ -1,4 +1,4 @@
<div id="become-sponsor-container">
<div id="become-sponsor-container" [ngClass]="context">
<div class="become-sponsor community">
<p style="font-weight: 700; font-size: 18px;">If you're an individual...</p>
<a [href]="host + '/sponsor'" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.community-sponsor-button" (click)="onSponsorClick($event)">Become a Community Sponsor</a>

View File

@ -6,6 +6,11 @@
align-items: center;
gap: 20px;
margin: 68px auto;
text-align: center;
}
#become-sponsor-container.account {
margin: 20px auto;
}
.become-sponsor {

View File

@ -8,6 +8,7 @@ import { EnterpriseService } from '../../services/enterprise.service';
})
export class AboutSponsorsComponent {
@Input() host = 'https://mempool.space';
@Input() context = 'about';
constructor(private enterpriseService: EnterpriseService) {
}

View File

@ -78,10 +78,7 @@
<li class="nav-item" routerLinkActive="active" id="btn-assets">
<a class="nav-link" [routerLink]="['/assets' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'database']" [fixedWidth]="true" i18n-title="master-page.assets" title="Assets"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-audit">
<a class="nav-link" [routerLink]="['/audit']" (click)="collapse()"><fa-icon [icon]="['fas', 'scale-balanced']" [fixedWidth]="true" i18n-title="master-page.btc-reserves-audit" title="BTC Reserves Audit"></fa-icon></a>
</li>
<li [hidden]="isMobile" class="nav-item mr-2" routerLinkActive="active" id="btn-docs">
<li class="nav-item mr-2" routerLinkActive="active" id="btn-docs">
<a class="nav-link" [routerLink]="['/docs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'book']" [fixedWidth]="true" i18n-title="master-page.docs" title="Docs"></fa-icon></a>
</li>
<li class="nav-item" routerLinkActive="active" id="btn-about">

View File

@ -23,6 +23,11 @@ li.nav-item {
margin: auto 10px;
padding-left: 10px;
padding-right: 10px;
@media (max-width: 429px) {
margin: auto 5px;
padding-left: 6px;
padding-right: 6px;
}
}
@media (min-width: 992px) {

View File

@ -6,11 +6,14 @@
tr, td, th {
border: 0px;
padding-top: 0.65rem !important;
padding-bottom: 0.6rem !important;
padding-right: 2rem !important;
.widget {
padding-right: 1rem !important;
padding-top: 0.65rem;
padding-bottom: 0.6rem;
padding-right: 2rem;
.widget &.widget {
padding-right: 1rem;
@media (max-width: 510px) {
padding-right: 0.5rem;
}
}
}

View File

@ -1,15 +1,12 @@
<div *ngIf="(federationAddresses$ | async) as federationAddresses; else loadingData">
<div *ngIf="(federationWalletStats$ | async) as federationWalletStats; else loadingData">
<div class="fee-estimation-container">
<div class="item">
<a class="title-link" [routerLink]="['/audit/wallet/addresses' | relativeUrl]">
<h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container>&nbsp;<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
</a>
<div class="card-text">
<div class="fee-text">{{ federationAddresses.length }} <span i18n="shared.addresses">addresses</span></div>
<span class="fiat" *ngIf="(federationAddressesOneMonthAgo$ | async) as federationAddressesOneMonthAgo; else loadingSkeleton" i18n-ngbTooltip="liquid.percentage-change-last-month" ngbTooltip="Percentage change past month" placement="bottom">
<app-change [current]="federationAddresses.length" [previous]="federationAddressesOneMonthAgo.addresses_count_one_month"></app-change>
</span>
<div class="fee-text">{{ federationWalletStats.address_count }} <span i18n="shared.addresses">addresses</span></div>
<div class="fiat">{{ federationWalletStats.utxo_count }} <span i18n="shared.utxos">UTXOs</span></div>
</div>
</div>
</div>
@ -30,5 +27,5 @@
</ng-template>
<ng-template #loadingSkeleton>
<div class="skeleton-loader skeleton-loader-transactions" style="margin-top: 2px; margin-bottom: 5px;"></div>
<div class="skeleton-loader skeleton-loader-transactions" style="margin-top: 8px; margin-bottom: 8px;"></div>
</ng-template>

View File

@ -1,6 +1,7 @@
.fee-estimation-container {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
@media (min-width: 376px) {
flex-direction: row;
}
@ -21,6 +22,7 @@
}
.card-text {
padding-top: 9px;
font-size: 22px;
span {
font-size: 11px;
@ -48,10 +50,6 @@
}
}
.loading-container{
min-height: 76px;
}
.card-text {
.skeleton-loader {
width: 100%;

View File

@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { FederationAddress } from '../../../interfaces/node-api.interface';
import { Observable, combineLatest, map, of } from 'rxjs';
@Component({
selector: 'app-federation-addresses-stats',
@ -9,12 +8,24 @@ import { FederationAddress } from '../../../interfaces/node-api.interface';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FederationAddressesStatsComponent implements OnInit {
@Input() federationAddresses$: Observable<FederationAddress[]>;
@Input() federationAddressesOneMonthAgo$: Observable<any>;
@Input() federationAddressesNumber$: Observable<number>;
@Input() federationUtxosNumber$: Observable<number>;
federationWalletStats$: Observable<any>;
constructor() { }
ngOnInit(): void {
this.federationWalletStats$ = combineLatest([
this.federationAddressesNumber$ ?? of(undefined),
this.federationUtxosNumber$ ?? of(undefined)
]).pipe(
map(([address_count, utxo_count]) => {
if (address_count === undefined || utxo_count === undefined) {
return undefined;
}
return { address_count, utxo_count}
})
)
}
}

View File

@ -1,5 +1,4 @@
<div class="container-xl">
<div [ngClass]="{'widget': widget}">
<div [ngClass]="{'container-xl': !widget, 'widget': widget}">
<div *ngIf="!widget">
<h1 i18n="liquid.recent-pegs">Recent Peg-In / Out's</h1>
@ -16,7 +15,7 @@
<th class="output text-left" *ngIf="!widget" i18n="liquid.fund-redemption-tx">Fund / Redemption Tx</th>
<th class="address text-left" *ngIf="!widget" i18n="liquid.bitcoin-address">BTC Address</th>
</thead>
<tbody *ngIf="recentPegs$ | async as pegs; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
<tbody *ngIf="recentPegsList$ | async as pegs; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
<ng-container *ngIf="widget; else regularRows">
<tr *ngFor="let peg of pegs | slice:0:5">
<td class="transaction text-left widget">
@ -40,7 +39,7 @@
</tr>
</ng-container>
<ng-template #regularRows>
<tr *ngFor="let peg of pegs | slice:(page - 1) * pageSize:page * pageSize">
<tr *ngFor="let peg of pegs;">
<td class="transaction text-left">
<ng-container *ngIf="peg.amount > 0">
<a [routerLink]="['/tx' | relativeUrl, peg.txid]" [fragment]="'vin=' + peg.txindex">
@ -102,7 +101,7 @@
<span class="skeleton-loader" style="max-width: 300px"></span>
</td>
<td class="timestamp text-left">
<span class="skeleton-loader" style="max-width: 140px"></span>
<span class="skeleton-loader" style="max-width: 240px"></span>
</td>
<td class="amount text-right">
<span class="skeleton-loader" style="max-width: 140px"></span>
@ -111,15 +110,15 @@
<span class="skeleton-loader" style="max-width: 300px"></span>
</td>
<td class="address text-left">
<span class="skeleton-loader" style="max-width: 140px"></span>
<span class="skeleton-loader" style="max-width: 240px"></span>
</td>
</tr>
</ng-template>
</ng-template>
</table>
<ngb-pagination *ngIf="!widget && recentPegs$ | async as pegs" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
[collectionSize]="pegs.length" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
<ngb-pagination *ngIf="!widget && pegsCount$ | async as pegsCount" class="pagination-container float-right mt-2" [class]="isLoading || isPegCountLoading ? 'disabled' : ''"
[collectionSize]="pegsCount" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
</ngb-pagination>
@ -129,7 +128,6 @@
</ng-template>
</div>
</div>
</div>
<br>

View File

@ -6,11 +6,14 @@
tr, td, th {
border: 0px;
padding-top: 0.65rem !important;
padding-bottom: 0.6rem !important;
padding-right: 2rem !important;
.widget {
padding-right: 1rem !important;
padding-top: 0.65rem;
padding-bottom: 0.6rem;
padding-right: 2rem;
.widget &.widget {
padding-right: 1rem;
@media (max-width: 510px) {
padding-right: 0.5rem;
}
}
}
@ -93,7 +96,7 @@ tr, td, th {
display: block;
}
@media (max-width: 500px) {
@media (max-width: 510px) {
display: none;
}
}

View File

@ -1,9 +1,9 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { Observable, Subject, combineLatest, of, timer } from 'rxjs';
import { BehaviorSubject, Observable, Subject, combineLatest, of, timer } from 'rxjs';
import { delayWhen, filter, map, share, shareReplay, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
import { ApiService } from '../../../services/api.service';
import { Env, StateService } from '../../../services/state.service';
import { AuditStatus, CurrentPegs, FederationUtxo, RecentPeg } from '../../../interfaces/node-api.interface';
import { AuditStatus, CurrentPegs, RecentPeg } from '../../../interfaces/node-api.interface';
import { WebsocketService } from '../../../services/websocket.service';
import { SeoService } from '../../../services/seo.service';
@ -15,21 +15,22 @@ import { SeoService } from '../../../services/seo.service';
})
export class RecentPegsListComponent implements OnInit {
@Input() widget: boolean = false;
@Input() recentPegIns$: Observable<RecentPeg[]> = of([]);
@Input() recentPegOuts$: Observable<RecentPeg[]> = of([]);
@Input() recentPegsList$: Observable<RecentPeg[]>;
env: Env;
isLoading = true;
isPegCountLoading = true;
page = 1;
pageSize = 15;
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
skeletonLines: number[] = [];
auditStatus$: Observable<AuditStatus>;
auditUpdated$: Observable<boolean>;
federationUtxos$: Observable<FederationUtxo[]>;
recentPegs$: Observable<RecentPeg[]>;
lastReservesBlockUpdate: number = 0;
currentPeg$: Observable<CurrentPegs>;
pegsCount$: Observable<number>;
startingIndexSubject: BehaviorSubject<number> = new BehaviorSubject(0);
currentIndex: number = 0;
lastPegBlockUpdate: number = 0;
lastPegAmount: string = '';
isLoad: boolean = true;
@ -93,53 +94,36 @@ export class RecentPegsListComponent implements OnInit {
share()
);
this.federationUtxos$ = this.auditUpdated$.pipe(
this.pegsCount$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.federationUtxos$()),
tap(() => this.isPegCountLoading = true),
switchMap(_ => this.apiService.pegsCount$()),
map((data) => data.pegs_count),
tap(() => this.isPegCountLoading = false),
share()
);
this.recentPegIns$ = this.federationUtxos$.pipe(
map(federationUtxos => federationUtxos.filter(utxo => utxo.pegtxid).map(utxo => {
return {
txid: utxo.pegtxid,
txindex: utxo.pegindex,
amount: utxo.amount,
bitcoinaddress: utxo.bitcoinaddress,
bitcointxid: utxo.txid,
bitcoinindex: utxo.txindex,
blocktime: utxo.pegblocktime,
}
})),
share()
);
this.recentPegOuts$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.recentPegOuts$()),
share()
);
}
this.recentPegs$ = combineLatest([
this.recentPegIns$,
this.recentPegOuts$
this.recentPegsList$ = combineLatest([
this.auditStatus$,
this.auditUpdated$,
this.startingIndexSubject
]).pipe(
map(([recentPegIns, recentPegOuts]) => {
return [
...recentPegIns,
...recentPegOuts
].sort((a, b) => {
return b.blocktime - a.blocktime;
});
filter(([auditStatus, auditUpdated, startingIndex]) => {
const auditStatusCheck = auditStatus.isAuditSynced === true;
const auditUpdatedCheck = auditUpdated === true;
const startingIndexCheck = startingIndex !== this.currentIndex;
return auditStatusCheck && (auditUpdatedCheck || startingIndexCheck);
}),
filter(recentPegs => recentPegs.length > 0),
tap(_ => this.isLoading = false),
tap(([_, __, startingIndex]) => {
this.currentIndex = startingIndex;
this.isLoading = true;
}),
switchMap(([_, __, startingIndex]) => this.apiService.recentPegsList$(startingIndex)),
tap(() => this.isLoading = false),
share()
);
}
}
ngOnDestroy(): void {
@ -148,6 +132,7 @@ export class RecentPegsListComponent implements OnInit {
}
pageChange(page: number): void {
this.startingIndexSubject.next((page - 1) * 15);
this.page = page;
}

View File

@ -1,6 +1,7 @@
.fee-estimation-container {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
@media (min-width: 376px) {
flex-direction: row;
}

View File

@ -1,98 +0,0 @@
<div class="container-xl dashboard-container" *ngIf="(auditStatus$ | async)?.isAuditSynced; else auditInProgress">
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card">
<div class="card-body">
<app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats>
<app-reserves-ratio [currentPeg]="currentPeg$ | async" [currentReserves]="currentReserves$ | async"></app-reserves-ratio>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card">
<div class="card-title">
<app-reserves-ratio-stats [fullHistory$]="fullHistory$"></app-reserves-ratio-stats>
</div>
<div class="card-body pl-0" style="padding-top: 10px;">
<app-reserves-ratio-graph [data]="fullHistory$ | async"></app-reserves-ratio-graph>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<app-recent-pegs-stats [pegsVolume$]="pegsVolume$"></app-recent-pegs-stats>
<app-recent-pegs-list [recentPegIns$]="recentPegIns$" [recentPegOuts$]="recentPegOuts$"[widget]="true"></app-recent-pegs-list>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card">
<div class="card-body">
<app-federation-addresses-stats [federationAddresses$]="federationAddresses$" [federationAddressesOneMonthAgo$]="federationAddressesOneMonthAgo$"></app-federation-addresses-stats>
<app-federation-addresses-list [federationAddresses$]="federationAddresses$" [widget]="true"></app-federation-addresses-list>
</div>
</div>
</div>
</div>
</div>
<ng-template #loadingSkeleton>
<div class="container-xl dashboard-container">
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card">
<div class="card-body">
<app-reserves-supply-stats></app-reserves-supply-stats>
<app-reserves-ratio></app-reserves-ratio>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card">
<div class="card-title">
<app-reserves-ratio-stats></app-reserves-ratio-stats>
</div>
<div class="card-body pl-0" style="padding-top: 10px;">
<app-reserves-ratio-graph></app-reserves-ratio-graph>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<app-recent-pegs-stats></app-recent-pegs-stats>
<app-recent-pegs-list [widget]="true"></app-recent-pegs-list>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card">
<div class="card-body">
<app-federation-addresses-stats></app-federation-addresses-stats>
<app-federation-addresses-list [widget]="true"></app-federation-addresses-list>
</div>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template #auditInProgress>
<ng-container *ngIf="(auditStatus$ | async) as auditStatus; else loadingSkeleton">
<div class="in-progress-message" *ngIf="auditStatus.lastBlockAudit && auditStatus.bitcoinHeaders; else loadingSkeleton">
<span i18n="liquid.audit-in-progress">Audit in progress: Bitcoin block height #{{ auditStatus.lastBlockAudit }} / #{{ auditStatus.bitcoinHeaders }}</span>
</div>
</ng-container>
</ng-template>

View File

@ -1,138 +0,0 @@
.dashboard-container {
text-align: center;
margin-top: 0.5rem;
.col {
margin-bottom: 1.5rem;
}
}
.card {
background-color: #1d1f31;
}
.card-title {
padding-top: 20px;
}
.card-body.pool-ranking {
padding: 1.25rem 0.25rem 0.75rem 0.25rem;
}
.card-text {
font-size: 22px;
}
#blockchain-container {
position: relative;
overflow-x: scroll;
overflow-y: hidden;
scrollbar-width: none;
-ms-overflow-style: none;
}
#blockchain-container::-webkit-scrollbar {
display: none;
}
.fade-border {
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 80%, transparent 100%)
}
.in-progress-message {
position: relative;
color: #ffffff91;
margin-top: 20px;
text-align: center;
padding-bottom: 3px;
font-weight: 500;
}
.more-padding {
padding: 24px 20px !important;
}
.card-wrapper {
.card {
height: auto !important;
}
.card-body {
display: flex;
flex: inherit;
text-align: center;
flex-direction: column;
justify-content: space-around;
padding: 22px 20px;
}
}
.skeleton-loader {
width: 100%;
display: block;
&:first-child {
max-width: 90px;
margin: 15px auto 3px;
}
&:last-child {
margin: 10px auto 3px;
max-width: 55px;
}
}
.card-text {
font-size: 22px;
}
.title-link, .title-link:hover, .title-link:focus, .title-link:active {
display: block;
margin-bottom: 10px;
text-decoration: none;
color: inherit;
}
.lastest-blocks-table {
width: 100%;
text-align: left;
tr, td, th {
border: 0px;
padding-top: 0.65rem !important;
padding-bottom: 0.8rem !important;
}
.table-cell-height {
width: 25%;
}
.table-cell-fee {
width: 25%;
text-align: right;
}
.table-cell-pool {
text-align: left;
width: 30%;
@media (max-width: 875px) {
display: none;
}
.pool-name {
margin-left: 1em;
}
}
.table-cell-acceleration-count {
text-align: right;
width: 20%;
}
}
.card {
height: 385px;
}
.list-card {
height: 410px;
@media (max-width: 767px) {
height: auto;
}
}
.mempool-block-wrapper {
max-height: 380px;
max-width: 380px;
margin: auto;
}

View File

@ -1,212 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { SeoService } from '../../../services/seo.service';
import { WebsocketService } from '../../../services/websocket.service';
import { StateService } from '../../../services/state.service';
import { Observable, Subject, combineLatest, delayWhen, filter, interval, map, of, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime, timer } from 'rxjs';
import { ApiService } from '../../../services/api.service';
import { AuditStatus, CurrentPegs, FederationAddress, FederationUtxo, PegsVolume, RecentPeg } from '../../../interfaces/node-api.interface';
@Component({
selector: 'app-reserves-audit-dashboard',
templateUrl: './reserves-audit-dashboard.component.html',
styleUrls: ['./reserves-audit-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReservesAuditDashboardComponent implements OnInit {
auditStatus$: Observable<AuditStatus>;
auditUpdated$: Observable<boolean>;
currentPeg$: Observable<CurrentPegs>;
currentReserves$: Observable<CurrentPegs>;
federationUtxos$: Observable<FederationUtxo[]>;
recentPegIns$: Observable<RecentPeg[]>;
recentPegOuts$: Observable<RecentPeg[]>;
pegsVolume$: Observable<PegsVolume[]>;
federationAddresses$: Observable<FederationAddress[]>;
federationAddressesOneMonthAgo$: Observable<any>;
liquidPegsMonth$: Observable<any>;
liquidReservesMonth$: Observable<any>;
fullHistory$: Observable<any>;
isLoad: boolean = true;
private lastPegBlockUpdate: number = 0;
private lastPegAmount: string = '';
private lastReservesBlockUpdate: number = 0;
private destroy$ = new Subject();
constructor(
private seoService: SeoService,
private websocketService: WebsocketService,
private apiService: ApiService,
private stateService: StateService,
) {
this.seoService.setTitle($localize`:@@liquid.reserves-audit:Reserves Audit Dashboard`);
}
ngOnInit(): void {
this.websocketService.want(['blocks', 'mempool-blocks']);
this.auditStatus$ = this.stateService.blocks$.pipe(
takeUntil(this.destroy$),
throttleTime(40000),
delayWhen(_ => this.isLoad ? timer(0) : timer(2000)),
tap(() => this.isLoad = false),
switchMap(() => this.apiService.federationAuditSynced$()),
shareReplay(1),
);
this.currentPeg$ = this.auditStatus$.pipe(
filter(auditStatus => auditStatus.isAuditSynced === true),
switchMap(_ =>
this.apiService.liquidPegs$().pipe(
filter((currentPegs) => currentPegs.lastBlockUpdate >= this.lastPegBlockUpdate),
tap((currentPegs) => {
this.lastPegBlockUpdate = currentPegs.lastBlockUpdate;
})
)
),
share()
);
this.auditUpdated$ = combineLatest([
this.auditStatus$,
this.currentPeg$
]).pipe(
filter(([auditStatus, _]) => auditStatus.isAuditSynced === true),
map(([auditStatus, currentPeg]) => ({
lastBlockAudit: auditStatus.lastBlockAudit,
currentPegAmount: currentPeg.amount
})),
switchMap(({ lastBlockAudit, currentPegAmount }) => {
const blockAuditCheck = lastBlockAudit > this.lastReservesBlockUpdate;
const amountCheck = currentPegAmount !== this.lastPegAmount;
this.lastPegAmount = currentPegAmount;
return of(blockAuditCheck || amountCheck);
}),
share()
);
this.currentReserves$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ =>
this.apiService.liquidReserves$().pipe(
filter((currentReserves) => currentReserves.lastBlockUpdate >= this.lastReservesBlockUpdate),
tap((currentReserves) => {
this.lastReservesBlockUpdate = currentReserves.lastBlockUpdate;
})
)
),
share()
);
this.federationUtxos$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.federationUtxos$()),
share()
);
this.recentPegIns$ = this.federationUtxos$.pipe(
map(federationUtxos => federationUtxos.filter(utxo => utxo.pegtxid).map(utxo => {
return {
txid: utxo.pegtxid,
txindex: utxo.pegindex,
amount: utxo.amount,
bitcoinaddress: utxo.bitcoinaddress,
bitcointxid: utxo.txid,
bitcoinindex: utxo.txindex,
blocktime: utxo.pegblocktime,
}
})),
share()
);
this.recentPegOuts$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.recentPegOuts$()),
share()
);
this.pegsVolume$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.pegsVolume$()),
share()
);
this.federationAddresses$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.federationAddresses$()),
share()
);
this.federationAddressesOneMonthAgo$ = interval(60 * 60 * 1000)
.pipe(
startWith(0),
switchMap(() => this.apiService.federationAddressesOneMonthAgo$())
);
this.liquidPegsMonth$ = interval(60 * 60 * 1000)
.pipe(
startWith(0),
switchMap(() => this.apiService.listLiquidPegsMonth$()),
map((pegs) => {
const labels = pegs.map(stats => stats.date);
const series = pegs.map(stats => parseFloat(stats.amount) / 100000000);
series.reduce((prev, curr, i) => series[i] = prev + curr, 0);
return {
series,
labels
};
}),
share(),
);
this.liquidReservesMonth$ = interval(60 * 60 * 1000).pipe(
startWith(0),
switchMap(() => this.apiService.listLiquidReservesMonth$()),
map(reserves => {
const labels = reserves.map(stats => stats.date);
const series = reserves.map(stats => parseFloat(stats.amount) / 100000000);
return {
series,
labels
};
}),
share()
);
this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$, this.currentReserves$])
.pipe(
map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => {
liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000;
if (liquidPegs.series.length === liquidReserves?.series.length) {
liquidReserves.series[liquidReserves.series.length - 1] = parseFloat(currentReserves?.amount) / 100000000;
} else if (liquidPegs.series.length === liquidReserves?.series.length + 1) {
liquidReserves.series.push(parseFloat(currentReserves?.amount) / 100000000);
liquidReserves.labels.push(liquidPegs.labels[liquidPegs.labels.length - 1]);
} else {
liquidReserves = {
series: [],
labels: []
};
}
return {
liquidPegs,
liquidReserves
};
}),
share()
);
}
ngOnDestroy(): void {
this.destroy$.next(1);
this.destroy$.complete();
}
}

View File

@ -14,7 +14,7 @@
<h5 class="card-title" i18n="liquid.avg-peg-ratio">Avg Peg Ratio</h5>
<div class="card-text">
<div class="fee-text" [ngClass]="{'danger' : unbackedMonths.avg < 1, 'correct': unbackedMonths.avg >= 1}">
{{ unbackedMonths.avg.toFixed(5) }}
{{ (unbackedMonths.avg * 100).toFixed(3) }} %
</div>
</div>
</div>

View File

@ -1,4 +0,0 @@
<div class="echarts" echarts [initOpts]="ratioHistoryChartInitOptions" [options]="ratioHistoryChartOptions" (chartRendered)="rendered()"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@ -1,6 +0,0 @@
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 16px);
z-index: 100;
}

View File

@ -1,195 +0,0 @@
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
import { formatDate, formatNumber } from '@angular/common';
import { EChartsOption } from '../../../graphs/echarts';
@Component({
selector: 'app-reserves-ratio-graph',
templateUrl: './reserves-ratio-graph.component.html',
styleUrls: ['./reserves-ratio-graph.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReservesRatioGraphComponent implements OnInit, OnChanges {
@Input() data: any;
ratioHistoryChartOptions: EChartsOption;
ratioSeries: number[] = [];
height: number | string = '200';
right: number | string = '10';
top: number | string = '20';
left: number | string = '50';
template: ('widget' | 'advanced') = 'widget';
isLoading = true;
ratioHistoryChartInitOptions = {
renderer: 'svg'
};
constructor(
@Inject(LOCALE_ID) private locale: string,
) { }
ngOnInit() {
this.isLoading = true;
}
ngOnChanges() {
if (!this.data) {
return;
}
// Compute the ratio series: the ratio of the reserves to the pegs
this.ratioSeries = this.data.liquidReserves.series.map((value: number, index: number) => value / this.data.liquidPegs.series[index]);
// Truncate the ratio series and labels series to last 3 years
this.ratioSeries = this.ratioSeries.slice(Math.max(this.ratioSeries.length - 36, 0));
this.data.liquidPegs.labels = this.data.liquidPegs.labels.slice(Math.max(this.data.liquidPegs.labels.length - 36, 0));
// Cut the values that are too high or too low
this.ratioSeries = this.ratioSeries.map((value: number) => Math.min(Math.max(value, 0.995), 1.005));
this.ratioHistoryChartOptions = this.createChartOptions(this.ratioSeries, this.data.liquidPegs.labels);
}
rendered() {
if (!this.data) {
return;
}
this.isLoading = false;
}
createChartOptions(ratioSeries: number[], labels: string[]): EChartsOption {
return {
grid: {
height: this.height,
right: this.right,
top: this.top,
left: this.left,
},
animation: false,
dataZoom: [{
type: 'inside',
realtime: true,
zoomOnMouseWheel: (this.template === 'advanced') ? true : false,
maxSpan: 100,
minSpan: 10,
}, {
show: (this.template === 'advanced') ? true : false,
type: 'slider',
brushSelect: false,
realtime: true,
selectedDataBackground: {
lineStyle: {
color: '#fff',
opacity: 0.45,
},
areaStyle: {
opacity: 0,
}
}
}],
tooltip: {
trigger: 'axis',
position: (pos, params, el, elRect, size) => {
const obj = { top: -20 };
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 80;
return obj;
},
extraCssText: `width: ${(this.template === 'widget') ? '125px' : '135px'};
background: transparent;
border: none;
box-shadow: none;`,
axisPointer: {
type: 'line',
},
formatter: (params: any) => {
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ${color};"></span>`;
let itemFormatted = '<div class="title">' + params[0].axisValue + '</div>';
const item = params[0];
const formattedValue = formatNumber(item.value, this.locale, '1.5-5');
const symbol = (item.value === 1.005) ? '≥ ' : (item.value === 0.995) ? '≤ ' : '';
itemFormatted += `<div class="item">
<div class="indicator-container">${colorSpan(item.color)}</div>
<div style="margin-right: 5px"></div>
<div class="value">${symbol}${formattedValue}</div>
</div>`;
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}">${itemFormatted}</div>`;
}
},
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
fontSize: 11,
lineHeight: 12
},
boundaryGap: false,
data: labels.map((value: any) => `${formatDate(value, 'MMM\ny', this.locale)}`),
},
yAxis: {
type: 'value',
axisLabel: {
fontSize: 11,
},
splitLine: {
lineStyle: {
type: 'dotted',
color: '#ffffff66',
opacity: 0.25,
}
},
min: 0.995,
max: 1.005,
},
series: [
{
data: ratioSeries,
name: '',
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
width: 3,
},
markLine: {
silent: true,
symbol: 'none',
lineStyle: {
color: '#fff',
opacity: 1,
width: 1,
},
data: [{
yAxis: 1,
label: {
show: false,
color: '#ffffff',
}
}],
},
},
],
visualMap: {
show: false,
top: 50,
right: 10,
pieces: [{
gt: 0,
lte: 0.999,
color: '#D81B60'
},
{
gt: 0.999,
lte: 1.001,
color: '#FDD835'
},
{
gt: 1.001,
lte: 2,
color: '#7CB342'
}
],
outOfRange: {
color: '#999'
}
},
};
}
}

View File

@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, OnChanges, OnInit, HostListener } from '@angular/core';
import { EChartsOption } from '../../../graphs/echarts';
import { CurrentPegs } from '../../../interfaces/node-api.interface';
@ -32,6 +32,10 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
}
ngOnChanges() {
this.updateChartOptions();
}
updateChartOptions() {
if (!this.currentPeg || !this.currentReserves || this.currentPeg.amount === '0') {
return;
}
@ -46,13 +50,43 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
}
createChartOptions(currentPeg: CurrentPegs, currentReserves: CurrentPegs): EChartsOption {
const value = parseFloat(currentReserves.amount) / parseFloat(currentPeg.amount);
const hideMaxAxisLabels = value >= 1.001;
const hideMinAxisLabels = value <= 0.999;
let axisFontSize = 14;
let pointerLength = '50%';
let pointerWidth = 16;
let offsetCenter = ['0%', '-22%'];
if (window.innerWidth >= 992) {
axisFontSize = 14;
pointerLength = '50%';
pointerWidth = 16;
offsetCenter = value >= 1.0007 || value <= 0.9993 ? ['0%', '-30%'] : ['0%', '-22%'];
} else if (window.innerWidth >= 768) {
axisFontSize = 10;
pointerLength = '35%';
pointerWidth = 12;
offsetCenter = value >= 1.0007 || value <= 0.9993 ? ['0%', '-37%'] : ['0%', '-27%'];
} else if (window.innerWidth >= 450) {
axisFontSize = 14;
pointerLength = '45%';
pointerWidth = 14;
offsetCenter = value >= 1.0007 || value <= 0.9993 ? ['0%', '-32%'] : ['0%', '-22%'];
} else {
axisFontSize = 10;
pointerLength = '35%';
pointerWidth = 12;
offsetCenter = value >= 1.0007 || value <= 0.9993 ? ['0%', '-37%'] : ['0%', '-27%'];
}
return {
series: [
{
type: 'gauge',
startAngle: 180,
endAngle: 0,
center: ['50%', '70%'],
center: ['50%', '75%'],
radius: '100%',
min: 0.999,
max: 1.001,
@ -69,12 +103,22 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
axisLabel: {
color: 'inherit',
fontFamily: 'inherit',
fontSize: axisFontSize,
formatter: function (value) {
if (value === 0.999) {
return hideMinAxisLabels ? '' : '99.9%';
} else if (value === 1.001) {
return hideMaxAxisLabels ? '' : '100.1%';
} else {
return '100%';
}
},
},
pointer: {
icon: 'path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z',
length: '50%',
width: 16,
offsetCenter: [0, '-27%'],
length: pointerLength,
width: pointerWidth,
offsetCenter: offsetCenter,
itemStyle: {
color: 'auto'
}
@ -95,7 +139,7 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
},
title: {
show: true,
offsetCenter: [0, '-117.5%'],
offsetCenter: [0, '-127%'],
fontSize: 18,
color: '#4a68b9',
fontFamily: 'inherit',
@ -108,19 +152,24 @@ export class ReservesRatioComponent implements OnInit, OnChanges {
fontFamily: 'inherit',
fontWeight: 500,
formatter: function (value) {
return (value).toFixed(5);
return (value * 100).toFixed(3) + '%';
},
color: 'inherit'
},
data: [
{
value: parseFloat(currentReserves.amount) / parseFloat(currentPeg.amount),
name: 'Peg-O-Meter'
value: value,
name: 'Assets vs Liabilities'
}
]
}
]
};
}
@HostListener('window:resize', ['$event'])
onResize(): void {
this.updateChartOptions();
}
}

View File

@ -1,44 +1,29 @@
<div *ngIf="(currentPeg$ | async) as currentPeg; else loadingData">
<div *ngIf="(currentReserves$ | async) as currentReserves; else loadingData">
<div class="fee-estimation-container">
<div class="fee-estimation-container">
<div class="item">
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
<div class="card-text">
<div *ngIf="(currentPeg$ | async) as currentPeg; else loadingData" class="card-text">
<div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div>
<span class="fiat">
<span>As of block&nbsp;<a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span>
<span><ng-container i18n="shared.as-of-block">As of block</ng-container>&nbsp;<a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span>
</span>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="dashboard.btc-reserves">BTC Reserves</h5>
<div class="card-text">
<h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5>
<div *ngIf="(currentReserves$ | async) as currentReserves; else loadingData" class="card-text">
<div class="fee-text">{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} <span style="color: #b86d12;">BTC</span></div>
<span class="fiat">
<span>As of block&nbsp;<a href="{{ env.MEMPOOL_WEBSITE_URL + '/block/' + currentReserves.hash }}" target="_blank" style="color:#b86d12">{{ currentReserves.lastBlockUpdate }}</a></span>
<span><ng-container i18n="shared.as-of-block">As of block</ng-container>&nbsp;<a href="{{ env.MEMPOOL_WEBSITE_URL + '/block/' + currentReserves.hash }}" target="_blank" style="color:#b86d12">{{ currentReserves.lastBlockUpdate }}</a></span>
</span>
</div>
</div>
</div>
</div>
</div>
<ng-template #loadingData>
<div class="fee-estimation-container loading-container">
<div class="item">
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
<div class="item">
<h5 class="card-title" i18n="dashboard.btc-reserves">BTC Reserves</h5>
<div class="card-text">
<div class="skeleton-loader"></div>
<div class="skeleton-loader"></div>
</div>
</div>
</div>
</ng-template>

View File

@ -41,6 +41,7 @@
}
.fee-text{
border-bottom: 1px solid #ffffff1c;
color: #ffffff;
width: fit-content;
margin: auto;
line-height: 1.45;

View File

@ -1,7 +1,7 @@
<div class="container-xl dashboard-container">
<div class="container-xl dashboard-container" *ngIf="(network$ | async) !== 'liquid'; else liquidDashboard">
<div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData">
<ng-container *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'">
<ng-container *ngIf="(network$ | async) !== 'liquidtestnet'">
<div class="col card-wrapper">
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
<div class="card">
@ -17,7 +17,6 @@
<div class="col">
<div class="card graph-card">
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
<ng-template [ngIf]="(network$ | async) !== 'liquid'" [ngIfElse]="liquidPegs">
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
<h5 class="card-title d-inline"><span i18n="dashboard.mempool-goggles">Mempool Goggles</span>: {{ goggleCycle[goggleIndex].name }}</h5>
<span>&nbsp;</span>
@ -38,14 +37,6 @@
[filterMode]="goggleMode"
></app-mempool-block-overview>
</div>
</ng-template>
<ng-template #liquidPegs>
<div style="padding-left: 1.25rem;">
<ng-container *ngTemplateOutlet="stateService.network === 'liquid' ? lbtcPegs : mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
<hr>
</div>
<app-lbtc-pegs-graph [data]="fullHistory$ | async" [height]="lbtcPegGraphHeight"></app-lbtc-pegs-graph>
</ng-template>
</div>
</div>
</div>
@ -53,7 +44,6 @@
<div class="card graph-card">
<div class="card-body">
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
<ng-container *ngIf="stateService.network !== 'liquid'">
<h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
<div class="mempool-graph" *ngIf="{ value: (mempoolStats$ | async) } as mempoolStats">
<app-incoming-transactions-graph
@ -64,30 +54,11 @@
[windowPreferenceOverride]="'2h'"
></app-incoming-transactions-graph>
</div>
</ng-container>
<div class="mempool-graph" *ngIf="stateService.network === 'liquid'">
<hr>
<table class="table table-borderless table-striped" *ngIf="(featuredAssets$ | async) as featuredAssets else loadingAssetsTable">
<tbody>
<tr *ngFor="let group of featuredAssets">
<td class="asset-icon">
<a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">
<img class="assetIcon" [src]="'/api/v1/asset/' + group.asset + '/icon'">
</a>
</td>
<td class="asset-title">
<a [routerLink]="['/assets/asset/' | relativeUrl, group.asset]">{{ group.name }}</a>
</td>
<td class="circulating-amount"><app-asset-circulation [assetId]="group.asset"></app-asset-circulation></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col" style="max-height: 410px">
<div class="card" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else latestBlocks">
<div class="card" *ngIf="(network$ | async) !== 'liquidtestnet'; else latestBlocks">
<div class="card-body">
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
<h5 class="card-title d-inline" i18n="dashboard.recent-rbf-replacements">Recent Replacements</h5>
@ -171,7 +142,7 @@
<app-truncate [text]="transaction.txid" [lastChars]="5"></app-truncate>
</a>
</td>
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
<td class="table-cell-fiat" *ngIf="(network$ | async) === ''" ><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
<td class="table-cell-fees"><app-fee-rate [fee]="transaction.fee" [weight]="transaction.vsize * 4"></app-fee-rate></td>
</tr>
@ -184,25 +155,50 @@
</div>
</div>
<ng-template #loadingAssetsTable>
<table class="table table-borderless table-striped asset-table">
<tbody>
<tr *ngFor="let i of getArrayFromNumber(this.nbFeaturedAssets)">
<td class="asset-icon">
<div class="skeleton-loader skeleton-loader-transactions"></div>
</td>
<td class="asset-title">
<div class="skeleton-loader skeleton-loader-transactions"></div>
</td>
<td class="asset-title d-none d-md-table-cell">
<div class="skeleton-loader skeleton-loader-transactions"></div>
</td>
<td class="asset-title">
<div class="skeleton-loader skeleton-loader-transactions"></div>
</td>
</tr>
</tbody>
</table>
<ng-template #liquidDashboard>
<div class="container-xl dashboard-container" *ngIf="(auditStatus$ | async)?.isAuditSynced; else auditInProgress">
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card-liquid card">
<div class="card-title card-title-liquid">
<app-reserves-supply-stats [currentPeg$]="currentPeg$" [currentReserves$]="currentReserves$"></app-reserves-supply-stats>
</div>
<div class="card-body pl-0" style="padding-top: 10px;">
<app-lbtc-pegs-graph [data]="fullHistory$ | async" [height]="lbtcPegGraphHeight"></app-lbtc-pegs-graph>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card-liquid card">
<div class="card-body">
<app-reserves-ratio-stats [fullHistory$]="fullHistory$"></app-reserves-ratio-stats>
<app-reserves-ratio [currentPeg]="currentPeg$ | async" [currentReserves]="currentReserves$ | async"></app-reserves-ratio>
</div>
</div>
</div>
<div class="col">
<div class="card card-liquid smaller">
<div class="card-body">
<app-recent-pegs-stats [pegsVolume$]="pegsVolume$"></app-recent-pegs-stats>
<app-recent-pegs-list [recentPegsList$]="recentPegsList$" [widget]="true"></app-recent-pegs-list>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card-liquid card smaller">
<div class="card-body">
<app-federation-addresses-stats [federationAddressesNumber$]="federationAddressesNumber$" [federationUtxosNumber$]="federationUtxosNumber$"></app-federation-addresses-stats>
<app-federation-addresses-list [federationAddresses$]="federationAddresses$" [widget]="true"></app-federation-addresses-list>
</div>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template #replacementsSkeleton>
@ -283,21 +279,56 @@
</div>
</ng-template>
<ng-template #lbtcPegs let-mempoolInfoData>
<div class="mempool-info-data">
<div class="item">
<h5 class="card-title" i18n="dashboard.lbtc-pegs-in-circulation">L-BTC in circulation</h5>
<ng-container *ngIf="(currentPeg$ | async) as currentPeg; else loadingTransactions">
<p i18n-ngbTooltip="liquid.last-elements-audit-block" [ngbTooltip]="'L-BTC supply last updated at Liquid block ' + (currentPeg.lastBlockUpdate)" placement="top" class="card-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></p>
</ng-container>
<ng-template #loadingSkeletonLiquid>
<div class="container-xl dashboard-container">
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<div class="card-liquid card">
<div class="card-title card-title-liquid">
<app-reserves-supply-stats></app-reserves-supply-stats>
</div>
<div class="card-body pl-0" style="padding-top: 10px;">
<app-lbtc-pegs-graph [height]="lbtcPegGraphHeight"></app-lbtc-pegs-graph>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card-liquid card">
<div class="card-body">
<app-reserves-ratio-stats></app-reserves-ratio-stats>
<app-reserves-ratio></app-reserves-ratio>
</div>
</div>
</div>
<div class="col">
<div class="card card-liquid smaller">
<div class="card-body">
<app-recent-pegs-stats></app-recent-pegs-stats>
<app-recent-pegs-list [widget]="true"></app-recent-pegs-list>
</div>
</div>
</div>
<div class="col" style="margin-bottom: 1.47rem">
<div class="card-liquid card smaller">
<div class="card-body">
<app-federation-addresses-stats></app-federation-addresses-stats>
<app-federation-addresses-list [widget]="true"></app-federation-addresses-list>
</div>
</div>
</div>
<div class="item">
<a class="title-link" [routerLink]="['/audit' | relativeUrl]">
<h5 class="card-title"><ng-container i18n="dashboard.btc-reserves">BTC Reserves</ng-container>&nbsp;<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
</a>
<ng-container *ngIf="(currentReserves$ | async) as currentReserves; else loadingTransactions">
<p i18n-ngbTooltip="liquid.last-bitcoin-audit-block" [ngbTooltip]="'BTC reserves last updated at Bitcoin block ' + (currentReserves.lastBlockUpdate)" placement="top" class="card-text">{{ +(currentReserves.amount) / 100000000 | number: '1.2-2' }} <span class="bitcoin-color">BTC</span></p>
</ng-container>
</div>
</div>
</ng-template>
<ng-template #auditInProgress>
<ng-container *ngIf="(auditStatus$ | async) as auditStatus; else loadingSkeletonLiquid">
<div class="in-progress-message" *ngIf="auditStatus.lastBlockAudit && auditStatus.bitcoinHeaders; else loadingSkeletonLiquid">
<span i18n="liquid.audit-in-progress">Audit in progress: Bitcoin block height #{{ auditStatus.lastBlockAudit }} / #{{ auditStatus.bitcoinHeaders }}</span>
</div>
</ng-container>
</ng-template>

View File

@ -59,6 +59,10 @@
display: flex;
flex-direction: row;
}
&.lbtc-pegs-stats {
display: flex;
flex-direction: row;
}
h5 {
margin-bottom: 10px;
}
@ -409,3 +413,27 @@
margin-top: 5px;
margin-bottom: 6px;
}
.card-liquid {
background-color: #1d1f31;
height: 418px;
@media (min-width: 992px) {
height: 512px;
}
&.smaller {
height: 408px;
}
}
.card-title-liquid {
padding-top: 20px;
}
.in-progress-message {
position: relative;
color: #ffffff91;
margin-top: 20px;
text-align: center;
padding-bottom: 3px;
font-weight: 500;
}

View File

@ -1,7 +1,7 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { combineLatest, EMPTY, fromEvent, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
import { AuditStatus, BlockExtended, CurrentPegs, OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface';
import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
@ -33,8 +33,6 @@ interface MempoolStatsData {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
featuredAssets$: Observable<any>;
nbFeaturedAssets = 6;
network$: Observable<string>;
mempoolBlocksData$: Observable<MempoolBlocksData>;
mempoolInfoData$: Observable<MempoolInfoData>;
@ -54,6 +52,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
auditUpdated$: Observable<boolean>;
liquidReservesMonth$: Observable<any>;
currentReserves$: Observable<CurrentPegs>;
recentPegsList$: Observable<RecentPeg[]>;
pegsVolume$: Observable<PegsVolume[]>;
federationAddresses$: Observable<FederationAddress[]>;
federationAddressesNumber$: Observable<number>;
federationUtxosNumber$: Observable<number>;
fullHistory$: Observable<any>;
isLoad: boolean = true;
filterSubscription: Subscription;
@ -184,26 +187,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
})
);
const windowResize$ = fromEvent(window, 'resize').pipe(
distinctUntilChanged(),
startWith(null)
);
this.featuredAssets$ = combineLatest([
this.apiService.listFeaturedAssets$(),
windowResize$
]).pipe(
map(([featured, _]) => {
const newArray = [];
for (const feature of featured) {
if (feature.ticker !== 'L-BTC' && feature.asset) {
newArray.push(feature);
}
}
return newArray.slice(0, this.nbFeaturedAssets);
}),
);
this.transactions$ = this.stateService.transactions$
.pipe(
scan((acc, tx) => {
@ -269,7 +252,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
share(),
);
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
if (this.stateService.network === 'liquid') {
this.auditStatus$ = this.stateService.blocks$.pipe(
takeUntil(this.destroy$),
throttleTime(40000),
@ -279,22 +262,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
shareReplay(1)
);
////////// Pegs historical data //////////
this.liquidPegsMonth$ = this.auditStatus$.pipe(
throttleTime(60 * 60 * 1000),
switchMap(() => this.apiService.listLiquidPegsMonth$()),
map((pegs) => {
const labels = pegs.map(stats => stats.date);
const series = pegs.map(stats => parseFloat(stats.amount) / 100000000);
series.reduce((prev, curr, i) => series[i] = prev + curr, 0);
return {
series,
labels
};
}),
share(),
);
this.currentPeg$ = this.auditStatus$.pipe(
switchMap(_ =>
this.apiService.liquidPegs$().pipe(
@ -307,7 +274,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
share()
);
////////// BTC Reserves historical data //////////
this.auditUpdated$ = combineLatest([
this.auditStatus$,
this.currentPeg$
@ -322,21 +288,6 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
const amountCheck = currentPegAmount !== this.lastPegAmount;
this.lastPegAmount = currentPegAmount;
return of(blockAuditCheck || amountCheck);
})
);
this.liquidReservesMonth$ = this.auditStatus$.pipe(
throttleTime(60 * 60 * 1000),
switchMap((auditStatus) => {
return auditStatus.isAuditSynced ? this.apiService.listLiquidReservesMonth$() : EMPTY;
}),
map(reserves => {
const labels = reserves.map(stats => stats.date);
const series = reserves.map(stats => parseFloat(stats.amount) / 100000000);
return {
series,
labels
};
}),
share()
);
@ -355,7 +306,74 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
share()
);
this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$.pipe(startWith(null)), this.currentReserves$.pipe(startWith(null))])
this.recentPegsList$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.recentPegsList$()),
share()
);
this.pegsVolume$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.pegsVolume$()),
share()
);
this.federationAddresses$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.federationAddresses$()),
share()
);
this.federationAddressesNumber$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.federationAddressesNumber$()),
map(count => count.address_count),
share()
);
this.federationUtxosNumber$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.federationUtxosNumber$()),
map(count => count.utxo_count),
share()
);
this.liquidPegsMonth$ = interval(60 * 60 * 1000)
.pipe(
startWith(0),
switchMap(() => this.apiService.listLiquidPegsMonth$()),
map((pegs) => {
const labels = pegs.map(stats => stats.date);
const series = pegs.map(stats => parseFloat(stats.amount) / 100000000);
series.reduce((prev, curr, i) => series[i] = prev + curr, 0);
return {
series,
labels
};
}),
share(),
);
this.liquidReservesMonth$ = interval(60 * 60 * 1000).pipe(
startWith(0),
switchMap(() => this.apiService.listLiquidReservesMonth$()),
map(reserves => {
const labels = reserves.map(stats => stats.date);
const series = reserves.map(stats => parseFloat(stats.amount) / 100000000);
return {
series,
labels
};
}),
share()
);
this.fullHistory$ = combineLatest([this.liquidPegsMonth$, this.currentPeg$, this.liquidReservesMonth$, this.currentReserves$])
.pipe(
map(([liquidPegs, currentPeg, liquidReserves, currentReserves]) => {
liquidPegs.series[liquidPegs.series.length - 1] = parseFloat(currentPeg.amount) / 100000000;
@ -415,17 +433,14 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
this.incomingGraphHeight = 300;
this.goggleResolution = 82;
this.lbtcPegGraphHeight = 320;
this.nbFeaturedAssets = 6;
} else if (window.innerWidth >= 768) {
this.incomingGraphHeight = 215;
this.goggleResolution = 80;
this.lbtcPegGraphHeight = 230;
this.nbFeaturedAssets = 4;
} else {
this.incomingGraphHeight = 180;
this.goggleResolution = 86;
this.lbtcPegGraphHeight = 220;
this.nbFeaturedAssets = 4;
}
}
}

View File

@ -12,6 +12,13 @@ import { FeeDistributionGraphComponent } from '../components/fee-distribution-gr
import { IncomingTransactionsGraphComponent } from '../components/incoming-transactions-graph/incoming-transactions-graph.component';
import { MempoolGraphComponent } from '../components/mempool-graph/mempool-graph.component';
import { LbtcPegsGraphComponent } from '../components/lbtc-pegs-graph/lbtc-pegs-graph.component';
import { ReservesSupplyStatsComponent } from '../components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component';
import { ReservesRatioStatsComponent } from '../components/liquid-reserves-audit/reserves-ratio-stats/reserves-ratio-stats.component';
import { ReservesRatioComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component';
import { RecentPegsStatsComponent } from '../components/liquid-reserves-audit/recent-pegs-stats/recent-pegs-stats.component';
import { RecentPegsListComponent } from '../components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component';
import { FederationAddressesStatsComponent } from '../components/liquid-reserves-audit/federation-addresses-stats/federation-addresses-stats.component';
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
import { GraphsComponent } from '../components/graphs/graphs.component';
import { StatisticsComponent } from '../components/statistics/statistics.component';
import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component';
@ -48,6 +55,13 @@ import { CommonModule } from '@angular/common';
IncomingTransactionsGraphComponent,
MempoolGraphComponent,
LbtcPegsGraphComponent,
ReservesSupplyStatsComponent,
ReservesRatioStatsComponent,
ReservesRatioComponent,
RecentPegsStatsComponent,
RecentPegsListComponent,
FederationAddressesStatsComponent,
FederationAddressesListComponent,
HashrateChartComponent,
HashrateChartPoolsComponent,
BlockHealthGraphComponent,

View File

@ -15,17 +15,10 @@ import { AssetsComponent } from '../components/assets/assets.component';
import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component'
import { AssetComponent } from '../components/asset/asset.component';
import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component';
import { ReservesAuditDashboardComponent } from '../components/liquid-reserves-audit/reserves-audit-dashboard/reserves-audit-dashboard.component';
import { ReservesSupplyStatsComponent } from '../components/liquid-reserves-audit/reserves-supply-stats/reserves-supply-stats.component';
import { RecentPegsStatsComponent } from '../components/liquid-reserves-audit/recent-pegs-stats/recent-pegs-stats.component';
import { RecentPegsListComponent } from '../components/liquid-reserves-audit/recent-pegs-list/recent-pegs-list.component';
import { FederationWalletComponent } from '../components/liquid-reserves-audit/federation-wallet/federation-wallet.component';
import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component';
import { FederationAddressesStatsComponent } from '../components/liquid-reserves-audit/federation-addresses-stats/federation-addresses-stats.component';
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
import { ReservesRatioComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio.component';
import { ReservesRatioStatsComponent } from '../components/liquid-reserves-audit/reserves-ratio-stats/reserves-ratio-stats.component';
import { ReservesRatioGraphComponent } from '../components/liquid-reserves-audit/reserves-ratio/reserves-ratio-graph.component';
const routes: Routes = [
{
@ -77,18 +70,6 @@ const routes: Routes = [
data: { preload: true, networkSpecific: true },
loadChildren: () => import('../components/block/block.module').then(m => m.BlockModule),
},
{
path: 'audit',
data: { networks: ['liquid'] },
component: StartComponent,
children: [
{
path: '',
data: { networks: ['liquid'] },
component: ReservesAuditDashboardComponent,
}
]
},
{
path: 'audit/wallet',
data: { networks: ['liquid'] },
@ -180,17 +161,8 @@ export class LiquidRoutingModule { }
],
declarations: [
LiquidMasterPageComponent,
ReservesAuditDashboardComponent,
ReservesSupplyStatsComponent,
RecentPegsStatsComponent,
RecentPegsListComponent,
FederationWalletComponent,
FederationUtxosListComponent,
FederationAddressesStatsComponent,
FederationAddressesListComponent,
ReservesRatioComponent,
ReservesRatioStatsComponent,
ReservesRatioGraphComponent,
]
})
export class LiquidMasterPageModule { }

View File

@ -200,16 +200,20 @@ export class ApiService {
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos');
}
recentPegOuts$(): Observable<RecentPeg[]> {
return this.httpClient.get<RecentPeg[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegouts');
recentPegsList$(count: number = 0): Observable<RecentPeg[]> {
return this.httpClient.get<RecentPeg[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/list/' + count);
}
federationAddressesOneMonthAgo$(): Observable<any> {
return this.httpClient.get<FederationAddress[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/addresses/previous-month');
pegsCount$(): Observable<any> {
return this.httpClient.get<number>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/count');
}
federationUtxosOneMonthAgo$(): Observable<any> {
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/previous-month');
federationAddressesNumber$(): Observable<any> {
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/addresses/total');
}
federationUtxosNumber$(): Observable<any> {
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos/total');
}
listFeaturedAssets$(): Observable<any[]> {