Bisq statistics page.

This commit is contained in:
softsimon 2020-07-14 14:38:52 +07:00
parent 87abfc38cb
commit b7376fbd8d
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
14 changed files with 221 additions and 11 deletions

View File

@ -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"

View File

@ -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) {

View File

@ -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)

View File

@ -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;

View File

@ -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) {

View File

@ -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);
} }

View 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>

View File

@ -0,0 +1,13 @@
.td-width {
width: 300px;
}
@media (max-width: 767.98px) {
.td-width {
width: 175px;
}
}
.skeleton-loader {
width: 200px;
}

View 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;
});
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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 &nbsp;<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 &nbsp;<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>

View File

@ -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);
} }