diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d263db3ad..729a650f7 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -37,6 +37,7 @@ import { IncomingTransactionsGraphComponent } from './components/incoming-transa import { TimeSpanComponent } from './components/time-span/time-span.component'; import { SeoService } from './services/seo.service'; import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; +import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component'; import { AssetComponent } from './components/asset/asset.component'; import { AssetsComponent } from './assets/assets.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; @@ -84,6 +85,7 @@ import { SponsorComponent } from './components/sponsor/sponsor.component'; FeeDistributionGraphComponent, IncomingTransactionsGraphComponent, MempoolGraphComponent, + LbtcPegsGraphComponent, AssetComponent, AssetsComponent, MinerComponent, diff --git a/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.html b/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.html new file mode 100644 index 000000000..6b5017785 --- /dev/null +++ b/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.html @@ -0,0 +1 @@ +
diff --git a/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts b/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts new file mode 100644 index 000000000..ca93be029 --- /dev/null +++ b/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts @@ -0,0 +1,137 @@ +import { Component, OnInit, Inject, LOCALE_ID, ChangeDetectionStrategy, Output, EventEmitter, Input, OnChanges } from '@angular/core'; +import { formatDate, formatNumber } from '@angular/common'; +import { EChartsOption } from 'echarts'; +import { ApiService } from 'src/app/services/api.service'; +import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-lbtc-pegs-graph', + templateUrl: './lbtc-pegs-graph.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class LbtcPegsGraphComponent implements OnChanges { + @Input() data: any; + pegsChartOptions: EChartsOption; + + height: number | string = '200'; + right: number | string = '10'; + top: number | string = '20'; + left: number | string = '50'; + template: ('widget' | 'advanced') = 'widget'; + + pegsChartOption: EChartsOption = {}; + pegsChartInitOption = { + renderer: 'svg' + }; + + constructor( + @Inject(LOCALE_ID) private locale: string, + ) { } + + ngOnChanges() { + this.pegsChartOptions = this.createChartOptions(this.data.series, this.data.labels); + } + + createChartOptions(series: 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) => ``; + let itemFormatted = '
' + params[0].axisValue + '
'; + params.map((item: any, index: number) => { + if (index < 26) { + itemFormatted += `
+
${colorSpan(item.color)}
+
+
${formatNumber(item.value, this.locale, '1.2-2')} L-BTC
+
`; + } + }); + return `
${itemFormatted}
`; + } + }, + xAxis: { + type: 'category', + axisLabel: { + align: 'center', + fontSize: 11, + lineHeight: 12 + }, + 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, + } + } + }, + series: [ + { + data: series, + type: 'line', + stack: 'total', + smooth: false, + showSymbol: false, + areaStyle: { + opacity: 0.2, + color: '#116761', + }, + lineStyle: { + width: 3, + color: '#116761', + }, + }, + ], + }; + } +} + diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index b42956b30..79b39d4d2 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -16,7 +16,7 @@
- +
@@ -44,16 +44,21 @@
- +
-
- +
+
+ +
+ +
+
@@ -192,6 +197,17 @@ + +
+
+
L-BTC in circulation
+ +

{{ liquidPegsMonth.series.slice(-1)[0] | number: '1.2-2' }} L-BTC

+
+
+
+
+
Incoming transactions
@@ -207,7 +223,6 @@
-
Difficulty Adjustment
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index 7911fc204..f76438942 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@ import { combineLatest, merge, Observable, of, timer } from 'rxjs'; import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; import { Block } from '../interfaces/electrs.interface'; -import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; +import { LiquidPegs, OptimizedMempoolStats } from '../interfaces/node-api.interface'; import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface'; import { ApiService } from '../services/api.service'; import { StateService } from '../services/state.service'; @@ -63,6 +63,7 @@ export class DashboardComponent implements OnInit { mempoolStats$: Observable; transactionsWeightPerSecondOptions: any; isLoadingWebSocket$: Observable; + liquidPegsMonth$: Observable; constructor( @Inject(LOCALE_ID) private locale: string, @@ -261,6 +262,22 @@ export class DashboardComponent implements OnInit { }), share(), ); + + if (this.stateService.network === 'liquid') { + this.liquidPegsMonth$ = this.apiService.listLiquidPegsMonth$() + .pipe( + 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(), + ); + } } handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) { diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index d73c12d4e..550f712d9 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -47,3 +47,8 @@ export interface AddressInformation { confidential_key?: string; // (string) Elements only unconfidential?: string; // (string) Elements only } + +export interface LiquidPegs { + amount: string; + date: string; +} diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index d38cdf8f4..48c52547f 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation } from '../interfaces/node-api.interface'; +import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs } from '../interfaces/node-api.interface'; import { Observable } from 'rxjs'; import { StateService } from './state.service'; import { WebsocketResponse } from '../interfaces/websocket.interface'; @@ -100,4 +100,8 @@ export class ApiService { validateAddress$(address: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address); } + + listLiquidPegsMonth$(): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month'); + } }