Bisq stats page.

BSQ prices.
This commit is contained in:
softsimon 2020-07-14 21:26:02 +07:00
parent b7376fbd8d
commit ca0cf23d66
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
21 changed files with 207 additions and 61 deletions

View File

@ -1,6 +1,8 @@
const config = require('../../mempool-config.json');
import * as fs from 'fs';
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats } from '../interfaces';
import * as request from 'request';
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from '../interfaces';
import { Common } from './common';
class Bisq {
private blocks: BisqBlock[] = [];
@ -15,6 +17,8 @@ class Bisq {
unspent_txos: 0,
spent_txos: 0,
};
private price: number = 0;
private priceUpdateCallbackFunction: ((price: number) => void) | undefined;
constructor() {}
@ -22,7 +26,7 @@ class Bisq {
this.loadBisqDumpFile();
let fsWait: NodeJS.Timeout | null = null;
fs.watch(config.BSQ_BLOCKS_DATA_PATH, (event, filename) => {
fs.watch(config.BSQ_BLOCKS_DATA_PATH, (event: string, filename: string) => {
if (filename) {
if (fsWait) {
clearTimeout(fsWait);
@ -33,6 +37,9 @@ class Bisq {
}, 1000);
}
});
setInterval(this.updatePrice.bind(this), 1000 * 60 * 60);
this.updatePrice();
}
getTransaction(txId: string): BisqTransaction | undefined {
@ -59,6 +66,26 @@ class Bisq {
return this.stats;
}
setPriceCallbackFunction(fn: (price: number) => void) {
this.priceUpdateCallbackFunction = fn;
}
private updatePrice() {
request('https://markets.bisq.network/api/trades/?market=bsq_btc', { json: true }, (err, res, trades: BisqTrade[]) => {
if (err) { return console.log(err); }
const prices: number[] = [];
trades.forEach((trade) => {
prices.push(parseFloat(trade.price) * 100000000);
});
prices.sort((a, b) => a - b);
this.price = Common.median(prices);
if (this.priceUpdateCallbackFunction) {
this.priceUpdateCallbackFunction(this.price);
}
});
}
private async loadBisqDumpFile(): Promise<void> {
try {
const data = await this.loadData();

View File

@ -12,6 +12,7 @@ import { Common } from './common';
class WebsocketHandler {
private wss: WebSocket.Server | undefined;
private nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
private extraInitProperties = {};
constructor() { }
@ -19,6 +20,10 @@ class WebsocketHandler {
this.wss = wss;
}
setExtraInitProperties(property: string, value: any) {
this.extraInitProperties[property] = value;
}
setupConnectionHandling() {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
@ -84,6 +89,7 @@ class WebsocketHandler {
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
'git-commit': backendInfo.gitCommitHash,
'hostname': backendInfo.hostname,
...this.extraInitProperties
}));
}

View File

@ -53,6 +53,7 @@ class Server {
if (config.BISQ_ENABLED) {
bisq.startBisqService();
bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitProperties('bsq-price', price));
}
this.server.listen(config.HTTP_PORT, () => {

View File

@ -312,3 +312,13 @@ interface SpentInfo {
inputIndex: number;
txId: string;
}
export interface BisqTrade {
direction: string;
price: string;
amount: string;
volume: string;
payment_method: string;
trade_id: string;
trade_date: number;
}

View File

@ -31,7 +31,6 @@ import { BlockchainBlocksComponent } from './components/blockchain-blocks/blockc
import { BlockchainComponent } from './components/blockchain/blockchain.component';
import { FooterComponent } from './components/footer/footer.component';
import { AudioService } from './services/audio.service';
import { FiatComponent } from './fiat/fiat.component';
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component';
import { TimespanComponent } from './components/timespan/timespan.component';
@ -65,7 +64,6 @@ import { SharedModule } from './shared/shared.module';
MempoolBlocksComponent,
ChartistComponent,
FooterComponent,
FiatComponent,
MempoolBlockComponent,
FeeDistributionGraphComponent,
MempoolGraphComponent,

View File

@ -18,15 +18,15 @@
<tbody>
<tr>
<td>Total received</td>
<td>{{ totalReceived / 100 }} BSQ</td>
<td>{{ totalReceived / 100 | number: '1.2-2' }} BSQ</td>
</tr>
<tr>
<td>Total sent</td>
<td>{{ totalSent / 100 }} BSQ</td>
<td>{{ totalSent / 100 | number: '1.2-2' }} BSQ</td>
</tr>
<tr>
<td>Final balance</td>
<td>{{ (totalReceived - totalSent) / 100 }} BSQ</td>
<td>{{ (totalReceived - totalSent) / 100 | number: '1.2-2' }} BSQ</td>
</tr>
</tbody>
</table>
@ -58,7 +58,7 @@
<div class="clearfix"></div>
</div>
<app-bisq-transfers [tx]="tx"></app-bisq-transfers>
<app-bisq-transfers [tx]="tx" [showConfirmations]="true"></app-bisq-transfers>
<br>
</ng-template>

View File

@ -59,7 +59,7 @@
<div class="clearfix"></div>
</div>
<app-bisq-transfers [tx]="tx"></app-bisq-transfers>
<app-bisq-transfers [tx]="tx" [showConfirmations]="true"></app-bisq-transfers>
<br>
</ng-template>

View File

@ -4,47 +4,52 @@
<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 class="row">
<div class="col-sm">
<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><app-fiat [value]="price"></app-fiat></td>
</tr>
<tr>
<td>Market cap</td>
<td><app-fiat [value]="price * (stats.minted - stats.burnt) / 100 "></app-fiat></td>
</tr>
</tbody>
</table>
</div>
<div class="col-sm"></div>
</div>
</div>
<ng-template #loadingTemplate>

View File

@ -1,5 +1,5 @@
.td-width {
width: 300px;
width: 250px;
}
@media (max-width: 767.98px) {
@ -7,7 +7,3 @@
width: 175px;
}
}
.skeleton-loader {
width: 200px;
}

View File

@ -2,6 +2,7 @@ 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';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-bisq-stats',
@ -11,15 +12,22 @@ import { SeoService } from 'src/app/services/seo.service';
export class BisqStatsComponent implements OnInit {
isLoading = true;
stats: BisqStats;
price: number;
constructor(
private bisqApiService: BisqApiService,
private seoService: SeoService,
private stateService: StateService,
) { }
ngOnInit(): void {
ngOnInit() {
this.seoService.setTitle('BSQ Statistics', false);
this.stateService.bsqPrice$
.subscribe((bsqPrice) => {
this.price = bsqPrice;
});
this.bisqApiService.getStats$()
.subscribe((stats) => {
this.isLoading = false;

View File

@ -36,7 +36,7 @@
<tr>
<td class="td-width">Fee burnt</td>
<td>
{{ bisqTx.burntFee / 100 | number: '1.2-2' }} BSQ
{{ bisqTx.burntFee / 100 | number: '1.2-2' }} BSQ (<app-bsq-amount [bsq]="bisqTx.burntFee" [forceFiat]="true" [green]="true"></app-bsq-amount>)
</tr>
</tbody>
</table>

View File

@ -17,6 +17,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
bisqTx: BisqTransaction;
latestBlock$: Observable<Block>;
txId: string;
price: number;
isLoading = true;
subscription: Subscription;
@ -46,6 +47,11 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
});
this.latestBlock$ = this.stateService.blocks$.pipe(map((([block]) => block)));
this.stateService.bsqPrice$
.subscribe((bsqPrice) => {
this.price = bsqPrice;
});
}
ngOnDestroy() {

View File

@ -22,7 +22,7 @@
</a>
</td>
<td class="text-right nowrap">
{{ input.bsqAmount / 100 | number: '1.2-2' }} BSQ
<app-bsq-amount [bsq]="input.bsqAmount"></app-bsq-amount>
</td>
</tr>
</ng-template>
@ -42,7 +42,7 @@
</a>
</td>
<td class="text-right nowrap">
{{ output.bsqAmount / 100 | number: '1.2-2' }} BSQ
<app-bsq-amount [bsq]="output.bsqAmount"></app-bsq-amount>
</td>
<td class="pl-1 arrow-td">
<i *ngIf="!output.spentInfo; else spent" class="arrow green"></i>
@ -57,4 +57,21 @@
</div>
</div>
<div>
<div class="float-left mt-2-5" *ngIf="showConfirmations && tx.burntFee">
Fee: {{ tx.burntFee / 100 | number: '1.2-2' }} BSQ (<app-bsq-amount [bsq]="tx.burntFee" [forceFiat]="true" [green]="true"></app-bsq-amount>)
</div>
<div class="float-right">
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
<button type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.blockHeight + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.blockHeight + 1 > 1">s</ng-container></button>
&nbsp;
</span>
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
<app-bsq-amount [bsq]="totalOutput"></app-bsq-amount>
</button>
</div>
<div class="clearfix"></div>
</div>
</div>

View File

@ -1,5 +1,9 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
import { BisqTransaction } from 'src/app/bisq/bisq.interfaces';
import { StateService } from 'src/app/services/state.service';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Block } from 'src/app/interfaces/electrs.interface';
@Component({
selector: 'app-bisq-transfers',
@ -7,13 +11,32 @@ import { BisqTransaction } from 'src/app/bisq/bisq.interfaces';
styleUrls: ['./bisq-transfers.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BisqTransfersComponent {
export class BisqTransfersComponent implements OnInit, OnChanges {
@Input() tx: BisqTransaction;
@Input() showConfirmations = false;
constructor() { }
totalOutput: number;
latestBlock$: Observable<Block>;
constructor(
private stateService: StateService,
) { }
trackByIndexFn(index: number) {
return index;
}
ngOnInit() {
this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block));
}
ngOnChanges() {
this.totalOutput = this.tx.outputs.filter((output) => output.isVerified).reduce((acc, output) => acc + output.bsqAmount, 0);;
}
switchCurrency() {
const oldvalue = !this.stateService.viewFiat$.value;
this.stateService.viewFiat$.next(oldvalue);
}
}

View File

@ -17,6 +17,7 @@ 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';
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
@NgModule({
declarations: [
@ -31,6 +32,7 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
BisqExplorerComponent,
BisqAddressComponent,
BisqStatsComponent,
BsqAmountComponent,
],
imports: [
CommonModule,

View File

@ -0,0 +1,6 @@
<ng-container *ngIf="(forceFiat || (viewFiat$ | async)) && (conversions$ | async) as conversions; else viewFiatVin">
<span [class.green-color]="green">{{ conversions.USD * bsq / 100 * (bsqPrice$ | async) / 100000000 | currency:'USD':'symbol':'1.2-2' }}</span>
</ng-container>
<ng-template #viewFiatVin>
{{ bsq / 100 | number : digitsInfo }} BSQ
</ng-template>

View File

@ -0,0 +1,3 @@
.green-color {
color: #3bcc49;
}

View File

@ -0,0 +1,30 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { StateService } from 'src/app/services/state.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-bsq-amount',
templateUrl: './bsq-amount.component.html',
styleUrls: ['./bsq-amount.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BsqAmountComponent implements OnInit {
conversions$: Observable<any>;
viewFiat$: Observable<boolean>;
bsqPrice$: Observable<number>;
@Input() bsq: number;
@Input() digitsInfo = '1.2-2';
@Input() forceFiat = false;
@Input() green = false;
constructor(
private stateService: StateService,
) { }
ngOnInit() {
this.viewFiat$ = this.stateService.viewFiat$.asObservable();
this.conversions$ = this.stateService.conversions$.asObservable();
this.bsqPrice$ = this.stateService.bsqPrice$;
}
}

View File

@ -23,6 +23,7 @@ export class StateService {
networkChanged$ = new ReplaySubject<string>(1);
blocks$ = new ReplaySubject<[Block, boolean, boolean]>(env.KEEP_BLOCKS_AMOUNT);
conversions$ = new ReplaySubject<any>(1);
bsqPrice$ = new ReplaySubject<number>(1);
mempoolStats$ = new ReplaySubject<MemPoolState>(1);
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
txReplaced$ = new Subject<Transaction>();

View File

@ -99,6 +99,10 @@ export class WebsocketService {
this.stateService.mempoolBlocks$.next(response['mempool-blocks']);
}
if (response['bsq-price']) {
this.stateService.bsqPrice$.next(response['bsq-price']);
}
if (response['git-commit']) {
if (!this.latestGitCommit) {
this.latestGitCommit = response['git-commit'];

View File

@ -11,6 +11,7 @@ import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe';
import { TimeSinceComponent } from '../components/time-since/time-since.component';
import { ClipboardComponent } from '../components/clipboard/clipboard.component';
import { QrcodeComponent } from '../components/qrcode/qrcode.component';
import { FiatComponent } from '../fiat/fiat.component';
@NgModule({
declarations: [
@ -25,6 +26,7 @@ import { QrcodeComponent } from '../components/qrcode/qrcode.component';
ClipboardComponent,
TimeSinceComponent,
QrcodeComponent,
FiatComponent,
],
imports: [
CommonModule,
@ -44,6 +46,7 @@ import { QrcodeComponent } from '../components/qrcode/qrcode.component';
TimeSinceComponent,
ClipboardComponent,
QrcodeComponent,
FiatComponent,
]
})
export class SharedModule {}