diff --git a/backend/src/api/bisq.ts b/backend/src/api/bisq.ts index 1fee079af..cc2578af9 100644 --- a/backend/src/api/bisq.ts +++ b/backend/src/api/bisq.ts @@ -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 { try { const data = await this.loadData(); diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index fef583d4e..1174a261d 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -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 })); } diff --git a/backend/src/index.ts b/backend/src/index.ts index 41ea8c6b5..5c20cf2e6 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -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, () => { diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index fa8dcfd38..9da3cde44 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -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; +} diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 8e11f8383..e25791775 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -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, diff --git a/frontend/src/app/bisq/bisq-address/bisq-address.component.html b/frontend/src/app/bisq/bisq-address/bisq-address.component.html index ef04202c4..054fb1fcb 100644 --- a/frontend/src/app/bisq/bisq-address/bisq-address.component.html +++ b/frontend/src/app/bisq/bisq-address/bisq-address.component.html @@ -18,15 +18,15 @@ Total received - {{ totalReceived / 100 }} BSQ + {{ totalReceived / 100 | number: '1.2-2' }} BSQ Total sent - {{ totalSent / 100 }} BSQ + {{ totalSent / 100 | number: '1.2-2' }} BSQ Final balance - {{ (totalReceived - totalSent) / 100 }} BSQ + {{ (totalReceived - totalSent) / 100 | number: '1.2-2' }} BSQ @@ -58,7 +58,7 @@
- +
diff --git a/frontend/src/app/bisq/bisq-block/bisq-block.component.html b/frontend/src/app/bisq/bisq-block/bisq-block.component.html index f2fd3c7cd..148ab3121 100644 --- a/frontend/src/app/bisq/bisq-block/bisq-block.component.html +++ b/frontend/src/app/bisq/bisq-block/bisq-block.component.html @@ -59,7 +59,7 @@
- +
diff --git a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html index c565edfcd..4d23bc87c 100644 --- a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html +++ b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.html @@ -4,47 +4,52 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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
+
+
+
diff --git a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss index 2c5c3f28a..e1f02094f 100644 --- a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss +++ b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.scss @@ -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; -} diff --git a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts index 314b575f3..787cad58c 100644 --- a/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts +++ b/frontend/src/app/bisq/bisq-stats/bisq-stats.component.ts @@ -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; diff --git a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html index 3625fcceb..1c8a0c345 100644 --- a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html +++ b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html @@ -36,7 +36,7 @@ Fee burnt - {{ bisqTx.burntFee / 100 | number: '1.2-2' }} BSQ + {{ bisqTx.burntFee / 100 | number: '1.2-2' }} BSQ () diff --git a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts index af7f764e5..94aa19733 100644 --- a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts +++ b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts @@ -17,6 +17,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy { bisqTx: BisqTransaction; latestBlock$: Observable; 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() { diff --git a/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html b/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html index 45cc0e971..e7f7eadd1 100644 --- a/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html +++ b/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html @@ -22,7 +22,7 @@ - {{ input.bsqAmount / 100 | number: '1.2-2' }} BSQ + @@ -42,7 +42,7 @@ - {{ output.bsqAmount / 100 | number: '1.2-2' }} BSQ + @@ -57,4 +57,21 @@ +
+
+ Fee: {{ tx.burntFee / 100 | number: '1.2-2' }} BSQ () +
+ +
+ + +   + + +
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.ts b/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.ts index 8f0a76307..be391305c 100644 --- a/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.ts +++ b/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.ts @@ -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; + + 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); + } + } diff --git a/frontend/src/app/bisq/bisq.module.ts b/frontend/src/app/bisq/bisq.module.ts index 89fe7db09..3d0314f14 100644 --- a/frontend/src/app/bisq/bisq.module.ts +++ b/frontend/src/app/bisq/bisq.module.ts @@ -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, diff --git a/frontend/src/app/bisq/bsq-amount/bsq-amount.component.html b/frontend/src/app/bisq/bsq-amount/bsq-amount.component.html new file mode 100644 index 000000000..f5de4cea6 --- /dev/null +++ b/frontend/src/app/bisq/bsq-amount/bsq-amount.component.html @@ -0,0 +1,6 @@ + + {{ conversions.USD * bsq / 100 * (bsqPrice$ | async) / 100000000 | currency:'USD':'symbol':'1.2-2' }} + + + {{ bsq / 100 | number : digitsInfo }} BSQ + diff --git a/frontend/src/app/bisq/bsq-amount/bsq-amount.component.scss b/frontend/src/app/bisq/bsq-amount/bsq-amount.component.scss new file mode 100644 index 000000000..843bd58b6 --- /dev/null +++ b/frontend/src/app/bisq/bsq-amount/bsq-amount.component.scss @@ -0,0 +1,3 @@ +.green-color { + color: #3bcc49; +} diff --git a/frontend/src/app/bisq/bsq-amount/bsq-amount.component.ts b/frontend/src/app/bisq/bsq-amount/bsq-amount.component.ts new file mode 100644 index 000000000..263b9d7f7 --- /dev/null +++ b/frontend/src/app/bisq/bsq-amount/bsq-amount.component.ts @@ -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; + viewFiat$: Observable; + bsqPrice$: Observable; + + @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$; + } +} diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 524637fcb..031ee8902 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -23,6 +23,7 @@ export class StateService { networkChanged$ = new ReplaySubject(1); blocks$ = new ReplaySubject<[Block, boolean, boolean]>(env.KEEP_BLOCKS_AMOUNT); conversions$ = new ReplaySubject(1); + bsqPrice$ = new ReplaySubject(1); mempoolStats$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); txReplaced$ = new Subject(); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index fc1ecc9c6..01f319b20 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -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']; diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 3c6c656dc..315e78b8c 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -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 {}