Merge branch 'master' into mononaut/raise-memory-limits
This commit is contained in:
		
						commit
						4203972a49
					
				@ -1,13 +1,13 @@
 | 
				
			|||||||
import { Application, Request, Response } from 'express';
 | 
					import { Application, Request, Response } from 'express';
 | 
				
			||||||
import config from "../../config";
 | 
					import config from "../../config";
 | 
				
			||||||
import logger from '../../logger';
 | 
					import logger from '../../logger';
 | 
				
			||||||
import audits from '../audit';
 | 
					 | 
				
			||||||
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
 | 
					import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
 | 
				
			||||||
import BlocksRepository from '../../repositories/BlocksRepository';
 | 
					import BlocksRepository from '../../repositories/BlocksRepository';
 | 
				
			||||||
import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
 | 
					import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
 | 
				
			||||||
import HashratesRepository from '../../repositories/HashratesRepository';
 | 
					import HashratesRepository from '../../repositories/HashratesRepository';
 | 
				
			||||||
import bitcoinClient from '../bitcoin/bitcoin-client';
 | 
					import bitcoinClient from '../bitcoin/bitcoin-client';
 | 
				
			||||||
import mining from "./mining";
 | 
					import mining from "./mining";
 | 
				
			||||||
 | 
					import PricesRepository from '../../repositories/PricesRepository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MiningRoutes {
 | 
					class MiningRoutes {
 | 
				
			||||||
  public initRoutes(app: Application) {
 | 
					  public initRoutes(app: Application) {
 | 
				
			||||||
@ -32,9 +32,18 @@ class MiningRoutes {
 | 
				
			|||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
 | 
				
			||||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
 | 
				
			||||||
 | 
					      .get(config.MEMPOOL.API_URL_PREFIX + 'historical-price', this.$getHistoricalPrice)
 | 
				
			||||||
    ;
 | 
					    ;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $getHistoricalPrice(req: Request, res: Response): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      res.status(200).send(await PricesRepository.$getHistoricalPrice());
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async $getPool(req: Request, res: Response): Promise<void> {
 | 
					  private async $getPool(req: Request, res: Response): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const stats = await mining.$getPoolStat(req.params.slug);
 | 
					      const stats = await mining.$getPoolStat(req.params.slug);
 | 
				
			||||||
 | 
				
			|||||||
@ -100,6 +100,7 @@ class Mining {
 | 
				
			|||||||
        rank: rank++,
 | 
					        rank: rank++,
 | 
				
			||||||
        emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0,
 | 
					        emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0,
 | 
				
			||||||
        slug: poolInfo.slug,
 | 
					        slug: poolInfo.slug,
 | 
				
			||||||
 | 
					        avgMatchRate: poolInfo.avgMatchRate !== null ? Math.round(100 * poolInfo.avgMatchRate) / 100 : null,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      poolsStats.push(poolStat);
 | 
					      poolsStats.push(poolStat);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ export interface PoolInfo {
 | 
				
			|||||||
  link: string;
 | 
					  link: string;
 | 
				
			||||||
  blockCount: number;
 | 
					  blockCount: number;
 | 
				
			||||||
  slug: string;
 | 
					  slug: string;
 | 
				
			||||||
 | 
					  avgMatchRate: number | null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PoolStats extends PoolInfo {
 | 
					export interface PoolStats extends PoolInfo {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,16 +27,25 @@ class PoolsRepository {
 | 
				
			|||||||
  public async $getPoolsInfo(interval: string | null = null): Promise<PoolInfo[]> {
 | 
					  public async $getPoolsInfo(interval: string | null = null): Promise<PoolInfo[]> {
 | 
				
			||||||
    interval = Common.getSqlInterval(interval);
 | 
					    interval = Common.getSqlInterval(interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let query = `SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link, slug
 | 
					    let query = `
 | 
				
			||||||
 | 
					      SELECT
 | 
				
			||||||
 | 
					        COUNT(blocks.height) As blockCount,
 | 
				
			||||||
 | 
					          pool_id AS poolId,
 | 
				
			||||||
 | 
					          pools.name AS name,
 | 
				
			||||||
 | 
					          pools.link AS link,
 | 
				
			||||||
 | 
					          slug,
 | 
				
			||||||
 | 
					          AVG(blocks_audits.match_rate) AS avgMatchRate
 | 
				
			||||||
      FROM blocks
 | 
					      FROM blocks
 | 
				
			||||||
      JOIN pools on pools.id = pool_id`;
 | 
					      JOIN pools on pools.id = pool_id
 | 
				
			||||||
 | 
					      LEFT JOIN blocks_audits ON blocks_audits.height = blocks.height
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (interval) {
 | 
					    if (interval) {
 | 
				
			||||||
      query += ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
					      query += ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    query += ` GROUP BY pool_id
 | 
					    query += ` GROUP BY pool_id
 | 
				
			||||||
      ORDER BY COUNT(height) DESC`;
 | 
					      ORDER BY COUNT(blocks.height) DESC`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const [rows] = await DB.query(query);
 | 
					      const [rows] = await DB.query(query);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,31 @@ import logger from '../logger';
 | 
				
			|||||||
import { IConversionRates } from '../mempool.interfaces';
 | 
					import { IConversionRates } from '../mempool.interfaces';
 | 
				
			||||||
import priceUpdater from '../tasks/price-updater';
 | 
					import priceUpdater from '../tasks/price-updater';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ApiPrice {
 | 
				
			||||||
 | 
					  time?: number,
 | 
				
			||||||
 | 
					  USD: number,
 | 
				
			||||||
 | 
					  EUR: number,
 | 
				
			||||||
 | 
					  GBP: number,
 | 
				
			||||||
 | 
					  CAD: number,
 | 
				
			||||||
 | 
					  CHF: number,
 | 
				
			||||||
 | 
					  AUD: number,
 | 
				
			||||||
 | 
					  JPY: number,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ExchangeRates {
 | 
				
			||||||
 | 
					  USDEUR: number,
 | 
				
			||||||
 | 
					  USDGBP: number,
 | 
				
			||||||
 | 
					  USDCAD: number,
 | 
				
			||||||
 | 
					  USDCHF: number,
 | 
				
			||||||
 | 
					  USDAUD: number,
 | 
				
			||||||
 | 
					  USDJPY: number,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Conversion {
 | 
				
			||||||
 | 
					  prices: ApiPrice[],
 | 
				
			||||||
 | 
					  exchangeRates: ExchangeRates;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PricesRepository {
 | 
					class PricesRepository {
 | 
				
			||||||
  public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
 | 
					  public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
 | 
				
			||||||
    if (prices.USD === 0) {
 | 
					    if (prices.USD === 0) {
 | 
				
			||||||
@ -60,6 +85,34 @@ class PricesRepository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return rates[0];
 | 
					    return rates[0];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $getHistoricalPrice(): Promise<Conversion | null> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const [rates]: any[] = await DB.query(`SELECT *, UNIX_TIMESTAMP(time) as time FROM prices ORDER BY time DESC`);
 | 
				
			||||||
 | 
					      if (!rates) {
 | 
				
			||||||
 | 
					        throw Error(`Cannot get average historical price from the database`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Compute fiat exchange rates
 | 
				
			||||||
 | 
					      const latestPrice: ApiPrice = rates[0];
 | 
				
			||||||
 | 
					      const exchangeRates: ExchangeRates = {
 | 
				
			||||||
 | 
					        USDEUR: Math.round(latestPrice.EUR / latestPrice.USD * 100) / 100,
 | 
				
			||||||
 | 
					        USDGBP: Math.round(latestPrice.GBP / latestPrice.USD * 100) / 100,
 | 
				
			||||||
 | 
					        USDCAD: Math.round(latestPrice.CAD / latestPrice.USD * 100) / 100,
 | 
				
			||||||
 | 
					        USDCHF: Math.round(latestPrice.CHF / latestPrice.USD * 100) / 100,
 | 
				
			||||||
 | 
					        USDAUD: Math.round(latestPrice.AUD / latestPrice.USD * 100) / 100,
 | 
				
			||||||
 | 
					        USDJPY: Math.round(latestPrice.JPY / latestPrice.USD * 100) / 100,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        prices: rates,
 | 
				
			||||||
 | 
					        exchangeRates: exchangeRates
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err(`Cannot fetch averaged historical prices from the db. Reason ${e instanceof Error ? e.message : e}`);
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new PricesRepository();
 | 
					export default new PricesRepository();
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import { AppComponent } from './components/app/app.component';
 | 
				
			|||||||
import { ElectrsApiService } from './services/electrs-api.service';
 | 
					import { ElectrsApiService } from './services/electrs-api.service';
 | 
				
			||||||
import { StateService } from './services/state.service';
 | 
					import { StateService } from './services/state.service';
 | 
				
			||||||
import { CacheService } from './services/cache.service';
 | 
					import { CacheService } from './services/cache.service';
 | 
				
			||||||
 | 
					import { PriceService } from './services/price.service';
 | 
				
			||||||
import { EnterpriseService } from './services/enterprise.service';
 | 
					import { EnterpriseService } from './services/enterprise.service';
 | 
				
			||||||
import { WebsocketService } from './services/websocket.service';
 | 
					import { WebsocketService } from './services/websocket.service';
 | 
				
			||||||
import { AudioService } from './services/audio.service';
 | 
					import { AudioService } from './services/audio.service';
 | 
				
			||||||
@ -26,6 +27,7 @@ const providers = [
 | 
				
			|||||||
  ElectrsApiService,
 | 
					  ElectrsApiService,
 | 
				
			||||||
  StateService,
 | 
					  StateService,
 | 
				
			||||||
  CacheService,
 | 
					  CacheService,
 | 
				
			||||||
 | 
					  PriceService,
 | 
				
			||||||
  WebsocketService,
 | 
					  WebsocketService,
 | 
				
			||||||
  AudioService,
 | 
					  AudioService,
 | 
				
			||||||
  SeoService,
 | 
					  SeoService,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,18 @@
 | 
				
			|||||||
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
 | 
					<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
 | 
				
			||||||
 | 
					  <span class="fiat" *ngIf="blockConversion; else noblockconversion">
 | 
				
			||||||
 | 
					    {{ addPlus && satoshis >= 0 ? '+' : '' }}
 | 
				
			||||||
 | 
					    {{
 | 
				
			||||||
 | 
					      (
 | 
				
			||||||
 | 
					        (blockConversion.price[currency] > 0 ? blockConversion.price[currency] : null) ??
 | 
				
			||||||
 | 
					        (blockConversion.price['USD'] * blockConversion.exchangeRates['USD' + currency]) ?? 0
 | 
				
			||||||
 | 
					      ) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency
 | 
				
			||||||
 | 
					    }}
 | 
				
			||||||
 | 
					  </span>
 | 
				
			||||||
 | 
					  <ng-template #noblockconversion>
 | 
				
			||||||
    <span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ (conversions ? conversions[currency] : 0) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
 | 
					    <span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ (conversions ? conversions[currency] : 0) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
 | 
				
			||||||
 | 
					  </ng-template>
 | 
				
			||||||
</ng-container>
 | 
					</ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <ng-template #viewFiatVin>
 | 
					  <ng-template #viewFiatVin>
 | 
				
			||||||
  <ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
 | 
					  <ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
 | 
				
			||||||
    <span i18n="shared.confidential">Confidential</span>
 | 
					    <span i18n="shared.confidential">Confidential</span>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
 | 
					import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
 | 
				
			||||||
import { StateService } from '../../services/state.service';
 | 
					import { StateService } from '../../services/state.service';
 | 
				
			||||||
import { Observable, Subscription } from 'rxjs';
 | 
					import { Observable, Subscription } from 'rxjs';
 | 
				
			||||||
 | 
					import { Price } from 'src/app/services/price.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-amount',
 | 
					  selector: 'app-amount',
 | 
				
			||||||
@ -21,6 +22,7 @@ export class AmountComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  @Input() digitsInfo = '1.8-8';
 | 
					  @Input() digitsInfo = '1.8-8';
 | 
				
			||||||
  @Input() noFiat = false;
 | 
					  @Input() noFiat = false;
 | 
				
			||||||
  @Input() addPlus = false;
 | 
					  @Input() addPlus = false;
 | 
				
			||||||
 | 
					  @Input() blockConversion: Price;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private stateService: StateService,
 | 
					    private stateService: StateService,
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
				
			||||||
          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" formControlName="dateSpan"> 24h
 | 
					          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" formControlName="dateSpan"> 24h
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
 | 
				
			|||||||
@ -78,3 +78,8 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
 | 
				
			||||||
          <input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]" formControlName="dateSpan"> 1M
 | 
					          <input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]" formControlName="dateSpan"> 1M
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
 | 
				
			|||||||
@ -78,3 +78,8 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -4,9 +4,9 @@ import { Observable, Subscription } from 'rxjs';
 | 
				
			|||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
					import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
				
			||||||
import { ApiService } from '../../services/api.service';
 | 
					import { ApiService } from '../../services/api.service';
 | 
				
			||||||
import { SeoService } from '../../services/seo.service';
 | 
					import { SeoService } from '../../services/seo.service';
 | 
				
			||||||
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
 | 
					import { formatNumber } from '@angular/common';
 | 
				
			||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
					import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
				
			||||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
 | 
					import { download, formatterXAxis } from '../../shared/graphs.utils';
 | 
				
			||||||
import { StateService } from '../../services/state.service';
 | 
					import { StateService } from '../../services/state.service';
 | 
				
			||||||
import { StorageService } from '../../services/storage.service';
 | 
					import { StorageService } from '../../services/storage.service';
 | 
				
			||||||
import { MiningService } from '../../services/mining.service';
 | 
					import { MiningService } from '../../services/mining.service';
 | 
				
			||||||
@ -92,6 +92,7 @@ export class BlockFeesGraphComponent implements OnInit {
 | 
				
			|||||||
      .pipe(
 | 
					      .pipe(
 | 
				
			||||||
        startWith(this.radioGroupForm.controls.dateSpan.value),
 | 
					        startWith(this.radioGroupForm.controls.dateSpan.value),
 | 
				
			||||||
        switchMap((timespan) => {
 | 
					        switchMap((timespan) => {
 | 
				
			||||||
 | 
					          this.isLoading = true;
 | 
				
			||||||
          this.storageService.setValue('miningWindowPreference', timespan);
 | 
					          this.storageService.setValue('miningWindowPreference', timespan);
 | 
				
			||||||
          this.timespan = timespan;
 | 
					          this.timespan = timespan;
 | 
				
			||||||
          this.isLoading = true;
 | 
					          this.isLoading = true;
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
				
			||||||
          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 24h
 | 
					          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 24h
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
 | 
				
			|||||||
@ -78,3 +78,8 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
 | 
				
			||||||
          <input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 1M
 | 
					          <input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 1M
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
@ -31,7 +31,7 @@
 | 
				
			|||||||
          <input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 3Y
 | 
					          <input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 3Y
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
 | 
					        <label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
 | 
				
			||||||
          <input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"> ALL
 | 
					          <input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> ALL
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
 | 
				
			|||||||
@ -78,3 +78,8 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -80,7 +80,7 @@ export class BlockRewardsGraphComponent implements OnInit {
 | 
				
			|||||||
    this.route
 | 
					    this.route
 | 
				
			||||||
      .fragment
 | 
					      .fragment
 | 
				
			||||||
      .subscribe((fragment) => {
 | 
					      .subscribe((fragment) => {
 | 
				
			||||||
        if (['3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
 | 
					        if (['1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
 | 
				
			||||||
          this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
 | 
					          this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(blockSizesWeightsObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(blockSizesWeightsObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
				
			||||||
          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" formControlName="dateSpan"> 24h
 | 
					          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" formControlName="dateSpan"> 24h
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
 | 
				
			|||||||
@ -78,3 +78,8 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -124,7 +124,13 @@
 | 
				
			|||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      <tr *ngIf="block?.extras?.medianFee != undefined">
 | 
					      <tr *ngIf="block?.extras?.medianFee != undefined">
 | 
				
			||||||
        <td class="td-width" i18n="block.median-fee">Median fee</td>
 | 
					        <td class="td-width" i18n="block.median-fee">Median fee</td>
 | 
				
			||||||
        <td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
 | 
					        <td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
				
			||||||
 | 
					          <span class="fiat">
 | 
				
			||||||
 | 
					            <app-fiat [blockConversion]="blockConversion" [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2"
 | 
				
			||||||
 | 
					              i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes"
 | 
				
			||||||
 | 
					              placement="bottom"></app-fiat>
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      <ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
 | 
					      <ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
@ -132,13 +138,13 @@
 | 
				
			|||||||
          <td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
 | 
					          <td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
 | 
				
			||||||
            <app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
					            <app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
				
			||||||
            <span class="fiat">
 | 
					            <span class="fiat">
 | 
				
			||||||
              <app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
 | 
					              <app-fiat [blockConversion]="blockConversion" [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
          <ng-template #liquidTotalFees>
 | 
					          <ng-template #liquidTotalFees>
 | 
				
			||||||
            <td>
 | 
					            <td>
 | 
				
			||||||
              <app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount>  <app-fiat
 | 
					              <app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount>  <app-fiat
 | 
				
			||||||
                [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
 | 
					              [blockConversion]="blockConversion" [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </ng-template>
 | 
					          </ng-template>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
@ -147,7 +153,7 @@
 | 
				
			|||||||
          <td>
 | 
					          <td>
 | 
				
			||||||
            <app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
					            <app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
				
			||||||
            <span class="fiat">
 | 
					            <span class="fiat">
 | 
				
			||||||
              <app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
 | 
					              <app-fiat [blockConversion]="blockConversion" [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
 | 
				
			||||||
            </span>
 | 
					            </span>
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces
 | 
				
			|||||||
import { ApiService } from '../../services/api.service';
 | 
					import { ApiService } from '../../services/api.service';
 | 
				
			||||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
 | 
					import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
 | 
				
			||||||
import { detectWebGL } from '../../shared/graphs.utils';
 | 
					import { detectWebGL } from '../../shared/graphs.utils';
 | 
				
			||||||
 | 
					import { PriceService, Price } from 'src/app/services/price.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-block',
 | 
					  selector: 'app-block',
 | 
				
			||||||
@ -82,6 +83,9 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  childChangeSubscription: Subscription;
 | 
					  childChangeSubscription: Subscription;
 | 
				
			||||||
  auditPrefSubscription: Subscription;
 | 
					  auditPrefSubscription: Subscription;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  priceSubscription: Subscription;
 | 
				
			||||||
 | 
					  blockConversion: Price;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
 | 
					  @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
 | 
				
			||||||
  @ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
 | 
					  @ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -94,7 +98,8 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    private seoService: SeoService,
 | 
					    private seoService: SeoService,
 | 
				
			||||||
    private websocketService: WebsocketService,
 | 
					    private websocketService: WebsocketService,
 | 
				
			||||||
    private relativeUrlPipe: RelativeUrlPipe,
 | 
					    private relativeUrlPipe: RelativeUrlPipe,
 | 
				
			||||||
    private apiService: ApiService
 | 
					    private apiService: ApiService,
 | 
				
			||||||
 | 
					    private priceService: PriceService,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.webGlEnabled = detectWebGL();
 | 
					    this.webGlEnabled = detectWebGL();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -432,6 +437,19 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.priceSubscription) {
 | 
				
			||||||
 | 
					      this.priceSubscription.unsubscribe();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.priceSubscription = block$.pipe(
 | 
				
			||||||
 | 
					      switchMap((block) => {
 | 
				
			||||||
 | 
					        return this.priceService.getPrices().pipe(
 | 
				
			||||||
 | 
					          tap(() => {
 | 
				
			||||||
 | 
					            this.blockConversion = this.priceService.getPriceForTimestamp(block.timestamp);
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).subscribe();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngAfterViewInit(): void {
 | 
					  ngAfterViewInit(): void {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,20 +14,26 @@
 | 
				
			|||||||
            }}</a>
 | 
					            }}</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="block-body">
 | 
					        <div class="block-body">
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
 | 
					          <div *ngIf="block?.extras; else emptyfees" [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
 | 
				
			||||||
            ~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
 | 
					            ~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
 | 
				
			||||||
              i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
					              i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					          <ng-template #emptyfees>
 | 
				
			||||||
 | 
					            <div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
 | 
				
			||||||
 | 
					               
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </ng-template>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
 | 
				
			||||||
            *ngIf="block?.extras?.feeRange">
 | 
					            *ngIf="block?.extras?.feeRange; else emptyfeespan">
 | 
				
			||||||
            {{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{
 | 
					            {{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{
 | 
				
			||||||
            block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container
 | 
					            block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container
 | 
				
			||||||
              i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
					              i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
 | 
					          <ng-template #emptyfeespan>
 | 
				
			||||||
            *ngIf="!block?.extras?.feeRange">
 | 
					            <div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fee-span">
 | 
				
			||||||
               
 | 
					               
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					          </ng-template>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
 | 
				
			||||||
            class="block-size">
 | 
					            class="block-size">
 | 
				
			||||||
            <app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
					            <app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
				
			||||||
 | 
				
			|||||||
@ -31,7 +31,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
 | 
				
			||||||
          <input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" [attr.data-cy]="'3m'" formControlName="dateSpan"> 3M
 | 
					          <input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" [attr.data-cy]="'3m'" formControlName="dateSpan"> 3M
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
 | 
				
			|||||||
@ -132,3 +132,8 @@
 | 
				
			|||||||
  max-width: 80px;
 | 
					  max-width: 80px;
 | 
				
			||||||
  margin: 15px auto 3px;
 | 
					  margin: 15px auto 3px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
 | 
				
			||||||
          <input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]" [attr.data-cy]="'6m'" formControlName="dateSpan"> 6M
 | 
					          <input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]" [attr.data-cy]="'6m'" formControlName="dateSpan"> 6M
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
 | 
				
			|||||||
@ -82,3 +82,8 @@
 | 
				
			|||||||
.loadingGraphs.widget {
 | 
					.loadingGraphs.widget {
 | 
				
			||||||
  top: 75%;
 | 
					  top: 75%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -5,7 +5,7 @@ import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/op
 | 
				
			|||||||
import { ApiService } from '../../services/api.service';
 | 
					import { ApiService } from '../../services/api.service';
 | 
				
			||||||
import { SeoService } from '../../services/seo.service';
 | 
					import { SeoService } from '../../services/seo.service';
 | 
				
			||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
					import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 | 
				
			||||||
import { poolsColor } from '../../app.constants';
 | 
					import { chartColors, poolsColor } from '../../app.constants';
 | 
				
			||||||
import { StorageService } from '../../services/storage.service';
 | 
					import { StorageService } from '../../services/storage.service';
 | 
				
			||||||
import { MiningService } from '../../services/mining.service';
 | 
					import { MiningService } from '../../services/mining.service';
 | 
				
			||||||
import { download } from '../../shared/graphs.utils';
 | 
					import { download } from '../../shared/graphs.utils';
 | 
				
			||||||
@ -173,6 +173,7 @@ export class HashrateChartPoolsComponent implements OnInit {
 | 
				
			|||||||
    this.chartOptions = {
 | 
					    this.chartOptions = {
 | 
				
			||||||
      title: title,
 | 
					      title: title,
 | 
				
			||||||
      animation: false,
 | 
					      animation: false,
 | 
				
			||||||
 | 
					      color: chartColors,
 | 
				
			||||||
      grid: {
 | 
					      grid: {
 | 
				
			||||||
        right: this.right,
 | 
					        right: this.right,
 | 
				
			||||||
        left: this.left,
 | 
					        left: this.left,
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,7 @@
 | 
				
			|||||||
    </div>  
 | 
					    </div>  
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup"
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup"
 | 
				
			||||||
      *ngIf="!widget && (miningStatsObservable$ | async) as stats">
 | 
					      *ngIf="!widget && (miningStatsObservable$ | async) as stats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" name="radioBasic">
 | 
					      <div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
 | 
				
			||||||
        <label class="btn btn-primary btn-sm" *ngIf="stats.totalBlockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
					        <label class="btn btn-primary btn-sm" *ngIf="stats.totalBlockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
 | 
				
			||||||
          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/pools' | relativeUrl]" [attr.data-cy]="'24h'" formControlName="dateSpan"> 24h
 | 
					          <input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/pools' | relativeUrl]" [attr.data-cy]="'24h'" formControlName="dateSpan"> 24h
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
@ -92,6 +92,8 @@
 | 
				
			|||||||
          <th class="" i18n="mining.pool-name">Pool</th>
 | 
					          <th class="" i18n="mining.pool-name">Pool</th>
 | 
				
			||||||
          <th class="" *ngIf="this.miningWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
 | 
					          <th class="" *ngIf="this.miningWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
 | 
				
			||||||
          <th class="" i18n="master-page.blocks">Blocks</th>
 | 
					          <th class="" i18n="master-page.blocks">Blocks</th>
 | 
				
			||||||
 | 
					          <th *ngIf="auditAvailable" class="health text-right widget" i18n="latest-blocks.avg_health"
 | 
				
			||||||
 | 
					            i18n-ngbTooltip="latest-blocks.avg_health" ngbTooltip="Avg Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Avg Health</th>
 | 
				
			||||||
          <th class="d-none d-md-block" i18n="mining.empty-blocks">Empty blocks</th>
 | 
					          <th class="d-none d-md-block" i18n="mining.empty-blocks">Empty blocks</th>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
      </thead>
 | 
					      </thead>
 | 
				
			||||||
@ -102,9 +104,23 @@
 | 
				
			|||||||
            <img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
 | 
					            <img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
          <td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
 | 
					          <td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
 | 
				
			||||||
          <td class="" *ngIf="this.miningWindowPreference === '24h' && !isLoading">{{ pool.lastEstimatedHashrate }} {{
 | 
					          <td class="" *ngIf="this.miningWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{
 | 
				
			||||||
            miningStats.miningUnits.hashrateUnit }}</td>
 | 
					            miningStats.miningUnits.hashrateUnit }}</td>
 | 
				
			||||||
          <td class="">{{ pool['blockText'] }}</td>
 | 
					          <td class="d-flex justify-content-center">
 | 
				
			||||||
 | 
					            {{ pool.blockCount }}<span class="d-none d-md-block"> ({{ pool.share }}%)</span>
 | 
				
			||||||
 | 
					          </td>
 | 
				
			||||||
 | 
					          <td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
 | 
				
			||||||
 | 
					            <a
 | 
				
			||||||
 | 
					              class="health-badge badge"
 | 
				
			||||||
 | 
					              [class.badge-success]="pool.avgMatchRate >= 99"
 | 
				
			||||||
 | 
					              [class.badge-warning]="pool.avgMatchRate >= 75 && pool.avgMatchRate < 99"
 | 
				
			||||||
 | 
					              [class.badge-danger]="pool.avgMatchRate < 75"
 | 
				
			||||||
 | 
					              *ngIf="pool.avgMatchRate != null; else nullHealth"
 | 
				
			||||||
 | 
					            >{{ pool.avgMatchRate }}%</a>
 | 
				
			||||||
 | 
					            <ng-template #nullHealth>
 | 
				
			||||||
 | 
					              <span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
 | 
				
			||||||
 | 
					            </ng-template>
 | 
				
			||||||
 | 
					          </td>
 | 
				
			||||||
          <td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
 | 
					          <td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
        <tr style="border-top: 1px solid #555">
 | 
					        <tr style="border-top: 1px solid #555">
 | 
				
			||||||
 | 
				
			|||||||
@ -139,3 +139,8 @@
 | 
				
			|||||||
  max-width: 80px;
 | 
					  max-width: 80px;
 | 
				
			||||||
  margin: 15px auto 3px;
 | 
					  margin: 15px auto 3px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled {
 | 
				
			||||||
 | 
					  pointer-events: none;
 | 
				
			||||||
 | 
					  opacity: 0.5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -26,6 +26,8 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
  miningWindowPreference: string;
 | 
					  miningWindowPreference: string;
 | 
				
			||||||
  radioGroupForm: UntypedFormGroup;
 | 
					  radioGroupForm: UntypedFormGroup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auditAvailable = false;
 | 
				
			||||||
 | 
					  indexingAvailable = false;
 | 
				
			||||||
  isLoading = true;
 | 
					  isLoading = true;
 | 
				
			||||||
  chartOptions: EChartsOption = {};
 | 
					  chartOptions: EChartsOption = {};
 | 
				
			||||||
  chartInitOptions = {
 | 
					  chartInitOptions = {
 | 
				
			||||||
@ -60,6 +62,10 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
 | 
					    this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
 | 
				
			||||||
    this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
 | 
					    this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' &&
 | 
				
			||||||
 | 
					      this.stateService.env.MINING_DASHBOARD === true);
 | 
				
			||||||
 | 
					    this.auditAvailable = this.indexingAvailable && this.stateService.env.AUDIT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.route
 | 
					    this.route
 | 
				
			||||||
      .fragment
 | 
					      .fragment
 | 
				
			||||||
      .subscribe((fragment) => {
 | 
					      .subscribe((fragment) => {
 | 
				
			||||||
@ -73,6 +79,7 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
        .pipe(
 | 
					        .pipe(
 | 
				
			||||||
          startWith(this.radioGroupForm.controls.dateSpan.value), // (trigger when the page loads)
 | 
					          startWith(this.radioGroupForm.controls.dateSpan.value), // (trigger when the page loads)
 | 
				
			||||||
          tap((value) => {
 | 
					          tap((value) => {
 | 
				
			||||||
 | 
					            this.isLoading = true;
 | 
				
			||||||
            this.timespan = value;
 | 
					            this.timespan = value;
 | 
				
			||||||
            if (!this.widget) {
 | 
					            if (!this.widget) {
 | 
				
			||||||
              this.storageService.setValue('miningWindowPreference', value);
 | 
					              this.storageService.setValue('miningWindowPreference', value);
 | 
				
			||||||
@ -92,7 +99,6 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
      )
 | 
					      )
 | 
				
			||||||
      .pipe(
 | 
					      .pipe(
 | 
				
			||||||
        map(data => {
 | 
					        map(data => {
 | 
				
			||||||
          data.pools = data.pools.map((pool: SinglePoolStats) => this.formatPoolUI(pool));
 | 
					 | 
				
			||||||
          data['minersLuck'] = (100 * (data.blockCount / 1008)).toFixed(2); // luck 1w
 | 
					          data['minersLuck'] = (100 * (data.blockCount / 1008)).toFixed(2); // luck 1w
 | 
				
			||||||
          return data;
 | 
					          return data;
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
@ -104,11 +110,6 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  formatPoolUI(pool: SinglePoolStats) {
 | 
					 | 
				
			||||||
    pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`;
 | 
					 | 
				
			||||||
    return pool;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  generatePoolsChartSerieData(miningStats) {
 | 
					  generatePoolsChartSerieData(miningStats) {
 | 
				
			||||||
    let poolShareThreshold = 0.5;
 | 
					    let poolShareThreshold = 0.5;
 | 
				
			||||||
    if (isMobile()) {
 | 
					    if (isMobile()) {
 | 
				
			||||||
 | 
				
			|||||||
@ -61,12 +61,6 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  </td>
 | 
					                  </td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
                <tr *ngIf="latestBlock && tx.status.block_height <= latestBlock.height - 8">
 | 
					 | 
				
			||||||
                  <td class="td-width" i18n="transaction.included-in-block|Transaction included in block">Included in block</td>
 | 
					 | 
				
			||||||
                  <td>
 | 
					 | 
				
			||||||
                    <a [routerLink]="['/block/' | relativeUrl, tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
 | 
					 | 
				
			||||||
                  </td>
 | 
					 | 
				
			||||||
                </tr>
 | 
					 | 
				
			||||||
                <ng-template [ngIf]="transactionTime > 0">
 | 
					                <ng-template [ngIf]="transactionTime > 0">
 | 
				
			||||||
                  <tr>
 | 
					                  <tr>
 | 
				
			||||||
                    <td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
 | 
					                    <td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
 | 
				
			||||||
@ -475,7 +469,7 @@
 | 
				
			|||||||
    <tbody>
 | 
					    <tbody>
 | 
				
			||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
 | 
					        <td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
 | 
				
			||||||
        <td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></td>
 | 
					        <td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="tx.fee"></app-fiat></span></td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
 | 
					        <td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
 | 
				
			||||||
 | 
				
			|||||||
@ -8,10 +8,11 @@ import {
 | 
				
			|||||||
  retryWhen,
 | 
					  retryWhen,
 | 
				
			||||||
  delay,
 | 
					  delay,
 | 
				
			||||||
  map,
 | 
					  map,
 | 
				
			||||||
  mergeMap
 | 
					  mergeMap,
 | 
				
			||||||
 | 
					  tap
 | 
				
			||||||
} from 'rxjs/operators';
 | 
					} from 'rxjs/operators';
 | 
				
			||||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
					import { Transaction } from '../../interfaces/electrs.interface';
 | 
				
			||||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
 | 
					import { of, merge, Subscription, Observable, Subject, timer, from, throwError } from 'rxjs';
 | 
				
			||||||
import { StateService } from '../../services/state.service';
 | 
					import { StateService } from '../../services/state.service';
 | 
				
			||||||
import { CacheService } from '../../services/cache.service';
 | 
					import { CacheService } from '../../services/cache.service';
 | 
				
			||||||
import { WebsocketService } from '../../services/websocket.service';
 | 
					import { WebsocketService } from '../../services/websocket.service';
 | 
				
			||||||
@ -21,6 +22,7 @@ import { SeoService } from '../../services/seo.service';
 | 
				
			|||||||
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
 | 
					import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
 | 
				
			||||||
import { LiquidUnblinding } from './liquid-ublinding';
 | 
					import { LiquidUnblinding } from './liquid-ublinding';
 | 
				
			||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
					import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
				
			||||||
 | 
					import { Price, PriceService } from 'src/app/services/price.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-transaction',
 | 
					  selector: 'app-transaction',
 | 
				
			||||||
@ -69,7 +71,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
  hideFlow: boolean = this.stateService.hideFlow.value;
 | 
					  hideFlow: boolean = this.stateService.hideFlow.value;
 | 
				
			||||||
  overrideFlowPreference: boolean = null;
 | 
					  overrideFlowPreference: boolean = null;
 | 
				
			||||||
  flowEnabled: boolean;
 | 
					  flowEnabled: boolean;
 | 
				
			||||||
 | 
					  blockConversion: Price;
 | 
				
			||||||
  tooltipPosition: { x: number, y: number };
 | 
					  tooltipPosition: { x: number, y: number };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ViewChild('graphContainer')
 | 
					  @ViewChild('graphContainer')
 | 
				
			||||||
@ -85,7 +87,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
    private websocketService: WebsocketService,
 | 
					    private websocketService: WebsocketService,
 | 
				
			||||||
    private audioService: AudioService,
 | 
					    private audioService: AudioService,
 | 
				
			||||||
    private apiService: ApiService,
 | 
					    private apiService: ApiService,
 | 
				
			||||||
    private seoService: SeoService
 | 
					    private seoService: SeoService,
 | 
				
			||||||
 | 
					    private priceService: PriceService,
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
@ -323,6 +326,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            this.fetchRbfHistory$.next(this.tx.txid);
 | 
					            this.fetchRbfHistory$.next(this.tx.txid);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          this.priceService.getPrices().pipe(
 | 
				
			||||||
 | 
					            tap(() => {
 | 
				
			||||||
 | 
					              this.blockConversion = this.priceService.getPriceForTimestamp(tx.status.block_time);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          ).subscribe();
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
          setTimeout(() => { this.applyFragment(); }, 0);
 | 
					          setTimeout(() => { this.applyFragment(); }, 0);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        (error) => {
 | 
					        (error) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -88,7 +88,7 @@
 | 
				
			|||||||
                  </ng-template>
 | 
					                  </ng-template>
 | 
				
			||||||
                  <ng-template #defaultOutput>
 | 
					                  <ng-template #defaultOutput>
 | 
				
			||||||
                    <span *ngIf="vin.lazy" class="skeleton-loader"></span>
 | 
					                    <span *ngIf="vin.lazy" class="skeleton-loader"></span>
 | 
				
			||||||
                    <app-amount *ngIf="vin.prevout" [satoshis]="vin.prevout.value"></app-amount>
 | 
					                    <app-amount [blockConversion]="tx.price" *ngIf="vin.prevout" [satoshis]="vin.prevout.value"></app-amount>
 | 
				
			||||||
                  </ng-template>
 | 
					                  </ng-template>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
@ -216,7 +216,7 @@
 | 
				
			|||||||
                    </ng-template>
 | 
					                    </ng-template>
 | 
				
			||||||
                  </ng-template>
 | 
					                  </ng-template>
 | 
				
			||||||
                  <ng-template #defaultOutput>
 | 
					                  <ng-template #defaultOutput>
 | 
				
			||||||
                    <app-amount [satoshis]="vout.value"></app-amount>
 | 
					                    <app-amount [blockConversion]="tx.price" [satoshis]="vout.value"></app-amount>
 | 
				
			||||||
                  </ng-template>
 | 
					                  </ng-template>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
                <td class="arrow-td">
 | 
					                <td class="arrow-td">
 | 
				
			||||||
@ -283,7 +283,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <div class="summary">
 | 
					    <div class="summary">
 | 
				
			||||||
      <div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase && tx.fee !== -1">
 | 
					      <div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase && tx.fee !== -1">
 | 
				
			||||||
        {{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
 | 
					        {{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span
 | 
				
			||||||
 | 
					          class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol"
 | 
				
			||||||
 | 
					            i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee"></app-fiat></span></span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="float-left mt-2-5 grey-info-text" *ngIf="tx.fee === -1" i18n="transactions-list.load-to-reveal-fee-info">Show more inputs to reveal fee data</div>
 | 
					      <div class="float-left mt-2-5 grey-info-text" *ngIf="tx.fee === -1" i18n="transactions-list.load-to-reveal-fee-info">Show more inputs to reveal fee data</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -301,12 +303,12 @@
 | 
				
			|||||||
        <button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
 | 
					        <button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
 | 
				
			||||||
          <ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
 | 
					          <ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
 | 
				
			||||||
          <ng-template #defaultAmount>
 | 
					          <ng-template #defaultAmount>
 | 
				
			||||||
            <app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
 | 
					            <app-amount [blockConversion]="tx.price" [satoshis]="getTotalTxOutput(tx)"></app-amount>
 | 
				
			||||||
          </ng-template>
 | 
					          </ng-template>
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
        <ng-template #viewingAddress>
 | 
					        <ng-template #viewingAddress>
 | 
				
			||||||
          <button type="button" class="btn btn-sm mt-2 ml-2" (click)="switchCurrency()" [ngClass]="{'btn-success': tx['addressValue'] >= 0, 'btn-danger': tx['addressValue'] < 0}">
 | 
					          <button type="button" class="btn btn-sm mt-2 ml-2" (click)="switchCurrency()" [ngClass]="{'btn-success': tx['addressValue'] >= 0, 'btn-danger': tx['addressValue'] < 0}">
 | 
				
			||||||
            <app-amount [satoshis]="tx['addressValue']" [addPlus]="true"></app-amount>
 | 
					            <app-amount [blockConversion]="tx.price" [satoshis]="tx['addressValue']" [addPlus]="true"></app-amount>
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
        </ng-template>
 | 
					        </ng-template>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import { AssetsService } from '../../services/assets.service';
 | 
				
			|||||||
import { filter, map, tap, switchMap } from 'rxjs/operators';
 | 
					import { filter, map, tap, switchMap } from 'rxjs/operators';
 | 
				
			||||||
import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
					import { BlockExtended } from '../../interfaces/node-api.interface';
 | 
				
			||||||
import { ApiService } from '../../services/api.service';
 | 
					import { ApiService } from '../../services/api.service';
 | 
				
			||||||
 | 
					import { PriceService } from 'src/app/services/price.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-transactions-list',
 | 
					  selector: 'app-transactions-list',
 | 
				
			||||||
@ -50,6 +51,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
				
			|||||||
    private apiService: ApiService,
 | 
					    private apiService: ApiService,
 | 
				
			||||||
    private assetsService: AssetsService,
 | 
					    private assetsService: AssetsService,
 | 
				
			||||||
    private ref: ChangeDetectorRef,
 | 
					    private ref: ChangeDetectorRef,
 | 
				
			||||||
 | 
					    private priceService: PriceService,
 | 
				
			||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit(): void {
 | 
					  ngOnInit(): void {
 | 
				
			||||||
@ -147,6 +149,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          tx['addressValue'] = addressIn - addressOut;
 | 
					          tx['addressValue'] = addressIn - addressOut;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.priceService.getPrices().pipe(
 | 
				
			||||||
 | 
					          tap(() => {
 | 
				
			||||||
 | 
					            tx['price'] = this.priceService.getPriceForTimestamp(tx.status.block_time);
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        ).subscribe();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
 | 
					      const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
 | 
				
			||||||
      if (txIds.length) {
 | 
					      if (txIds.length) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1,14 @@
 | 
				
			|||||||
<span class="green-color" *ngIf="(conversions$ | async) as conversions">{{ (conversions ? conversions[currency] : 0) * value / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
 | 
					<span class="green-color" *ngIf="blockConversion; else noblockconversion">
 | 
				
			||||||
 | 
					  {{
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					      (blockConversion.price[currency] > 0 ? blockConversion.price[currency] : null) ??
 | 
				
			||||||
 | 
					      (blockConversion.price['USD'] * blockConversion.exchangeRates['USD' + currency]) ?? 0
 | 
				
			||||||
 | 
					    ) * value / 100000000 | fiatCurrency : digitsInfo : currency
 | 
				
			||||||
 | 
					  }}
 | 
				
			||||||
 | 
					</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template #noblockconversion>
 | 
				
			||||||
 | 
					  <span class="green-color" *ngIf="(conversions$ | async) as conversions">
 | 
				
			||||||
 | 
					    {{ (conversions[currency] ?? conversions['USD'] ?? 0) * value / 100000000 | fiatCurrency : digitsInfo : currency }}
 | 
				
			||||||
 | 
					  </span>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
 | 
					import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
 | 
				
			||||||
import { Observable, Subscription } from 'rxjs';
 | 
					import { Observable, Subscription } from 'rxjs';
 | 
				
			||||||
 | 
					import { Price } from '../services/price.service';
 | 
				
			||||||
import { StateService } from '../services/state.service';
 | 
					import { StateService } from '../services/state.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
@ -15,6 +16,7 @@ export class FiatComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @Input() value: number;
 | 
					  @Input() value: number;
 | 
				
			||||||
  @Input() digitsInfo = '1.2-2';
 | 
					  @Input() digitsInfo = '1.2-2';
 | 
				
			||||||
 | 
					  @Input() blockConversion: Price;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private stateService: StateService,
 | 
					    private stateService: StateService,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { Price } from '../services/price.service';
 | 
				
			||||||
import { IChannel } from './node-api.interface';
 | 
					import { IChannel } from './node-api.interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Transaction {
 | 
					export interface Transaction {
 | 
				
			||||||
@ -23,6 +24,7 @@ export interface Transaction {
 | 
				
			|||||||
  _deduced?: boolean;
 | 
					  _deduced?: boolean;
 | 
				
			||||||
  _outspends?: Outspend[];
 | 
					  _outspends?: Outspend[];
 | 
				
			||||||
  _channels?: TransactionChannels;
 | 
					  _channels?: TransactionChannels;
 | 
				
			||||||
 | 
					  price?: Price;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface TransactionChannels {
 | 
					export interface TransactionChannels {
 | 
				
			||||||
 | 
				
			|||||||
@ -73,6 +73,7 @@ export interface SinglePoolStats {
 | 
				
			|||||||
  emptyBlockRatio: string;
 | 
					  emptyBlockRatio: string;
 | 
				
			||||||
  logo: string;
 | 
					  logo: string;
 | 
				
			||||||
  slug: string;
 | 
					  slug: string;
 | 
				
			||||||
 | 
					  avgMatchRate: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export interface PoolsStats {
 | 
					export interface PoolsStats {
 | 
				
			||||||
  blockCount: number;
 | 
					  blockCount: number;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core';
 | 
					import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core';
 | 
				
			||||||
import { SeoService } from '../../services/seo.service';
 | 
					import { SeoService } from '../../services/seo.service';
 | 
				
			||||||
import { ApiService } from '../../services/api.service';
 | 
					import { ApiService } from '../../services/api.service';
 | 
				
			||||||
import { Observable, switchMap, tap, zip } from 'rxjs';
 | 
					import { delay, Observable, switchMap, tap, zip } from 'rxjs';
 | 
				
			||||||
import { AssetsService } from '../../services/assets.service';
 | 
					import { AssetsService } from '../../services/assets.service';
 | 
				
			||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
					import { ActivatedRoute, ParamMap, Router } from '@angular/router';
 | 
				
			||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
					import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
 | 
				
			||||||
@ -75,6 +75,7 @@ export class NodesChannelsMap implements OnInit {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    this.channelsObservable = this.activatedRoute.paramMap
 | 
					    this.channelsObservable = this.activatedRoute.paramMap
 | 
				
			||||||
     .pipe(
 | 
					     .pipe(
 | 
				
			||||||
 | 
					       delay(100),
 | 
				
			||||||
       switchMap((params: ParamMap) => {
 | 
					       switchMap((params: ParamMap) => {
 | 
				
			||||||
        this.isLoading = true;
 | 
					        this.isLoading = true;
 | 
				
			||||||
        if (this.style === 'channelpage' && this.channel.length === 0 || !this.hasLocation) {
 | 
					        if (this.style === 'channelpage' && this.channel.length === 0 || !this.hasLocation) {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { Observable } from 'rxjs';
 | 
				
			|||||||
import { StateService } from './state.service';
 | 
					import { StateService } from './state.service';
 | 
				
			||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
 | 
					import { WebsocketResponse } from '../interfaces/websocket.interface';
 | 
				
			||||||
import { Outspend, Transaction } from '../interfaces/electrs.interface';
 | 
					import { Outspend, Transaction } from '../interfaces/electrs.interface';
 | 
				
			||||||
 | 
					import { Conversion } from './price.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable({
 | 
					@Injectable({
 | 
				
			||||||
  providedIn: 'root'
 | 
					  providedIn: 'root'
 | 
				
			||||||
@ -303,4 +304,8 @@ export class ApiService {
 | 
				
			|||||||
        (style     !== undefined ? `?style=${style}` : '')
 | 
					        (style     !== undefined ? `?style=${style}` : '')
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getHistoricalPrice$(): Observable<Conversion> {
 | 
				
			||||||
 | 
					    return this.httpClient.get<Conversion>( this.apiBaseUrl + this.apiBasePath + '/api/v1/historical-price');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										121
									
								
								frontend/src/app/services/price.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								frontend/src/app/services/price.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
 | 
					import { map, Observable, of, shareReplay } from 'rxjs';
 | 
				
			||||||
 | 
					import { ApiService } from './api.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// nodejs backend interfaces
 | 
				
			||||||
 | 
					export interface ApiPrice {
 | 
				
			||||||
 | 
					  time?: number,
 | 
				
			||||||
 | 
					  USD: number,
 | 
				
			||||||
 | 
					  EUR: number,
 | 
				
			||||||
 | 
					  GBP: number,
 | 
				
			||||||
 | 
					  CAD: number,
 | 
				
			||||||
 | 
					  CHF: number,
 | 
				
			||||||
 | 
					  AUD: number,
 | 
				
			||||||
 | 
					  JPY: number,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export interface ExchangeRates {
 | 
				
			||||||
 | 
					  USDEUR: number,
 | 
				
			||||||
 | 
					  USDGBP: number,
 | 
				
			||||||
 | 
					  USDCAD: number,
 | 
				
			||||||
 | 
					  USDCHF: number,
 | 
				
			||||||
 | 
					  USDAUD: number,
 | 
				
			||||||
 | 
					  USDJPY: number,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export interface Conversion {
 | 
				
			||||||
 | 
					  prices: ApiPrice[],
 | 
				
			||||||
 | 
					  exchangeRates: ExchangeRates;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// frontend interface
 | 
				
			||||||
 | 
					export interface Price {
 | 
				
			||||||
 | 
					  price: ApiPrice,
 | 
				
			||||||
 | 
					  exchangeRates: ExchangeRates,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export interface ConversionDict {
 | 
				
			||||||
 | 
					  prices: { [timestamp: number]: ApiPrice }
 | 
				
			||||||
 | 
					  exchangeRates: ExchangeRates;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable({
 | 
				
			||||||
 | 
					  providedIn: 'root'
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class PriceService {
 | 
				
			||||||
 | 
					  historicalPrice: ConversionDict = {
 | 
				
			||||||
 | 
					    prices: null,
 | 
				
			||||||
 | 
					    exchangeRates: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private apiService: ApiService
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getEmptyPrice(): Price {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      price: {
 | 
				
			||||||
 | 
					        USD: 0, EUR: 0, GBP: 0, CAD: 0, CHF: 0, AUD: 0, JPY: 0,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      exchangeRates: {
 | 
				
			||||||
 | 
					        USDEUR: 0, USDGBP: 0, USDCAD: 0, USDCHF: 0, USDAUD: 0, USDJPY: 0,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Fetch prices from the nodejs backend only once
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getPrices(): Observable<void> {
 | 
				
			||||||
 | 
					    if (this.historicalPrice.prices) {
 | 
				
			||||||
 | 
					      return of(null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.apiService.getHistoricalPrice$().pipe(
 | 
				
			||||||
 | 
					      map((conversion: Conversion) => {
 | 
				
			||||||
 | 
					        if (!this.historicalPrice.prices) {
 | 
				
			||||||
 | 
					          this.historicalPrice.prices = Object();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const price of conversion.prices) {
 | 
				
			||||||
 | 
					          this.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(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Note: The first block with a price we have is block 68952 (using MtGox price history)
 | 
				
			||||||
 | 
					   * 
 | 
				
			||||||
 | 
					   * @param blockTimestamp 
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getPriceForTimestamp(blockTimestamp: number): Price | null {
 | 
				
			||||||
 | 
					    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();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,7 +106,7 @@ build_backend()
 | 
				
			|||||||
        -e "s!__MEMPOOL_SIGNET_LIGHTNING_PASS__!${MEMPOOL_SIGNET_LIGHTNING_PASS}!" \
 | 
					        -e "s!__MEMPOOL_SIGNET_LIGHTNING_PASS__!${MEMPOOL_SIGNET_LIGHTNING_PASS}!" \
 | 
				
			||||||
        -e "s!__MEMPOOL_LIQUID_USER__!${MEMPOOL_LIQUID_USER}!" \
 | 
					        -e "s!__MEMPOOL_LIQUID_USER__!${MEMPOOL_LIQUID_USER}!" \
 | 
				
			||||||
        -e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
 | 
					        -e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
 | 
				
			||||||
        -e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${LIQUIDTESTNET_USER}!" \
 | 
					        -e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${MEMPOOL_LIQUIDTESTNET_USER}!" \
 | 
				
			||||||
        -e "s!__MEMPOOL_LIQUIDTESTNET_PASS__!${MEMPOOL_LIQUIDTESTNET_PASS}!" \
 | 
					        -e "s!__MEMPOOL_LIQUIDTESTNET_PASS__!${MEMPOOL_LIQUIDTESTNET_PASS}!" \
 | 
				
			||||||
        -e "s!__MEMPOOL_BISQ_USER__!${MEMPOOL_BISQ_USER}!" \
 | 
					        -e "s!__MEMPOOL_BISQ_USER__!${MEMPOOL_BISQ_USER}!" \
 | 
				
			||||||
        -e "s!__MEMPOOL_BISQ_PASS__!${MEMPOOL_BISQ_PASS}!" \
 | 
					        -e "s!__MEMPOOL_BISQ_PASS__!${MEMPOOL_BISQ_PASS}!" \
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,6 @@
 | 
				
			|||||||
    "PORT": 3306,
 | 
					    "PORT": 3306,
 | 
				
			||||||
    "USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__",
 | 
					    "USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__",
 | 
				
			||||||
    "PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__",
 | 
					    "PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__",
 | 
				
			||||||
    "PASSWORD": "mempool_mainnet_lightning"
 | 
					    "DATABASE": "mempool_mainnet_lightning"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -120,14 +120,16 @@ do for url in / \
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	do
 | 
						do
 | 
				
			||||||
		warm "https://${hostname}${url}"
 | 
							warm "https://${hostname}${url}"
 | 
				
			||||||
 | 
							sleep 0.25 # wait 250ms between queries to not DoS mariadb
 | 
				
			||||||
	done
 | 
						done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for slug in $slugs
 | 
						for slug in $slugs
 | 
				
			||||||
	do
 | 
						do
 | 
				
			||||||
		warm "https://${hostname}/api/v1/mining/pool/${slug}"
 | 
							warm "https://${hostname}/api/v1/mining/pool/${slug}"
 | 
				
			||||||
 | 
							sleep 0.25 # wait 250ms between queries to not DoS mariadb
 | 
				
			||||||
		warm "https://${hostname}/api/v1/mining/pool/${slug}/hashrate"
 | 
							warm "https://${hostname}/api/v1/mining/pool/${slug}/hashrate"
 | 
				
			||||||
 | 
							sleep 0.25 # wait 250ms between queries to not DoS mariadb
 | 
				
			||||||
		warm "https://${hostname}/api/v1/mining/pool/${slug}/blocks"
 | 
							warm "https://${hostname}/api/v1/mining/pool/${slug}/blocks"
 | 
				
			||||||
 | 
							sleep 0.25 # wait 250ms between queries to not DoS mariadb
 | 
				
			||||||
	done
 | 
						done
 | 
				
			||||||
 | 
					 | 
				
			||||||
	sleep 10
 | 
					 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user