diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index b4b83669f..4f2944ef7 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -14,7 +14,7 @@ "TX_PER_SECOND_SPAN_SECONDS": 150, "ELECTRS_API_URL": "https://www.blockstream.info/testnet/api", "BISQ_ENABLED": false, - "BSQ_BLOCKS_DATA_PATH": "/mempool/data/all/blocks.json", + "BSQ_BLOCKS_DATA_PATH": "/bisq/data/all/blocks.json", "SSL": false, "SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem", "SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem" diff --git a/backend/src/api/bisq.ts b/backend/src/api/bisq.ts index 8ffd599e7..1fee079af 100644 --- a/backend/src/api/bisq.ts +++ b/backend/src/api/bisq.ts @@ -1,6 +1,6 @@ const config = require('../../mempool-config.json'); import * as fs from 'fs'; -import { BisqBlocks, BisqBlock, BisqTransaction } from '../interfaces'; +import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats } from '../interfaces'; class Bisq { private blocks: BisqBlock[] = []; @@ -8,6 +8,13 @@ class Bisq { private transactionIndex: { [txId: string]: BisqTransaction } = {}; private blockIndex: { [hash: string]: BisqBlock } = {}; private addressIndex: { [address: string]: BisqTransaction[] } = {}; + private stats: BisqStats = { + minted: 0, + burnt: 0, + addresses: 0, + unspent_txos: 0, + spent_txos: 0, + }; constructor() {} @@ -48,11 +55,16 @@ class Bisq { return [this.blocks.slice(start, length + start), this.blocks.length]; } + getStats(): BisqStats { + return this.stats; + } + private async loadBisqDumpFile(): Promise { try { const data = await this.loadData(); await this.loadBisqBlocksDump(data); this.buildIndex(); + this.calculateStats(); } catch (e) { console.log('loadBisqDumpFile() error.', e.message); } @@ -101,6 +113,38 @@ class Bisq { 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 { const start = new Date().getTime(); if (cacheData && cacheData.length !== 0) { diff --git a/backend/src/index.ts b/backend/src/index.ts index 1ac1fd45d..41ea8c6b5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -92,6 +92,7 @@ class Server { if (config.BISQ_ENABLED) { this.app + .get(config.API_ENDPOINT + 'bisq/stats', routes.getBisqStats) .get(config.API_ENDPOINT + 'bisq/tx/:txId', routes.getBisqTransaction) .get(config.API_ENDPOINT + 'bisq/block/:hash', routes.getBisqBlock) .get(config.API_ENDPOINT + 'bisq/blocks/:index/:length', routes.getBisqBlocks) diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index 5d19411b7..fa8dcfd38 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -259,6 +259,14 @@ export interface BisqTransaction { unlockBlockHeight: number; } +export interface BisqStats { + minted: number; + burnt: number; + addresses: number; + unspent_txos: number; + spent_txos: number; +} + interface BisqInput { spendingTxOutputIndex: number; spendingTxId: string; diff --git a/backend/src/routes.ts b/backend/src/routes.ts index aed87f0be..ea7c97472 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -87,6 +87,11 @@ class Routes { res.send(backendInfo.getBackendInfo()); } + public getBisqStats(req: Request, res: Response) { + const result = bisq.getStats(); + res.send(result); + } + public getBisqTransaction(req: Request, res: Response) { const result = bisq.getTransaction(req.params.txId); if (result) { diff --git a/frontend/src/app/bisq/bisq-api.service.ts b/frontend/src/app/bisq/bisq-api.service.ts index f4ab802b0..eedd502c8 100644 --- a/frontend/src/app/bisq/bisq-api.service.ts +++ b/frontend/src/app/bisq/bisq-api.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { BisqTransaction, BisqBlock } from './bisq.interfaces'; +import { BisqTransaction, BisqBlock, BisqStats } from './bisq.interfaces'; const API_BASE_URL = '/api/v1'; @@ -15,6 +15,10 @@ export class BisqApiService { private httpClient: HttpClient, ) { } + getStats$(): Observable { + return this.httpClient.get(API_BASE_URL + '/bisq/stats'); + } + getTransaction$(txId: string): Observable { return this.httpClient.get(API_BASE_URL + '/bisq/tx/' + txId); } diff --git a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html new file mode 100644 index 000000000..c565edfcd --- /dev/null +++ b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html @@ -0,0 +1,85 @@ +
+

BSQ Statistics

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValue
Existing amount{{ (stats.minted - stats.burnt) / 100 | number: '1.2-2' }} BSQ
Minted amount{{ stats.minted | number: '1.2-2' }} BSQ
Burnt amount{{ stats.burnt | number: '1.2-2' }} BSQ
Addresses{{ stats.addresses | number }}
Unspent TXOs{{ stats.unspent_txos | number }}
Spent TXOs{{ stats.spent_txos | number }}
Price
Market cap
+ +
+ + + + + Existing amount + + + + Minted amount + + + + Burnt amount + + + + Addresses + + + + Unspent TXOs + + + + Spent TXOs + + + + Price + + + + Market cap + + + + \ No newline at end of file diff --git a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss new file mode 100644 index 000000000..2c5c3f28a --- /dev/null +++ b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss @@ -0,0 +1,13 @@ +.td-width { + width: 300px; +} + +@media (max-width: 767.98px) { + .td-width { + width: 175px; + } +} + +.skeleton-loader { + width: 200px; +} diff --git a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts new file mode 100644 index 000000000..314b575f3 --- /dev/null +++ b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts @@ -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; + }); + } + +} diff --git a/frontend/src/app/bisq/bisq.interfaces.ts b/frontend/src/app/bisq/bisq.interfaces.ts index c6cbeaa42..710bada2a 100644 --- a/frontend/src/app/bisq/bisq.interfaces.ts +++ b/frontend/src/app/bisq/bisq.interfaces.ts @@ -59,6 +59,14 @@ export interface BisqOutput { opReturn?: string; } +export interface BisqStats { + minted: number; + burnt: number; + addresses: number; + unspent_txos: number; + spent_txos: number; +} + interface BisqScriptPubKey { addresses: string[]; asm: string; diff --git a/frontend/src/app/bisq/bisq.module.ts b/frontend/src/app/bisq/bisq.module.ts index 8ce14bc34..89fe7db09 100644 --- a/frontend/src/app/bisq/bisq.module.ts +++ b/frontend/src/app/bisq/bisq.module.ts @@ -16,6 +16,7 @@ import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component'; import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component'; import { BisqApiService } from './bisq-api.service'; import { BisqAddressComponent } from './bisq-address/bisq-address.component'; +import { BisqStatsComponent } from './bisq-stats/bisq-stats.component'; @NgModule({ declarations: [ @@ -29,6 +30,7 @@ import { BisqAddressComponent } from './bisq-address/bisq-address.component'; BisqBlocksComponent, BisqExplorerComponent, BisqAddressComponent, + BisqStatsComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/bisq/bisq.routing.module.ts b/frontend/src/app/bisq/bisq.routing.module.ts index 882056a58..fdac7de60 100644 --- a/frontend/src/app/bisq/bisq.routing.module.ts +++ b/frontend/src/app/bisq/bisq.routing.module.ts @@ -8,6 +8,7 @@ import { BisqBlockComponent } from './bisq-block/bisq-block.component'; import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component'; import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component'; import { BisqAddressComponent } from './bisq-address/bisq-address.component'; +import { BisqStatsComponent } from './bisq-stats/bisq-stats.component'; const routes: Routes = [ { @@ -35,6 +36,10 @@ const routes: Routes = [ path: 'address/:id', component: BisqAddressComponent, }, + { + path: 'stats', + component: BisqStatsComponent, + }, { path: 'about', component: AboutComponent, diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 2b6d097cd..cfe044d1e 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -26,20 +26,25 @@