Fetch historical data based on timestamp and currency
This commit is contained in:
		
							parent
							
								
									23076172e4
								
							
						
					
					
						commit
						ccf1121f19
					
				@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -335,7 +335,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
    <app-transactions-list [transactions]="transactions" [paginated]="true"></app-transactions-list>
 | 
			
		||||
    <app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list>
 | 
			
		||||
 | 
			
		||||
    <ng-template [ngIf]="transactionsError">
 | 
			
		||||
      <div class="text-center">
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<BlockExtended>;
 | 
			
		||||
  outspendsSubscription: Subscription;
 | 
			
		||||
  currencyChangeSubscription: Subscription;
 | 
			
		||||
  currency: string;
 | 
			
		||||
  refreshOutspends$: ReplaySubject<string[]> = new ReplaySubject();
 | 
			
		||||
  refreshChannels$: ReplaySubject<string[]> = new ReplaySubject();
 | 
			
		||||
  showDetails$ = new BehaviorSubject<boolean>(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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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<Price | undefined> {
 | 
			
		||||
  getBlockPrice$(blockTimestamp: number, singlePrice = false, currency: string): Observable<Price | undefined> {
 | 
			
		||||
    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(
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user