parent
							
								
									6e7ed29caa
								
							
						
					
					
						commit
						f44eacd5d5
					
				@ -28,6 +28,16 @@ export interface Conversion {
 | 
			
		||||
  exchangeRates: ExchangeRates;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const MAX_PRICES = {
 | 
			
		||||
  USD: 100000000,
 | 
			
		||||
  EUR: 100000000,
 | 
			
		||||
  GBP: 100000000,
 | 
			
		||||
  CAD: 100000000,
 | 
			
		||||
  CHF: 100000000,
 | 
			
		||||
  AUD: 100000000,
 | 
			
		||||
  JPY: 10000000000,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PricesRepository {
 | 
			
		||||
  public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
 | 
			
		||||
    if (prices.USD === 0) {
 | 
			
		||||
@ -36,6 +46,14 @@ class PricesRepository {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sanity check
 | 
			
		||||
    for (const currency of Object.keys(prices)) {
 | 
			
		||||
      if (prices[currency] < -1 || prices[currency] > MAX_PRICES[currency]) { // We use -1 to mark a "missing data, so it's a valid entry"
 | 
			
		||||
        logger.info(`Ignore BTC${currency} price of ${prices[currency]}`);
 | 
			
		||||
        prices[currency] = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      await DB.query(`
 | 
			
		||||
        INSERT INTO prices(time,             USD, EUR, GBP, CAD, CHF, AUD, JPY)
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import path from 'path';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { IConversionRates } from '../mempool.interfaces';
 | 
			
		||||
import PricesRepository from '../repositories/PricesRepository';
 | 
			
		||||
import PricesRepository, { MAX_PRICES } from '../repositories/PricesRepository';
 | 
			
		||||
import BitfinexApi from './price-feeds/bitfinex-api';
 | 
			
		||||
import BitflyerApi from './price-feeds/bitflyer-api';
 | 
			
		||||
import CoinbaseApi from './price-feeds/coinbase-api';
 | 
			
		||||
@ -46,13 +46,13 @@ class PriceUpdater {
 | 
			
		||||
 | 
			
		||||
  public getEmptyPricesObj(): IConversionRates {
 | 
			
		||||
    return {
 | 
			
		||||
      USD: 0,
 | 
			
		||||
      EUR: 0,
 | 
			
		||||
      GBP: 0,
 | 
			
		||||
      CAD: 0,
 | 
			
		||||
      CHF: 0,
 | 
			
		||||
      AUD: 0,
 | 
			
		||||
      JPY: 0,
 | 
			
		||||
      USD: -1,
 | 
			
		||||
      EUR: -1,
 | 
			
		||||
      GBP: -1,
 | 
			
		||||
      CAD: -1,
 | 
			
		||||
      CHF: -1,
 | 
			
		||||
      AUD: -1,
 | 
			
		||||
      JPY: -1,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -115,7 +115,7 @@ class PriceUpdater {
 | 
			
		||||
        if (feed.currencies.includes(currency)) {
 | 
			
		||||
          try {
 | 
			
		||||
            const price = await feed.$fetchPrice(currency);
 | 
			
		||||
            if (price > 0) {
 | 
			
		||||
            if (price > -1 && price < MAX_PRICES[currency]) {
 | 
			
		||||
              prices.push(price);
 | 
			
		||||
            }
 | 
			
		||||
            logger.debug(`${feed.name} BTC/${currency} price: ${price}`, logger.tags.mining);
 | 
			
		||||
@ -239,7 +239,7 @@ class PriceUpdater {
 | 
			
		||||
 | 
			
		||||
        for (const currency of this.currencies) {
 | 
			
		||||
          const price = historicalEntry[time][currency];
 | 
			
		||||
          if (price > 0) {
 | 
			
		||||
          if (price > -1 && price < MAX_PRICES[currency]) {
 | 
			
		||||
            grouped[time][currency].push(typeof price === 'string' ? parseInt(price, 10) : price);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    {{ addPlus && satoshis >= 0 ? '+' : '' }}
 | 
			
		||||
    {{
 | 
			
		||||
      (
 | 
			
		||||
        (blockConversion.price[currency] > 0 ? blockConversion.price[currency] : null) ??
 | 
			
		||||
        (blockConversion.price[currency] >= 0 ? blockConversion.price[currency] : null) ??
 | 
			
		||||
        (blockConversion.price['USD'] * blockConversion.exchangeRates['USD' + currency]) ?? 0
 | 
			
		||||
      ) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency
 | 
			
		||||
    }}
 | 
			
		||||
 | 
			
		||||
@ -443,9 +443,9 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
    }
 | 
			
		||||
    this.priceSubscription = block$.pipe(
 | 
			
		||||
      switchMap((block) => {
 | 
			
		||||
        return this.priceService.getPrices().pipe(
 | 
			
		||||
          tap(() => {
 | 
			
		||||
            this.blockConversion = this.priceService.getPriceForTimestamp(block.timestamp);
 | 
			
		||||
        return this.priceService.getBlockPrice$(block.timestamp).pipe(
 | 
			
		||||
          tap((price) => {
 | 
			
		||||
            this.blockConversion = price;
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -327,9 +327,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
			
		||||
            this.fetchRbfHistory$.next(this.tx.txid);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.priceService.getPrices().pipe(
 | 
			
		||||
            tap(() => {
 | 
			
		||||
              this.blockConversion = this.priceService.getPriceForTimestamp(tx.status.block_time);
 | 
			
		||||
          this.priceService.getBlockPrice$(tx.status.block_time).pipe(
 | 
			
		||||
            tap((price) => {
 | 
			
		||||
              this.blockConversion = price;
 | 
			
		||||
            })
 | 
			
		||||
          ).subscribe();
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.inter
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
import { environment } from '../../../environments/environment';
 | 
			
		||||
import { AssetsService } from '../../services/assets.service';
 | 
			
		||||
import { filter, map, tap, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { filter, map, tap, switchMap, shareReplay } from 'rxjs/operators';
 | 
			
		||||
import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { ApiService } from '../../services/api.service';
 | 
			
		||||
import { PriceService } from 'src/app/services/price.service';
 | 
			
		||||
@ -150,10 +150,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
			
		||||
          tx['addressValue'] = addressIn - addressOut;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.priceService.getPrices().pipe(
 | 
			
		||||
          tap(() => {
 | 
			
		||||
            tx['price'] = this.priceService.getPriceForTimestamp(tx.status.block_time);
 | 
			
		||||
          })
 | 
			
		||||
        this.priceService.getBlockPrice$(tx.status.block_time).pipe(
 | 
			
		||||
          tap((price) => tx['price'] = price)
 | 
			
		||||
        ).subscribe();
 | 
			
		||||
      });
 | 
			
		||||
      const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<span class="green-color" *ngIf="blockConversion; else noblockconversion">
 | 
			
		||||
  {{
 | 
			
		||||
    (
 | 
			
		||||
      (blockConversion.price[currency] > 0 ? blockConversion.price[currency] : null) ??
 | 
			
		||||
      (blockConversion.price[currency] >= 0 ? blockConversion.price[currency] : null) ??
 | 
			
		||||
      (blockConversion.price['USD'] * blockConversion.exchangeRates['USD' + currency]) ?? 0
 | 
			
		||||
    ) * value / 100000000 | fiatCurrency : digitsInfo : currency
 | 
			
		||||
  }}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { map, Observable, of, shareReplay } from 'rxjs';
 | 
			
		||||
import { map, Observable, of, share, shareReplay, tap } from 'rxjs';
 | 
			
		||||
import { ApiService } from './api.service';
 | 
			
		||||
 | 
			
		||||
// nodejs backend interfaces
 | 
			
		||||
@ -40,6 +40,8 @@ export interface ConversionDict {
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
})
 | 
			
		||||
export class PriceService {
 | 
			
		||||
  priceObservable$: Observable<Conversion>;
 | 
			
		||||
 | 
			
		||||
  historicalPrice: ConversionDict = {
 | 
			
		||||
    prices: null,
 | 
			
		||||
    exchangeRates: null,
 | 
			
		||||
@ -61,65 +63,53 @@ export class PriceService {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fetch prices from the nodejs backend only once
 | 
			
		||||
   */
 | 
			
		||||
  getPrices(): Observable<void> {
 | 
			
		||||
    if (this.historicalPrice.prices) {
 | 
			
		||||
      return of(null);
 | 
			
		||||
  getBlockPrice$(blockTimestamp: number): Observable<Price | undefined> {
 | 
			
		||||
    if (!this.priceObservable$) {
 | 
			
		||||
      this.priceObservable$ = this.apiService.getHistoricalPrice$().pipe(shareReplay());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.apiService.getHistoricalPrice$().pipe(
 | 
			
		||||
      map((conversion: Conversion) => {
 | 
			
		||||
        if (!this.historicalPrice.prices) {
 | 
			
		||||
          this.historicalPrice.prices = Object();
 | 
			
		||||
    return this.priceObservable$.pipe(
 | 
			
		||||
      map((conversion) => {
 | 
			
		||||
        if (!blockTimestamp) {
 | 
			
		||||
          return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const historicalPrice = {
 | 
			
		||||
          prices: {},
 | 
			
		||||
          exchangeRates: conversion.exchangeRates,
 | 
			
		||||
        };
 | 
			
		||||
        for (const price of conversion.prices) {
 | 
			
		||||
          this.historicalPrice.prices[price.time] = {
 | 
			
		||||
          historicalPrice.prices[price.time] = {
 | 
			
		||||
            USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD,
 | 
			
		||||
            CHF: price.CHF, AUD: price.AUD, JPY: price.JPY
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
        this.historicalPrice.exchangeRates = conversion.exchangeRates;
 | 
			
		||||
        return;
 | 
			
		||||
      }),
 | 
			
		||||
      shareReplay(),
 | 
			
		||||
 | 
			
		||||
        const priceTimestamps = Object.keys(historicalPrice.prices);
 | 
			
		||||
        priceTimestamps.push(Number.MAX_SAFE_INTEGER.toString());
 | 
			
		||||
        priceTimestamps.sort().reverse();
 | 
			
		||||
        
 | 
			
		||||
        // Small trick here. Because latest blocks have higher timestamps than our
 | 
			
		||||
        // latest price timestamp (we only insert once every hour), we have no price for them.
 | 
			
		||||
        // Therefore we want to fallback to the websocket price by returning an undefined `price` field.
 | 
			
		||||
        // Since historicalPrice.prices[Number.MAX_SAFE_INTEGER] does not exists
 | 
			
		||||
        // it will return `undefined` and automatically use the websocket price.
 | 
			
		||||
        // This way we can differenciate blocks without prices like the genesis block
 | 
			
		||||
        // vs ones without a price (yet) like the latest blocks
 | 
			
		||||
    
 | 
			
		||||
        for (const t of priceTimestamps) {
 | 
			
		||||
          const priceTimestamp = parseInt(t, 10);
 | 
			
		||||
          if (blockTimestamp > priceTimestamp) {
 | 
			
		||||
            return {
 | 
			
		||||
              price: historicalPrice.prices[priceTimestamp],
 | 
			
		||||
              exchangeRates: historicalPrice.exchangeRates,
 | 
			
		||||
            };
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        return this.getEmptyPrice();    
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Note: The first block with a price we have is block 68952 (using MtGox price history)
 | 
			
		||||
   * 
 | 
			
		||||
   * @param blockTimestamp 
 | 
			
		||||
   */
 | 
			
		||||
  getPriceForTimestamp(blockTimestamp: number): Price | null {
 | 
			
		||||
    if (!blockTimestamp) {
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const priceTimestamps = Object.keys(this.historicalPrice.prices);
 | 
			
		||||
    priceTimestamps.push(Number.MAX_SAFE_INTEGER.toString());
 | 
			
		||||
    priceTimestamps.sort().reverse();
 | 
			
		||||
    
 | 
			
		||||
    // Small trick here. Because latest blocks have higher timestamps than our
 | 
			
		||||
    // latest price timestamp (we only insert once every hour), we have no price for them.
 | 
			
		||||
    // Therefore we want to fallback to the websocket price by returning an undefined `price` field.
 | 
			
		||||
    // Since this.historicalPrice.prices[Number.MAX_SAFE_INTEGER] does not exists
 | 
			
		||||
    // it will return `undefined` and automatically use the websocket price.
 | 
			
		||||
    // This way we can differenciate blocks without prices like the genesis block
 | 
			
		||||
    // vs ones without a price (yet) like the latest blocks
 | 
			
		||||
 | 
			
		||||
    for (const t of priceTimestamps) {
 | 
			
		||||
      const priceTimestamp = parseInt(t, 10);
 | 
			
		||||
      if (blockTimestamp > priceTimestamp) {
 | 
			
		||||
        return {
 | 
			
		||||
          price: this.historicalPrice.prices[priceTimestamp],
 | 
			
		||||
          exchangeRates: this.historicalPrice.exchangeRates,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.getEmptyPrice();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user