diff --git a/backend/src/repositories/PricesRepository.ts b/backend/src/repositories/PricesRepository.ts index d72ea45b7..e5acec861 100644 --- a/backend/src/repositories/PricesRepository.ts +++ b/backend/src/repositories/PricesRepository.ts @@ -329,14 +329,29 @@ class PricesRepository { throw Error(`Cannot get single historical price from the database`); } + let pricesUsedForExchangeRates; // If we don't have a fx API key, we need to use the latest prices to compute the exchange rates + if (!config.MEMPOOL.CURRENCY_API_KEY) { + const [latestPrices] = await DB.query(` + SELECT ${ApiPriceFields} + FROM prices + ORDER BY time DESC + LIMIT 1 + `); + if (!Array.isArray(latestPrices)) { + throw Error(`Cannot get single historical price from the database`); + } + pricesUsedForExchangeRates = latestPrices[0] as ApiPrice; + } else { + pricesUsedForExchangeRates = rates[0] as ApiPrice; + } + // Compute fiat exchange rates - let latestPrice = rates[0] as ApiPrice; + let latestPrice = pricesUsedForExchangeRates; if (!latestPrice || latestPrice.USD === -1) { latestPrice = priceUpdater.getEmptyPricesObj(); } - const computeFx = (usd: number, other: number): number => - Math.round(Math.max(other, 0) / Math.max(usd, 1) * 100) / 100; + const computeFx = (usd: number, other: number): number => usd <= 0.05 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; const exchangeRates: ExchangeRates = config.MEMPOOL.CURRENCY_API_KEY ? { @@ -388,7 +403,8 @@ class PricesRepository { const filteredRates = rates.map((rate: any) => { return { time: rate.time, - [currency]: rate[currency] + [currency]: rate[currency], + ['USD']: rate['USD'] }; }); return { @@ -424,8 +440,8 @@ class PricesRepository { latestPrice = priceUpdater.getEmptyPricesObj(); } - const computeFx = (usd: number, other: number): number => - Math.round(Math.max(other, 0) / Math.max(usd, 1) * 100) / 100; + const computeFx = (usd: number, other: number): number => + usd <= 0 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; const exchangeRates: ExchangeRates = config.MEMPOOL.CURRENCY_API_KEY ? { @@ -477,7 +493,8 @@ class PricesRepository { const filteredRates = rates.map((rate: any) => { return { time: rate.time, - [currency]: rate[currency] + [currency]: rate[currency], + ['USD']: rate['USD'] }; }); return { diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 777be0907..6b5a6a846 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -335,7 +335,7 @@
- +
diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 1eb1c4798..3cb60e048 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -526,9 +526,9 @@ export class BlockComponent implements OnInit, OnDestroy { if (this.priceSubscription) { this.priceSubscription.unsubscribe(); } - this.priceSubscription = block$.pipe( - switchMap((block) => { - return this.priceService.getBlockPrice$(block.timestamp).pipe( + this.priceSubscription = combineLatest([this.stateService.fiatCurrency$, block$]).pipe( + switchMap(([currency, block]) => { + return this.priceService.getBlockPrice$(block.timestamp, true, currency).pipe( tap((price) => { this.blockConversion = price; }) diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 0167a3d43..ea397ee90 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -76,6 +76,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { mempoolBlocksSubscription: Subscription; blocksSubscription: Subscription; miningSubscription: Subscription; + currencyChangeSubscription: Subscription; fragmentParams: URLSearchParams; rbfTransaction: undefined | Transaction; replaced: boolean = false; @@ -493,10 +494,12 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } } this.fetchRbfHistory$.next(this.tx.txid); - - this.priceService.getBlockPrice$(tx.status?.block_time, true).pipe( - tap((price) => { - this.blockConversion = price; + this.currencyChangeSubscription?.unsubscribe(); + this.currencyChangeSubscription = this.stateService.fiatCurrency$.pipe( + switchMap((currency) => { + return tx.status.block_time ? this.priceService.getBlockPrice$(tx.status.block_time, true, currency).pipe( + tap((price) => tx['price'] = price), + ) : of(undefined); }) ).subscribe(); @@ -810,6 +813,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.mempoolBlocksSubscription.unsubscribe(); this.blocksSubscription.unsubscribe(); this.miningSubscription?.unsubscribe(); + this.currencyChangeSubscription?.unsubscribe(); this.leaveTransaction(); } } diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index da9bdfe04..8f91489c1 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -32,11 +32,14 @@ export class TransactionsListComponent implements OnInit, OnChanges { @Input() outputIndex: number; @Input() address: string = ''; @Input() rowLimit = 12; + @Input() blockTime: number = 0; // Used for price calculation if all the transactions are in the same block @Output() loadMore = new EventEmitter(); latestBlock$: Observable; outspendsSubscription: Subscription; + currencyChangeSubscription: Subscription; + currency: string; refreshOutspends$: ReplaySubject = new ReplaySubject(); refreshChannels$: ReplaySubject = new ReplaySubject(); showDetails$ = new BehaviorSubject(false); @@ -125,6 +128,35 @@ export class TransactionsListComponent implements OnInit, OnChanges { ) , ).subscribe(() => this.ref.markForCheck()); + + this.currencyChangeSubscription = this.stateService.fiatCurrency$ + .subscribe(currency => { + this.currency = currency; + this.refreshPrice(); + }); + } + + refreshPrice(): void { + // Loop over all transactions + if (!this.transactions || !this.transactions.length || !this.currency) { + return; + } + const confirmedTxs = this.transactions.filter((tx) => tx.status.confirmed).length; + if (!this.blockTime) { + this.transactions.forEach((tx) => { + if (!this.blockTime) { + if (tx.status.block_time) { + this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 10, this.currency).pipe( + tap((price) => tx['price'] = price), + ).subscribe(); + } + } + }); + } else { + this.priceService.getBlockPrice$(this.blockTime, true, this.currency).pipe( + tap((price) => this.transactions.forEach((tx) => tx['price'] = price)), + ).subscribe(); + } } ngOnChanges(changes): void { @@ -148,6 +180,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { this.transactionsLength = this.transactions.length; this.cacheService.setTxCache(this.transactions); + const confirmedTxs = this.transactions.filter((tx) => tx.status.confirmed).length; this.transactions.forEach((tx) => { tx['@voutLimit'] = true; tx['@vinLimit'] = true; @@ -197,10 +230,18 @@ export class TransactionsListComponent implements OnInit, OnChanges { } } - this.priceService.getBlockPrice$(tx.status.block_time).pipe( - tap((price) => tx['price'] = price) - ).subscribe(); + if (!this.blockTime && tx.status.block_time && this.currency) { + this.priceService.getBlockPrice$(tx.status.block_time, confirmedTxs < 10, this.currency).pipe( + tap((price) => tx['price'] = price), + ).subscribe(); + } }); + + if (this.blockTime && this.transactions?.length && this.currency) { + this.priceService.getBlockPrice$(this.blockTime, true, this.currency).pipe( + tap((price) => this.transactions.forEach((tx) => tx['price'] = price)), + ).subscribe(); + } const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid); if (txIds.length && !this.cached) { this.refreshOutspends$.next(txIds); @@ -308,5 +349,6 @@ export class TransactionsListComponent implements OnInit, OnChanges { ngOnDestroy(): void { this.outspendsSubscription.unsubscribe(); + this.currencyChangeSubscription?.unsubscribe(); } } diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts index 924982983..aa98779ca 100644 --- a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, ViewChild, Input, OnChanges, OnInit } from '@angular/core'; -import { tap } from 'rxjs'; +import { Subscription, of, switchMap, tap } from 'rxjs'; import { Price, PriceService } from '../../services/price.service'; import { StateService } from '../../services/state.service'; import { environment } from '../../../environments/environment'; @@ -35,6 +35,7 @@ export class TxBowtieGraphTooltipComponent implements OnChanges { tooltipPosition = { x: 0, y: 0 }; blockConversion: Price; + currencyChangeSubscription: Subscription; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; @@ -47,11 +48,14 @@ export class TxBowtieGraphTooltipComponent implements OnChanges { ngOnChanges(changes): void { if (changes.line?.currentValue) { - this.priceService.getBlockPrice$(changes.line?.currentValue.timestamp, true).pipe( - tap((price) => { - this.blockConversion = price; - }) - ).subscribe(); + this.currencyChangeSubscription?.unsubscribe(); + this.currencyChangeSubscription = this.stateService.fiatCurrency$.pipe( + switchMap((currency) => { + return changes.line?.currentValue.timestamp ? this.priceService.getBlockPrice$(changes.line?.currentValue.timestamp, true, currency).pipe( + tap((price) => this.blockConversion = price), + ) : of(undefined); + }) + ).subscribe(); } if (changes.cursorPosition && changes.cursorPosition.currentValue) { diff --git a/frontend/src/app/services/price.service.ts b/frontend/src/app/services/price.service.ts index 41f928343..7a2f42c95 100644 --- a/frontend/src/app/services/price.service.ts +++ b/frontend/src/app/services/price.service.ts @@ -98,6 +98,8 @@ export class PriceService { lastQueriedTimestamp: number; lastPriceHistoryUpdate: number; + lastQueriedCurrency: string; + lastQueriedHistoricalCurrency: string; historicalPrice: ConversionDict = { prices: null, @@ -130,7 +132,7 @@ export class PriceService { }; } - getBlockPrice$(blockTimestamp: number, singlePrice = false): Observable { + getBlockPrice$(blockTimestamp: number, singlePrice = false, currency: string): Observable { if (this.stateService.env.BASE_MODULE !== 'mempool' || !this.stateService.env.HISTORICAL_PRICE) { return of(undefined); } @@ -142,9 +144,10 @@ export class PriceService { * query a different timestamp than the last one */ if (singlePrice) { - if (!this.singlePriceObservable$ || (this.singlePriceObservable$ && blockTimestamp !== this.lastQueriedTimestamp)) { - this.singlePriceObservable$ = this.apiService.getHistoricalPrice$(blockTimestamp).pipe(shareReplay()); + if (!this.singlePriceObservable$ || (this.singlePriceObservable$ && (blockTimestamp !== this.lastQueriedTimestamp || currency !== this.lastQueriedCurrency))) { + this.singlePriceObservable$ = this.apiService.getHistoricalPrice$(blockTimestamp, currency).pipe(shareReplay()); this.lastQueriedTimestamp = blockTimestamp; + this.lastQueriedCurrency = currency; } return this.singlePriceObservable$.pipe( @@ -177,9 +180,10 @@ export class PriceService { * Query all price history only once. The observable is invalidated after 1 hour */ else { - if (!this.priceObservable$ || (this.priceObservable$ && (now - this.lastPriceHistoryUpdate > 3600))) { - this.priceObservable$ = this.apiService.getHistoricalPrice$(undefined).pipe(shareReplay()); + if (!this.priceObservable$ || (this.priceObservable$ && (now - this.lastPriceHistoryUpdate > 3600 || currency !== this.lastQueriedHistoricalCurrency))) { + this.priceObservable$ = this.apiService.getHistoricalPrice$(undefined, currency).pipe(shareReplay()); this.lastPriceHistoryUpdate = new Date().getTime() / 1000; + this.lastQueriedHistoricalCurrency = currency; } return this.priceObservable$.pipe(