mirror of
https://github.com/mempool/mempool.git
synced 2025-03-26 17:51:45 +01:00
Bisq stats page.
BSQ prices.
This commit is contained in:
parent
b7376fbd8d
commit
ca0cf23d66
@ -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();
|
||||
|
@ -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
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -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, () => {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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() {
|
||||
|
@ -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>
|
||||
|
||||
</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>
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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>
|
@ -0,0 +1,3 @@
|
||||
.green-color {
|
||||
color: #3bcc49;
|
||||
}
|
30
frontend/src/app/bisq/bsq-amount/bsq-amount.component.ts
Normal file
30
frontend/src/app/bisq/bsq-amount/bsq-amount.component.ts
Normal 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$;
|
||||
}
|
||||
}
|
@ -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>();
|
||||
|
@ -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'];
|
||||
|
@ -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 {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user