Bisq statistics page.
This commit is contained in:
parent
87abfc38cb
commit
b7376fbd8d
@ -14,7 +14,7 @@
|
|||||||
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
||||||
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
|
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
|
||||||
"BISQ_ENABLED": false,
|
"BISQ_ENABLED": false,
|
||||||
"BSQ_BLOCKS_DATA_PATH": "/mempool/data/all/blocks.json",
|
"BSQ_BLOCKS_DATA_PATH": "/bisq/data/all/blocks.json",
|
||||||
"SSL": false,
|
"SSL": false,
|
||||||
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
||||||
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const config = require('../../mempool-config.json');
|
const config = require('../../mempool-config.json');
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { BisqBlocks, BisqBlock, BisqTransaction } from '../interfaces';
|
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats } from '../interfaces';
|
||||||
|
|
||||||
class Bisq {
|
class Bisq {
|
||||||
private blocks: BisqBlock[] = [];
|
private blocks: BisqBlock[] = [];
|
||||||
@ -8,6 +8,13 @@ class Bisq {
|
|||||||
private transactionIndex: { [txId: string]: BisqTransaction } = {};
|
private transactionIndex: { [txId: string]: BisqTransaction } = {};
|
||||||
private blockIndex: { [hash: string]: BisqBlock } = {};
|
private blockIndex: { [hash: string]: BisqBlock } = {};
|
||||||
private addressIndex: { [address: string]: BisqTransaction[] } = {};
|
private addressIndex: { [address: string]: BisqTransaction[] } = {};
|
||||||
|
private stats: BisqStats = {
|
||||||
|
minted: 0,
|
||||||
|
burnt: 0,
|
||||||
|
addresses: 0,
|
||||||
|
unspent_txos: 0,
|
||||||
|
spent_txos: 0,
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@ -48,11 +55,16 @@ class Bisq {
|
|||||||
return [this.blocks.slice(start, length + start), this.blocks.length];
|
return [this.blocks.slice(start, length + start), this.blocks.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStats(): BisqStats {
|
||||||
|
return this.stats;
|
||||||
|
}
|
||||||
|
|
||||||
private async loadBisqDumpFile(): Promise<void> {
|
private async loadBisqDumpFile(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const data = await this.loadData();
|
const data = await this.loadData();
|
||||||
await this.loadBisqBlocksDump(data);
|
await this.loadBisqBlocksDump(data);
|
||||||
this.buildIndex();
|
this.buildIndex();
|
||||||
|
this.calculateStats();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('loadBisqDumpFile() error.', e.message);
|
console.log('loadBisqDumpFile() error.', e.message);
|
||||||
}
|
}
|
||||||
@ -101,6 +113,38 @@ class Bisq {
|
|||||||
console.log('Bisq data index rebuilt in ' + time + ' ms');
|
console.log('Bisq data index rebuilt in ' + time + ' ms');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private calculateStats() {
|
||||||
|
let minted = 0;
|
||||||
|
let burned = 0;
|
||||||
|
let unspent = 0;
|
||||||
|
let spent = 0;
|
||||||
|
|
||||||
|
this.transactions.forEach((tx) => {
|
||||||
|
tx.outputs.forEach((output) => {
|
||||||
|
if (output.opReturn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (output.txOutputType === 'GENESIS_OUTPUT' || output.txOutputType === 'ISSUANCE_CANDIDATE_OUTPUT' && output.isVerified) {
|
||||||
|
minted += output.bsqAmount;
|
||||||
|
}
|
||||||
|
if (output.isUnspent) {
|
||||||
|
unspent++;
|
||||||
|
} else {
|
||||||
|
spent++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
burned += tx['burntFee'];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stats = {
|
||||||
|
addresses: Object.keys(this.addressIndex).length,
|
||||||
|
minted: minted,
|
||||||
|
burnt: burned,
|
||||||
|
spent_txos: spent,
|
||||||
|
unspent_txos: unspent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async loadBisqBlocksDump(cacheData: string): Promise<void> {
|
private async loadBisqBlocksDump(cacheData: string): Promise<void> {
|
||||||
const start = new Date().getTime();
|
const start = new Date().getTime();
|
||||||
if (cacheData && cacheData.length !== 0) {
|
if (cacheData && cacheData.length !== 0) {
|
||||||
|
@ -92,6 +92,7 @@ class Server {
|
|||||||
|
|
||||||
if (config.BISQ_ENABLED) {
|
if (config.BISQ_ENABLED) {
|
||||||
this.app
|
this.app
|
||||||
|
.get(config.API_ENDPOINT + 'bisq/stats', routes.getBisqStats)
|
||||||
.get(config.API_ENDPOINT + 'bisq/tx/:txId', routes.getBisqTransaction)
|
.get(config.API_ENDPOINT + 'bisq/tx/:txId', routes.getBisqTransaction)
|
||||||
.get(config.API_ENDPOINT + 'bisq/block/:hash', routes.getBisqBlock)
|
.get(config.API_ENDPOINT + 'bisq/block/:hash', routes.getBisqBlock)
|
||||||
.get(config.API_ENDPOINT + 'bisq/blocks/:index/:length', routes.getBisqBlocks)
|
.get(config.API_ENDPOINT + 'bisq/blocks/:index/:length', routes.getBisqBlocks)
|
||||||
|
@ -259,6 +259,14 @@ export interface BisqTransaction {
|
|||||||
unlockBlockHeight: number;
|
unlockBlockHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BisqStats {
|
||||||
|
minted: number;
|
||||||
|
burnt: number;
|
||||||
|
addresses: number;
|
||||||
|
unspent_txos: number;
|
||||||
|
spent_txos: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface BisqInput {
|
interface BisqInput {
|
||||||
spendingTxOutputIndex: number;
|
spendingTxOutputIndex: number;
|
||||||
spendingTxId: string;
|
spendingTxId: string;
|
||||||
|
@ -87,6 +87,11 @@ class Routes {
|
|||||||
res.send(backendInfo.getBackendInfo());
|
res.send(backendInfo.getBackendInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getBisqStats(req: Request, res: Response) {
|
||||||
|
const result = bisq.getStats();
|
||||||
|
res.send(result);
|
||||||
|
}
|
||||||
|
|
||||||
public getBisqTransaction(req: Request, res: Response) {
|
public getBisqTransaction(req: Request, res: Response) {
|
||||||
const result = bisq.getTransaction(req.params.txId);
|
const result = bisq.getTransaction(req.params.txId);
|
||||||
if (result) {
|
if (result) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpResponse } from '@angular/common/http';
|
import { HttpClient, HttpResponse } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BisqTransaction, BisqBlock } from './bisq.interfaces';
|
import { BisqTransaction, BisqBlock, BisqStats } from './bisq.interfaces';
|
||||||
|
|
||||||
const API_BASE_URL = '/api/v1';
|
const API_BASE_URL = '/api/v1';
|
||||||
|
|
||||||
@ -15,6 +15,10 @@ export class BisqApiService {
|
|||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
getStats$(): Observable<BisqStats> {
|
||||||
|
return this.httpClient.get<BisqStats>(API_BASE_URL + '/bisq/stats');
|
||||||
|
}
|
||||||
|
|
||||||
getTransaction$(txId: string): Observable<BisqTransaction> {
|
getTransaction$(txId: string): Observable<BisqTransaction> {
|
||||||
return this.httpClient.get<BisqTransaction>(API_BASE_URL + '/bisq/tx/' + txId);
|
return this.httpClient.get<BisqTransaction>(API_BASE_URL + '/bisq/tx/' + txId);
|
||||||
}
|
}
|
||||||
|
85
frontend/src/app/bisq/bisq-stats/bisq-stats.component.html
Normal file
85
frontend/src/app/bisq/bisq-stats/bisq-stats.component.html
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
<h1 style="float: left;">BSQ Statistics</h1>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<thead>
|
||||||
|
<th>Property</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="!isLoading; else loadingTemplate">
|
||||||
|
<tr>
|
||||||
|
<td class="td-width">Existing amount</td>
|
||||||
|
<td>{{ (stats.minted - stats.burnt) / 100 | number: '1.2-2' }} BSQ</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Minted amount</td>
|
||||||
|
<td>{{ stats.minted | number: '1.2-2' }} BSQ</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Burnt amount</td>
|
||||||
|
<td>{{ stats.burnt | number: '1.2-2' }} BSQ</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Addresses</td>
|
||||||
|
<td>{{ stats.addresses | number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Unspent TXOs</td>
|
||||||
|
<td>{{ stats.unspent_txos | number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Spent TXOs</td>
|
||||||
|
<td>{{ stats.spent_txos | number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Price</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Market cap</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingTemplate>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="td-width">Existing amount</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Minted amount</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Burnt amount</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Addresses</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Unspent TXOs</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Spent TXOs</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Price</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Market cap</td>
|
||||||
|
<td><span class="skeleton-loader"></span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</ng-template>
|
13
frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss
Normal file
13
frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.td-width {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.td-width {
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-loader {
|
||||||
|
width: 200px;
|
||||||
|
}
|
30
frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts
Normal file
30
frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { BisqApiService } from '../bisq-api.service';
|
||||||
|
import { BisqStats } from '../bisq.interfaces';
|
||||||
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-bisq-stats',
|
||||||
|
templateUrl: './bisq-stats.component.html',
|
||||||
|
styleUrls: ['./bisq-stats.component.scss']
|
||||||
|
})
|
||||||
|
export class BisqStatsComponent implements OnInit {
|
||||||
|
isLoading = true;
|
||||||
|
stats: BisqStats;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private bisqApiService: BisqApiService,
|
||||||
|
private seoService: SeoService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.seoService.setTitle('BSQ Statistics', false);
|
||||||
|
|
||||||
|
this.bisqApiService.getStats$()
|
||||||
|
.subscribe((stats) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.stats = stats;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -59,6 +59,14 @@ export interface BisqOutput {
|
|||||||
opReturn?: string;
|
opReturn?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BisqStats {
|
||||||
|
minted: number;
|
||||||
|
burnt: number;
|
||||||
|
addresses: number;
|
||||||
|
unspent_txos: number;
|
||||||
|
spent_txos: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface BisqScriptPubKey {
|
interface BisqScriptPubKey {
|
||||||
addresses: string[];
|
addresses: string[];
|
||||||
asm: string;
|
asm: string;
|
||||||
|
@ -16,6 +16,7 @@ import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
|||||||
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
|
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
|
||||||
import { BisqApiService } from './bisq-api.service';
|
import { BisqApiService } from './bisq-api.service';
|
||||||
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
||||||
|
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -29,6 +30,7 @@ import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
|||||||
BisqBlocksComponent,
|
BisqBlocksComponent,
|
||||||
BisqExplorerComponent,
|
BisqExplorerComponent,
|
||||||
BisqAddressComponent,
|
BisqAddressComponent,
|
||||||
|
BisqStatsComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -8,6 +8,7 @@ import { BisqBlockComponent } from './bisq-block/bisq-block.component';
|
|||||||
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
|
||||||
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
|
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
|
||||||
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
import { BisqAddressComponent } from './bisq-address/bisq-address.component';
|
||||||
|
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -35,6 +36,10 @@ const routes: Routes = [
|
|||||||
path: 'address/:id',
|
path: 'address/:id',
|
||||||
component: BisqAddressComponent,
|
component: BisqAddressComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'stats',
|
||||||
|
component: BisqStatsComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
component: AboutComponent,
|
component: AboutComponent,
|
||||||
|
@ -26,20 +26,25 @@
|
|||||||
|
|
||||||
<div class="navbar-collapse collapse" id="navbarCollapse" [ngClass]="{'show': navCollapsed}">
|
<div class="navbar-collapse collapse" id="navbarCollapse" [ngClass]="{'show': navCollapsed}">
|
||||||
<ul class="navbar-nav mr-auto {{ network }}">
|
<ul class="navbar-nav mr-auto {{ network }}">
|
||||||
<ng-template [ngIf]="network === 'bisq'">
|
<ng-template [ngIf]="network === 'bisq'" [ngIfElse]="notBisq">
|
||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
|
||||||
<a class="nav-link" [routerLink]="['/bisq']" (click)="collapse()">Transactions</a>
|
<a class="nav-link" [routerLink]="['/bisq']" (click)="collapse()">Transactions</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active">
|
<li class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" [routerLink]="['/bisq/blocks']" (click)="collapse()">Blocks</a>
|
<a class="nav-link" [routerLink]="['/bisq/blocks']" (click)="collapse()">Blocks</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" routerLinkActive="active">
|
||||||
|
<a class="nav-link" [routerLink]="['/bisq/stats']" (click)="collapse()">Stats</a>
|
||||||
|
</li>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #notBisq>
|
||||||
|
<li class="nav-item" routerLinkActive="active">
|
||||||
|
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()">Graphs</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" routerLinkActive="active">
|
||||||
|
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()">TV view <img src="./resources/expand.png" width="15"/></a>
|
||||||
|
</li>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<li *ngIf="network !== 'bisq'" class="nav-item" routerLinkActive="active">
|
|
||||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()">Graphs</a>
|
|
||||||
</li>
|
|
||||||
<li *ngIf="network !== 'bisq'" class="nav-item" routerLinkActive="active">
|
|
||||||
<a class="nav-link" [routerLink]="['/tv' | relativeUrl]" (click)="collapse()">TV view <img src="./resources/expand.png" width="15"/></a>
|
|
||||||
</li>
|
|
||||||
<li *ngIf="network === 'liquid'" class="nav-item" routerLinkActive="active">
|
<li *ngIf="network === 'liquid'" class="nav-item" routerLinkActive="active">
|
||||||
<a class="nav-link" [routerLink]="['liquid/assets']" (click)="collapse()">Assets</a>
|
<a class="nav-link" [routerLink]="['liquid/assets']" (click)="collapse()">Assets</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -35,7 +35,7 @@ export class QrcodeComponent implements AfterViewInit {
|
|||||||
address.toUpperCase();
|
address.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
QRCode.toCanvas(this.canvas.nativeElement, 'bitcoin:' + address, opts, (error: any) => {
|
QRCode.toCanvas(this.canvas.nativeElement, address, opts, (error: any) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user