Merge branch 'master' into simon/display-unblinded-tx-sum
This commit is contained in:
		
						commit
						e617e14901
					
				@ -116,6 +116,9 @@ class Blocks {
 | 
				
			|||||||
      Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
 | 
					      Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
 | 
				
			||||||
    blockExtended.extras.feeRange = transactionsTmp.length > 0 ?
 | 
					    blockExtended.extras.feeRange = transactionsTmp.length > 0 ?
 | 
				
			||||||
      Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
 | 
					      Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
 | 
				
			||||||
 | 
					    blockExtended.extras.totalFees = transactionsTmp.reduce((acc, tx) => {
 | 
				
			||||||
 | 
					      return acc + tx.fee;
 | 
				
			||||||
 | 
					    }, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (Common.indexingEnabled()) {
 | 
					    if (Common.indexingEnabled()) {
 | 
				
			||||||
      let pool: PoolTag;
 | 
					      let pool: PoolTag;
 | 
				
			||||||
 | 
				
			|||||||
@ -42,9 +42,7 @@ class Mining {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    poolsStatistics['pools'] = poolsStats;
 | 
					    poolsStatistics['pools'] = poolsStats;
 | 
				
			||||||
 | 
					    poolsStatistics['oldestIndexedBlockTimestamp'] = await BlocksRepository.$oldestBlockTimestamp();
 | 
				
			||||||
    const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
 | 
					 | 
				
			||||||
    poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const blockCount: number = await BlocksRepository.$blockCount(null, interval);
 | 
					    const blockCount: number = await BlocksRepository.$blockCount(null, interval);
 | 
				
			||||||
    poolsStatistics['blockCount'] = blockCount;
 | 
					    poolsStatistics['blockCount'] = blockCount;
 | 
				
			||||||
@ -79,26 +77,14 @@ class Mining {
 | 
				
			|||||||
   * Return the historical difficulty adjustments and oldest indexed block timestamp
 | 
					   * Return the historical difficulty adjustments and oldest indexed block timestamp
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $getHistoricalDifficulty(interval: string | null): Promise<object> {
 | 
					  public async $getHistoricalDifficulty(interval: string | null): Promise<object> {
 | 
				
			||||||
    const difficultyAdjustments = await BlocksRepository.$getBlocksDifficulty(interval);
 | 
					    return await BlocksRepository.$getBlocksDifficulty(interval);
 | 
				
			||||||
    const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      adjustments: difficultyAdjustments,
 | 
					 | 
				
			||||||
      oldestIndexedBlockTimestamp: oldestBlock.getTime(),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Return the historical hashrates and oldest indexed block timestamp
 | 
					   * Return the historical hashrates and oldest indexed block timestamp
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $getHistoricalHashrates(interval: string | null): Promise<object> {
 | 
					  public async $getHistoricalHashrates(interval: string | null): Promise<object> {
 | 
				
			||||||
    const hashrates = await HashratesRepository.$get(interval);
 | 
					    return await HashratesRepository.$get(interval);
 | 
				
			||||||
    const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      hashrates: hashrates,
 | 
					 | 
				
			||||||
      oldestIndexedBlockTimestamp: oldestBlock.getTime(),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -175,7 +161,18 @@ class Mining {
 | 
				
			|||||||
      ++totalIndexed;
 | 
					      ++totalIndexed;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await HashratesRepository.$saveHashrates(hashrates);
 | 
					    // Add genesis block manually
 | 
				
			||||||
 | 
					    if (!indexedTimestamp.includes(genesisTimestamp)) {
 | 
				
			||||||
 | 
					      hashrates.push({
 | 
				
			||||||
 | 
					        hashrateTimestamp: genesisTimestamp,
 | 
				
			||||||
 | 
					        avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
 | 
				
			||||||
 | 
					        poolId: null
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (hashrates.length > 0) {
 | 
				
			||||||
 | 
					      await HashratesRepository.$saveHashrates(hashrates);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    await HashratesRepository.$setLatestRunTimestamp();
 | 
					    await HashratesRepository.$setLatestRunTimestamp();
 | 
				
			||||||
    this.hashrateIndexingStarted = false;
 | 
					    this.hashrateIndexingStarted = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -78,6 +78,7 @@ export interface TransactionStripped {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BlockExtension {
 | 
					export interface BlockExtension {
 | 
				
			||||||
 | 
					  totalFees?: number;
 | 
				
			||||||
  medianFee?: number;
 | 
					  medianFee?: number;
 | 
				
			||||||
  feeRange?: number[];
 | 
					  feeRange?: number[];
 | 
				
			||||||
  reward?: number;
 | 
					  reward?: number;
 | 
				
			||||||
 | 
				
			|||||||
@ -187,12 +187,11 @@ class BlocksRepository {
 | 
				
			|||||||
   * Get the oldest indexed block
 | 
					   * Get the oldest indexed block
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $oldestBlockTimestamp(): Promise<number> {
 | 
					  public async $oldestBlockTimestamp(): Promise<number> {
 | 
				
			||||||
    const query = `SELECT blockTimestamp
 | 
					    const query = `SELECT UNIX_TIMESTAMP(blockTimestamp) as blockTimestamp
 | 
				
			||||||
      FROM blocks
 | 
					      FROM blocks
 | 
				
			||||||
      ORDER BY height
 | 
					      ORDER BY height
 | 
				
			||||||
      LIMIT 1;`;
 | 
					      LIMIT 1;`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // logger.debug(query);
 | 
					    // logger.debug(query);
 | 
				
			||||||
    const connection = await DB.pool.getConnection();
 | 
					    const connection = await DB.pool.getConnection();
 | 
				
			||||||
    const [rows]: any[] = await connection.query(query);
 | 
					    const [rows]: any[] = await connection.query(query);
 | 
				
			||||||
@ -266,15 +265,37 @@ class BlocksRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.pool.getConnection();
 | 
					    const connection = await DB.pool.getConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let query = `SELECT MIN(UNIX_TIMESTAMP(blockTimestamp)) as timestamp, difficulty, height
 | 
					    // :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
 | 
				
			||||||
      FROM blocks`;
 | 
					    // Basically, using temporary user defined fields, we are able to extract all
 | 
				
			||||||
 | 
					    // difficulty adjustments from the blocks tables.
 | 
				
			||||||
 | 
					    // This allow use to avoid indexing it in another table.
 | 
				
			||||||
 | 
					    let query = `
 | 
				
			||||||
 | 
					      SELECT
 | 
				
			||||||
 | 
					      *
 | 
				
			||||||
 | 
					      FROM 
 | 
				
			||||||
 | 
					      (
 | 
				
			||||||
 | 
					        SELECT
 | 
				
			||||||
 | 
					        UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty, height,
 | 
				
			||||||
 | 
					        IF(@prevStatus = YT.difficulty, @rn := @rn + 1,
 | 
				
			||||||
 | 
					          IF(@prevStatus := YT.difficulty, @rn := 1, @rn := 1)
 | 
				
			||||||
 | 
					        ) AS rn
 | 
				
			||||||
 | 
					        FROM blocks YT
 | 
				
			||||||
 | 
					        CROSS JOIN 
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					          SELECT @prevStatus := -1, @rn := 1
 | 
				
			||||||
 | 
					        ) AS var
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (interval) {
 | 
					    if (interval) {
 | 
				
			||||||
      query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
					      query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    query += ` GROUP BY difficulty
 | 
					    query += `
 | 
				
			||||||
      ORDER BY blockTimestamp`;
 | 
					        ORDER BY YT.height
 | 
				
			||||||
 | 
					      ) AS t
 | 
				
			||||||
 | 
					      WHERE t.rn = 1
 | 
				
			||||||
 | 
					      ORDER BY t.height
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [rows]: any[] = await connection.query(query);
 | 
					    const [rows]: any[] = await connection.query(query);
 | 
				
			||||||
    connection.release();
 | 
					    connection.release();
 | 
				
			||||||
 | 
				
			|||||||
@ -588,11 +588,17 @@ class Routes {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  public async $getHistoricalHashrate(req: Request, res: Response) {
 | 
					  public async $getHistoricalHashrate(req: Request, res: Response) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const stats = await mining.$getHistoricalHashrates(req.params.interval ?? null);
 | 
					      const hashrates = await mining.$getHistoricalHashrates(req.params.interval ?? null);
 | 
				
			||||||
 | 
					      const difficulty = await mining.$getHistoricalDifficulty(req.params.interval ?? null);
 | 
				
			||||||
 | 
					      const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp();
 | 
				
			||||||
      res.header('Pragma', 'public');
 | 
					      res.header('Pragma', 'public');
 | 
				
			||||||
      res.header('Cache-control', 'public');
 | 
					      res.header('Cache-control', 'public');
 | 
				
			||||||
      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
					      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
				
			||||||
      res.json(stats);
 | 
					      res.json({
 | 
				
			||||||
 | 
					        oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp,
 | 
				
			||||||
 | 
					        hashrates: hashrates,
 | 
				
			||||||
 | 
					        difficulty: difficulty,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,28 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
 | 
				
			|||||||
      pathRewrite: {
 | 
					      pathRewrite: {
 | 
				
			||||||
          "^/liquid/api/": "/api/v1/"
 | 
					          "^/liquid/api/": "/api/v1/"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      context: ['/liquidtestnet/api/v1/**'],
 | 
				
			||||||
 | 
					      target: `http://localhost:8999`,
 | 
				
			||||||
 | 
					      secure: false,
 | 
				
			||||||
 | 
					      ws: true,
 | 
				
			||||||
 | 
					      changeOrigin: true,
 | 
				
			||||||
 | 
					      proxyTimeout: 30000,
 | 
				
			||||||
 | 
					      pathRewrite: {
 | 
				
			||||||
 | 
					          "^/liquidtestnet": ""
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      context: ['/liquidtestnet/api/**'],
 | 
				
			||||||
 | 
					      target: `http://localhost:8999`,
 | 
				
			||||||
 | 
					      secure: false,
 | 
				
			||||||
 | 
					      changeOrigin: true,
 | 
				
			||||||
 | 
					      proxyTimeout: 30000,
 | 
				
			||||||
 | 
					      pathRewrite: {
 | 
				
			||||||
 | 
					          "^/liquidtestnet/api/": "/api/v1/"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  ]);
 | 
					  ]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,24 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
 | 
				
			|||||||
      changeOrigin: true,
 | 
					      changeOrigin: true,
 | 
				
			||||||
      proxyTimeout: 30000,
 | 
					      proxyTimeout: 30000,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      context: ['/liquidtestnet/api/v1/**'],
 | 
				
			||||||
 | 
					      target: `http://localhost:8999`,
 | 
				
			||||||
 | 
					      secure: false,
 | 
				
			||||||
 | 
					      ws: true,
 | 
				
			||||||
 | 
					      changeOrigin: true,
 | 
				
			||||||
 | 
					      proxyTimeout: 30000,
 | 
				
			||||||
 | 
					      pathRewrite: {
 | 
				
			||||||
 | 
					          "^/liquidtestnet": ""
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      context: ['/liquidtestnet/api/**'],
 | 
				
			||||||
 | 
					      target: `https://liquid.network`,
 | 
				
			||||||
 | 
					      secure: false,
 | 
				
			||||||
 | 
					      changeOrigin: true,
 | 
				
			||||||
 | 
					      proxyTimeout: 30000,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  ]);
 | 
					  ]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,6 @@ import { AssetsFeaturedComponent } from './components/assets/assets-featured/ass
 | 
				
			|||||||
import { AssetsComponent } from './components/assets/assets.component';
 | 
					import { AssetsComponent } from './components/assets/assets.component';
 | 
				
			||||||
import { PoolComponent } from './components/pool/pool.component';
 | 
					import { PoolComponent } from './components/pool/pool.component';
 | 
				
			||||||
import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component';
 | 
					import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component';
 | 
				
			||||||
import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component';
 | 
					 | 
				
			||||||
import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
 | 
					import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
 | 
				
			||||||
import { MiningStartComponent } from './components/mining-start/mining-start.component';
 | 
					import { MiningStartComponent } from './components/mining-start/mining-start.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -75,10 +74,6 @@ let routes: Routes = [
 | 
				
			|||||||
        path: 'mining',
 | 
					        path: 'mining',
 | 
				
			||||||
        component: MiningStartComponent,
 | 
					        component: MiningStartComponent,
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            path: 'difficulty',
 | 
					 | 
				
			||||||
            component: DifficultyChartComponent,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            path: 'hashrate',
 | 
					            path: 'hashrate',
 | 
				
			||||||
            component: HashrateChartComponent,
 | 
					            component: HashrateChartComponent,
 | 
				
			||||||
@ -194,10 +189,6 @@ let routes: Routes = [
 | 
				
			|||||||
            path: 'mining',
 | 
					            path: 'mining',
 | 
				
			||||||
            component: MiningStartComponent,
 | 
					            component: MiningStartComponent,
 | 
				
			||||||
            children: [
 | 
					            children: [
 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                path: 'difficulty',
 | 
					 | 
				
			||||||
                component: DifficultyChartComponent,
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              {
 | 
					              {
 | 
				
			||||||
                path: 'hashrate',
 | 
					                path: 'hashrate',
 | 
				
			||||||
                component: HashrateChartComponent,
 | 
					                component: HashrateChartComponent,
 | 
				
			||||||
@ -307,10 +298,6 @@ let routes: Routes = [
 | 
				
			|||||||
            path: 'mining',
 | 
					            path: 'mining',
 | 
				
			||||||
            component: MiningStartComponent,
 | 
					            component: MiningStartComponent,
 | 
				
			||||||
            children: [
 | 
					            children: [
 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                path: 'difficulty',
 | 
					 | 
				
			||||||
                component: DifficultyChartComponent,
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              {
 | 
					              {
 | 
				
			||||||
                path: 'hashrate',
 | 
					                path: 'hashrate',
 | 
				
			||||||
                component: HashrateChartComponent,
 | 
					                component: HashrateChartComponent,
 | 
				
			||||||
 | 
				
			|||||||
@ -70,7 +70,6 @@ import { AssetsFeaturedComponent } from './components/assets/assets-featured/ass
 | 
				
			|||||||
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
 | 
					import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
 | 
				
			||||||
import { AssetCirculationComponent } from './components/asset-circulation/asset-circulation.component';
 | 
					import { AssetCirculationComponent } from './components/asset-circulation/asset-circulation.component';
 | 
				
			||||||
import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component';
 | 
					import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component';
 | 
				
			||||||
import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component';
 | 
					 | 
				
			||||||
import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
 | 
					import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
 | 
				
			||||||
import { MiningStartComponent } from './components/mining-start/mining-start.component';
 | 
					import { MiningStartComponent } from './components/mining-start/mining-start.component';
 | 
				
			||||||
import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe';
 | 
					import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe';
 | 
				
			||||||
@ -126,7 +125,6 @@ import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe';
 | 
				
			|||||||
    AssetGroupComponent,
 | 
					    AssetGroupComponent,
 | 
				
			||||||
    AssetCirculationComponent,
 | 
					    AssetCirculationComponent,
 | 
				
			||||||
    MiningDashboardComponent,
 | 
					    MiningDashboardComponent,
 | 
				
			||||||
    DifficultyChartComponent,
 | 
					 | 
				
			||||||
    HashrateChartComponent,
 | 
					    HashrateChartComponent,
 | 
				
			||||||
    MiningStartComponent,
 | 
					    MiningStartComponent,
 | 
				
			||||||
    AmountShortenerPipe,
 | 
					    AmountShortenerPipe,
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@
 | 
				
			|||||||
          {{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
					          {{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div *ngIf="showMiningInfo" class="block-size">
 | 
					        <div *ngIf="showMiningInfo" class="block-size">
 | 
				
			||||||
          <app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
 | 
					          <app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
 | 
					        <div *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
 | 
				
			||||||
        <div class="transaction-count">
 | 
					        <div class="transaction-count">
 | 
				
			||||||
 | 
				
			|||||||
@ -1,53 +0,0 @@
 | 
				
			|||||||
<div [class]="widget === false ? 'container-xl' : ''">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <div class="card-header mb-0 mb-lg-4" [style]="widget ? 'display:none' : ''">
 | 
					 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(difficultyObservable$ | async) as diffChanges">
 | 
					 | 
				
			||||||
      <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
 | 
					 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 90">
 | 
					 | 
				
			||||||
          <input ngbButton type="radio" [value]="'3m'" fragment="3m"> 3M
 | 
					 | 
				
			||||||
        </label>
 | 
					 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 180">
 | 
					 | 
				
			||||||
          <input ngbButton type="radio" [value]="'6m'" fragment="6m"> 6M
 | 
					 | 
				
			||||||
        </label>
 | 
					 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 365">
 | 
					 | 
				
			||||||
          <input ngbButton type="radio" [value]="'1y'" fragment="1y"> 1Y
 | 
					 | 
				
			||||||
        </label>
 | 
					 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 730">
 | 
					 | 
				
			||||||
          <input ngbButton type="radio" [value]="'2y'" fragment="2y"> 2Y
 | 
					 | 
				
			||||||
        </label>
 | 
					 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm" [routerLink]="['/mining/difficulty' | relativeUrl]" *ngIf="diffChanges.availableTimespanDay >= 1095">
 | 
					 | 
				
			||||||
          <input ngbButton type="radio" [value]="'3y'" fragment="3y"> 3Y
 | 
					 | 
				
			||||||
        </label>
 | 
					 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm">
 | 
					 | 
				
			||||||
          <input ngbButton type="radio" [value]="'all'" [routerLink]="['/mining/difficulty' | relativeUrl]" fragment="all"> ALL
 | 
					 | 
				
			||||||
        </label>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </form>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <div *ngIf="difficultyObservable$ | async" class="mb-5" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
 | 
					 | 
				
			||||||
  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
					 | 
				
			||||||
    <div class="spinner-border text-light"></div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <table class="table table-borderless table-sm text-center" *ngIf="!widget">
 | 
					 | 
				
			||||||
    <thead>
 | 
					 | 
				
			||||||
      <tr>
 | 
					 | 
				
			||||||
        <th i18n="mining.rank">Block</th>
 | 
					 | 
				
			||||||
        <th i18n="block.timestamp">Timestamp</th>
 | 
					 | 
				
			||||||
        <th i18n="mining.difficulty">Difficulty</th>
 | 
					 | 
				
			||||||
        <th i18n="mining.change">Change</th>
 | 
					 | 
				
			||||||
      </tr>
 | 
					 | 
				
			||||||
    </thead>
 | 
					 | 
				
			||||||
    <tbody *ngIf="(difficultyObservable$ | async) as diffChanges">
 | 
					 | 
				
			||||||
      <tr *ngFor="let diffChange of diffChanges.data">
 | 
					 | 
				
			||||||
        <td><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height }}</a></td>
 | 
					 | 
				
			||||||
        <td>‎{{ diffChange.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
					 | 
				
			||||||
        <td class="d-none d-md-block">{{ formatNumber(diffChange.difficulty, locale, '1.2-2') }}</td>
 | 
					 | 
				
			||||||
        <td class="d-block d-md-none">{{ diffChange.difficultyShorten }}</td>
 | 
					 | 
				
			||||||
        <td [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">{{ formatNumber(diffChange.change, locale, '1.2-2') }}%</td>
 | 
					 | 
				
			||||||
      </tr>
 | 
					 | 
				
			||||||
    </tbody>
 | 
					 | 
				
			||||||
  </table>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
@ -1,27 +0,0 @@
 | 
				
			|||||||
.main-title {
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
  color: #ffffff91;
 | 
					 | 
				
			||||||
  margin-top: -13px;
 | 
					 | 
				
			||||||
  font-size: 10px;
 | 
					 | 
				
			||||||
  text-transform: uppercase;
 | 
					 | 
				
			||||||
  font-weight: 500;
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
  padding-bottom: 3px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.formRadioGroup {
 | 
					 | 
				
			||||||
  margin-top: 6px;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  @media (min-width: 830px) {
 | 
					 | 
				
			||||||
    flex-direction: row;
 | 
					 | 
				
			||||||
    float: right;
 | 
					 | 
				
			||||||
    margin-top: 0px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .btn-sm {
 | 
					 | 
				
			||||||
    font-size: 9px;
 | 
					 | 
				
			||||||
    @media (min-width: 830px) {
 | 
					 | 
				
			||||||
      font-size: 14px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,183 +0,0 @@
 | 
				
			|||||||
import { Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
 | 
					 | 
				
			||||||
import { EChartsOption, graphic } from 'echarts';
 | 
					 | 
				
			||||||
import { Observable } from 'rxjs';
 | 
					 | 
				
			||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
 | 
					 | 
				
			||||||
import { ApiService } from 'src/app/services/api.service';
 | 
					 | 
				
			||||||
import { SeoService } from 'src/app/services/seo.service';
 | 
					 | 
				
			||||||
import { formatNumber } from '@angular/common';
 | 
					 | 
				
			||||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
					 | 
				
			||||||
import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({
 | 
					 | 
				
			||||||
  selector: 'app-difficulty-chart',
 | 
					 | 
				
			||||||
  templateUrl: './difficulty-chart.component.html',
 | 
					 | 
				
			||||||
  styleUrls: ['./difficulty-chart.component.scss'],
 | 
					 | 
				
			||||||
  styles: [`
 | 
					 | 
				
			||||||
    .loadingGraphs {
 | 
					 | 
				
			||||||
      position: absolute;
 | 
					 | 
				
			||||||
      top: 38%;
 | 
					 | 
				
			||||||
      left: calc(50% - 15px);
 | 
					 | 
				
			||||||
      z-index: 100;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  `],
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
export class DifficultyChartComponent implements OnInit {
 | 
					 | 
				
			||||||
  @Input() widget: boolean = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  radioGroupForm: FormGroup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  chartOptions: EChartsOption = {};
 | 
					 | 
				
			||||||
  chartInitOptions = {
 | 
					 | 
				
			||||||
    renderer: 'svg'
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  difficultyObservable$: Observable<any>;
 | 
					 | 
				
			||||||
  isLoading = true;
 | 
					 | 
				
			||||||
  formatNumber = formatNumber;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @Inject(LOCALE_ID) public locale: string,
 | 
					 | 
				
			||||||
    private seoService: SeoService,
 | 
					 | 
				
			||||||
    private apiService: ApiService,
 | 
					 | 
				
			||||||
    private formBuilder: FormBuilder,
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`);
 | 
					 | 
				
			||||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
					 | 
				
			||||||
    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ngOnInit(): void {
 | 
					 | 
				
			||||||
    this.difficultyObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
 | 
					 | 
				
			||||||
      .pipe(
 | 
					 | 
				
			||||||
        startWith('1y'),
 | 
					 | 
				
			||||||
        switchMap((timespan) => {
 | 
					 | 
				
			||||||
          return this.apiService.getHistoricalDifficulty$(timespan)
 | 
					 | 
				
			||||||
            .pipe(
 | 
					 | 
				
			||||||
              tap(data => {
 | 
					 | 
				
			||||||
                this.prepareChartOptions(data.adjustments.map(val => [val.timestamp * 1000, val.difficulty]));
 | 
					 | 
				
			||||||
                this.isLoading = false;
 | 
					 | 
				
			||||||
              }),
 | 
					 | 
				
			||||||
              map(data => {
 | 
					 | 
				
			||||||
                const availableTimespanDay = (
 | 
					 | 
				
			||||||
                  (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp / 1000)
 | 
					 | 
				
			||||||
                ) / 3600 / 24;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const tableData = [];
 | 
					 | 
				
			||||||
                for (let i = data.adjustments.length - 1; i > 0; --i) {
 | 
					 | 
				
			||||||
                  const selectedPowerOfTen: any = selectPowerOfTen(data.adjustments[i].difficulty);
 | 
					 | 
				
			||||||
                  const change = (data.adjustments[i].difficulty / data.adjustments[i - 1].difficulty - 1) * 100;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  tableData.push(Object.assign(data.adjustments[i], {
 | 
					 | 
				
			||||||
                    change: change,
 | 
					 | 
				
			||||||
                    difficultyShorten: formatNumber(
 | 
					 | 
				
			||||||
                      data.adjustments[i].difficulty / selectedPowerOfTen.divider,
 | 
					 | 
				
			||||||
                      this.locale, '1.2-2') + selectedPowerOfTen.unit
 | 
					 | 
				
			||||||
                  }));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return {
 | 
					 | 
				
			||||||
                  availableTimespanDay: availableTimespanDay,
 | 
					 | 
				
			||||||
                  data: tableData
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
              }),
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          }),
 | 
					 | 
				
			||||||
          share()
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  prepareChartOptions(data) {
 | 
					 | 
				
			||||||
    this.chartOptions = {
 | 
					 | 
				
			||||||
      color: new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
					 | 
				
			||||||
        { offset: 0, color: '#D81B60' },
 | 
					 | 
				
			||||||
        { offset: 0.25, color: '#8E24AA' },
 | 
					 | 
				
			||||||
        { offset: 0.5, color: '#5E35B1' },
 | 
					 | 
				
			||||||
        { offset: 0.75, color: '#3949AB' },
 | 
					 | 
				
			||||||
        { offset: 1, color: '#1E88E5' }
 | 
					 | 
				
			||||||
      ]),
 | 
					 | 
				
			||||||
      title: {
 | 
					 | 
				
			||||||
        text: this.widget? '' : $localize`:@@mining.difficulty:Difficulty`,
 | 
					 | 
				
			||||||
        left: 'center',
 | 
					 | 
				
			||||||
        textStyle: {
 | 
					 | 
				
			||||||
          color: '#FFF',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      tooltip: {
 | 
					 | 
				
			||||||
        show: true,
 | 
					 | 
				
			||||||
        trigger: 'axis',
 | 
					 | 
				
			||||||
        backgroundColor: 'rgba(17, 19, 31, 1)',
 | 
					 | 
				
			||||||
        borderRadius: 4,
 | 
					 | 
				
			||||||
        shadowColor: 'rgba(0, 0, 0, 0.5)',
 | 
					 | 
				
			||||||
        textStyle: {
 | 
					 | 
				
			||||||
          color: '#b1b1b1',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        borderColor: '#000',
 | 
					 | 
				
			||||||
        formatter: params => {
 | 
					 | 
				
			||||||
          return `<b style="color: white">${params[0].axisValueLabel}</b><br>
 | 
					 | 
				
			||||||
            ${params[0].marker} ${formatNumber(params[0].value[1], this.locale, '1.0-0')}`
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      axisPointer: {
 | 
					 | 
				
			||||||
        type: 'line',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      xAxis: {
 | 
					 | 
				
			||||||
        type: 'time',
 | 
					 | 
				
			||||||
        splitNumber: this.isMobile() ? 5 : 10,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      yAxis: {
 | 
					 | 
				
			||||||
        type: 'value',
 | 
					 | 
				
			||||||
        axisLabel: {
 | 
					 | 
				
			||||||
          formatter: (val) => {
 | 
					 | 
				
			||||||
            const selectedPowerOfTen: any = selectPowerOfTen(val);
 | 
					 | 
				
			||||||
            const diff = val / selectedPowerOfTen.divider;
 | 
					 | 
				
			||||||
            return `${diff} ${selectedPowerOfTen.unit}`;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        splitLine: {
 | 
					 | 
				
			||||||
          lineStyle: {
 | 
					 | 
				
			||||||
            type: 'dotted',
 | 
					 | 
				
			||||||
            color: '#ffffff66',
 | 
					 | 
				
			||||||
            opacity: 0.25,
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      series: {
 | 
					 | 
				
			||||||
        showSymbol: false,
 | 
					 | 
				
			||||||
        data: data,
 | 
					 | 
				
			||||||
        type: 'line',
 | 
					 | 
				
			||||||
        smooth: false,
 | 
					 | 
				
			||||||
        lineStyle: {
 | 
					 | 
				
			||||||
          width: 2,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      dataZoom: this.widget ? null : [{
 | 
					 | 
				
			||||||
        type: 'inside',
 | 
					 | 
				
			||||||
        realtime: true,
 | 
					 | 
				
			||||||
        zoomLock: true,
 | 
					 | 
				
			||||||
        zoomOnMouseWheel: true,
 | 
					 | 
				
			||||||
        moveOnMouseMove: true,
 | 
					 | 
				
			||||||
        maxSpan: 100,
 | 
					 | 
				
			||||||
        minSpan: 10,
 | 
					 | 
				
			||||||
      }, {
 | 
					 | 
				
			||||||
        showDetail: false,
 | 
					 | 
				
			||||||
        show: true,
 | 
					 | 
				
			||||||
        type: 'slider',
 | 
					 | 
				
			||||||
        brushSelect: false,
 | 
					 | 
				
			||||||
        realtime: true,
 | 
					 | 
				
			||||||
        bottom: 0,
 | 
					 | 
				
			||||||
        selectedDataBackground: {
 | 
					 | 
				
			||||||
          lineStyle: {
 | 
					 | 
				
			||||||
            color: '#fff',
 | 
					 | 
				
			||||||
            opacity: 0.45,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          areaStyle: {
 | 
					 | 
				
			||||||
            opacity: 0,
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      }],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  isMobile() {
 | 
					 | 
				
			||||||
    return (window.innerWidth <= 767.98);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -25,9 +25,34 @@
 | 
				
			|||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div *ngIf="hashrateObservable$ | async" [class]="widget === false ? 'chart' : ''" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
 | 
					  <div *ngIf="hashrateObservable$ | async" [class]="!widget ? 'chart' : 'chart-widget'"
 | 
				
			||||||
 | 
					    echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
 | 
				
			||||||
  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
					  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
				
			||||||
    <div class="spinner-border text-light"></div>
 | 
					    <div class="spinner-border text-light"></div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  <div class="mt-3" *ngIf="!widget">
 | 
				
			||||||
 | 
					    <table class="table table-borderless table-sm text-center">
 | 
				
			||||||
 | 
					      <thead>
 | 
				
			||||||
 | 
					        <tr>
 | 
				
			||||||
 | 
					          <th i18n="mining.rank">Block</th>
 | 
				
			||||||
 | 
					          <th class="d-none d-md-block" i18n="block.timestamp">Timestamp</th>
 | 
				
			||||||
 | 
					          <th i18n="mining.adjusted">Adjusted</th>
 | 
				
			||||||
 | 
					          <th i18n="mining.difficulty">Difficulty</th>
 | 
				
			||||||
 | 
					          <th i18n="mining.change">Change</th>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					      </thead>
 | 
				
			||||||
 | 
					      <tbody *ngIf="(hashrateObservable$ | async) as data">
 | 
				
			||||||
 | 
					        <tr *ngFor="let diffChange of data.difficulty">
 | 
				
			||||||
 | 
					          <td><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height }}</a></td>
 | 
				
			||||||
 | 
					          <td class="d-none d-md-block">‎{{ diffChange.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
				
			||||||
 | 
					          <td><app-time-since [time]="diffChange.timestamp" [fastRender]="true"></app-time-since></td>
 | 
				
			||||||
 | 
					          <td class="d-none d-md-block">{{ formatNumber(diffChange.difficulty, locale, '1.2-2') }}</td>
 | 
				
			||||||
 | 
					          <td class="d-block d-md-none">{{ diffChange.difficultyShorten }}</td>
 | 
				
			||||||
 | 
					          <td [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">{{ formatNumber(diffChange.change, locale, '1.2-2') }}%</td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					      </tbody>
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,11 @@
 | 
				
			|||||||
  padding-bottom: 20px;
 | 
					  padding-bottom: 20px;
 | 
				
			||||||
  padding-right: 20px;
 | 
					  padding-right: 20px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.chart-widget {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  max-height: 275px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.formRadioGroup {
 | 
					.formRadioGroup {
 | 
				
			||||||
  margin-top: 6px;
 | 
					  margin-top: 6px;
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import { selectPowerOfTen } from 'src/app/bitcoin.utils';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class HashrateChartComponent implements OnInit {
 | 
					export class HashrateChartComponent implements OnInit {
 | 
				
			||||||
  @Input() widget: boolean = false;
 | 
					  @Input() widget: boolean = false;
 | 
				
			||||||
  @Input() right: number | string = 10;
 | 
					  @Input() right: number | string = 45;
 | 
				
			||||||
  @Input() left: number | string = 75;
 | 
					  @Input() left: number | string = 75;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  radioGroupForm: FormGroup;
 | 
					  radioGroupForm: FormGroup;
 | 
				
			||||||
@ -45,7 +45,7 @@ export class HashrateChartComponent implements OnInit {
 | 
				
			|||||||
    private apiService: ApiService,
 | 
					    private apiService: ApiService,
 | 
				
			||||||
    private formBuilder: FormBuilder,
 | 
					    private formBuilder: FormBuilder,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.seoService.setTitle($localize`:@@mining.hashrate:hashrate`);
 | 
					    this.seoService.setTitle($localize`:@@mining.hashrate-difficulty:Hashrate and Difficulty`);
 | 
				
			||||||
    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
					    this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
 | 
				
			||||||
    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
					    this.radioGroupForm.controls.dateSpan.setValue('1y');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -57,17 +57,61 @@ export class HashrateChartComponent implements OnInit {
 | 
				
			|||||||
        switchMap((timespan) => {
 | 
					        switchMap((timespan) => {
 | 
				
			||||||
          return this.apiService.getHistoricalHashrate$(timespan)
 | 
					          return this.apiService.getHistoricalHashrate$(timespan)
 | 
				
			||||||
            .pipe(
 | 
					            .pipe(
 | 
				
			||||||
              tap(data => {
 | 
					              tap((data: any) => {
 | 
				
			||||||
                this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate]));
 | 
					                // We generate duplicated data point so the tooltip works nicely
 | 
				
			||||||
 | 
					                const diffFixed = [];
 | 
				
			||||||
 | 
					                let diffIndex = 1;
 | 
				
			||||||
 | 
					                let hashIndex = 0;
 | 
				
			||||||
 | 
					                while (hashIndex < data.hashrates.length) {
 | 
				
			||||||
 | 
					                  if (diffIndex >= data.difficulty.length) {
 | 
				
			||||||
 | 
					                    while (hashIndex < data.hashrates.length) {
 | 
				
			||||||
 | 
					                      diffFixed.push({
 | 
				
			||||||
 | 
					                        timestamp: data.hashrates[hashIndex].timestamp,
 | 
				
			||||||
 | 
					                        difficulty: data.difficulty[data.difficulty.length - 1].difficulty
 | 
				
			||||||
 | 
					                      });
 | 
				
			||||||
 | 
					                      ++hashIndex;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  while (hashIndex < data.hashrates.length && diffIndex < data.difficulty.length &&
 | 
				
			||||||
 | 
					                    data.hashrates[hashIndex].timestamp <= data.difficulty[diffIndex].timestamp
 | 
				
			||||||
 | 
					                  ) {
 | 
				
			||||||
 | 
					                    diffFixed.push({
 | 
				
			||||||
 | 
					                      timestamp: data.hashrates[hashIndex].timestamp,
 | 
				
			||||||
 | 
					                      difficulty: data.difficulty[diffIndex - 1].difficulty
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    ++hashIndex;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  ++diffIndex;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.prepareChartOptions({
 | 
				
			||||||
 | 
					                  hashrates: data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate]),
 | 
				
			||||||
 | 
					                  difficulty: diffFixed.map(val => [val.timestamp * 1000, val.difficulty])
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
                this.isLoading = false;
 | 
					                this.isLoading = false;
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
              map(data => {
 | 
					              map((data: any) => {
 | 
				
			||||||
                const availableTimespanDay = (
 | 
					                const availableTimespanDay = (
 | 
				
			||||||
                  (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp / 1000)
 | 
					                  (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp)
 | 
				
			||||||
                ) / 3600 / 24;
 | 
					                ) / 3600 / 24;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const tableData = [];
 | 
				
			||||||
 | 
					                for (let i = data.difficulty.length - 1; i > 0; --i) {
 | 
				
			||||||
 | 
					                  const selectedPowerOfTen: any = selectPowerOfTen(data.difficulty[i].difficulty);
 | 
				
			||||||
 | 
					                  const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  tableData.push(Object.assign(data.difficulty[i], {
 | 
				
			||||||
 | 
					                    change: change,
 | 
				
			||||||
 | 
					                    difficultyShorten: formatNumber(
 | 
				
			||||||
 | 
					                      data.difficulty[i].difficulty / selectedPowerOfTen.divider,
 | 
				
			||||||
 | 
					                      this.locale, '1.2-2') + selectedPowerOfTen.unit
 | 
				
			||||||
 | 
					                  }));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                  availableTimespanDay: availableTimespanDay,
 | 
					                  availableTimespanDay: availableTimespanDay,
 | 
				
			||||||
                  data: data.hashrates
 | 
					                  difficulty: tableData
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@ -78,72 +122,144 @@ export class HashrateChartComponent implements OnInit {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  prepareChartOptions(data) {
 | 
					  prepareChartOptions(data) {
 | 
				
			||||||
    this.chartOptions = {
 | 
					    this.chartOptions = {
 | 
				
			||||||
      color: new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
					      color: [
 | 
				
			||||||
        { offset: 0, color: '#F4511E' },
 | 
					        new graphic.LinearGradient(0, 0, 0, 0.65, [
 | 
				
			||||||
        { offset: 0.25, color: '#FB8C00' },
 | 
					          { offset: 0, color: '#F4511E' },
 | 
				
			||||||
        { offset: 0.5, color: '#FFB300' },
 | 
					          { offset: 0.25, color: '#FB8C00' },
 | 
				
			||||||
        { offset: 0.75, color: '#FDD835' },
 | 
					          { offset: 0.5, color: '#FFB300' },
 | 
				
			||||||
        { offset: 1, color: '#7CB342' }
 | 
					          { offset: 0.75, color: '#FDD835' },
 | 
				
			||||||
      ]),
 | 
					          { offset: 1, color: '#7CB342' }
 | 
				
			||||||
 | 
					        ]),
 | 
				
			||||||
 | 
					        '#D81B60',
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
      grid: {
 | 
					      grid: {
 | 
				
			||||||
        right: this.right,
 | 
					        right: this.right,
 | 
				
			||||||
        left: this.left,
 | 
					        left: this.left,
 | 
				
			||||||
      },
 | 
					        bottom: this.widget ? 30 : 60,
 | 
				
			||||||
      title: {
 | 
					 | 
				
			||||||
        text: this.widget ? '' : $localize`:@@mining.hashrate:Hashrate`,
 | 
					 | 
				
			||||||
        left: 'center',
 | 
					 | 
				
			||||||
        textStyle: {
 | 
					 | 
				
			||||||
          color: '#FFF',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      tooltip: {
 | 
					      tooltip: {
 | 
				
			||||||
        show: true,
 | 
					 | 
				
			||||||
        trigger: 'axis',
 | 
					        trigger: 'axis',
 | 
				
			||||||
 | 
					        axisPointer: {
 | 
				
			||||||
 | 
					          type: 'line'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        backgroundColor: 'rgba(17, 19, 31, 1)',
 | 
					        backgroundColor: 'rgba(17, 19, 31, 1)',
 | 
				
			||||||
        borderRadius: 4,
 | 
					        borderRadius: 4,
 | 
				
			||||||
        shadowColor: 'rgba(0, 0, 0, 0.5)',
 | 
					        shadowColor: 'rgba(0, 0, 0, 0.5)',
 | 
				
			||||||
        textStyle: {
 | 
					        textStyle: {
 | 
				
			||||||
          color: '#b1b1b1',
 | 
					          color: '#b1b1b1',
 | 
				
			||||||
 | 
					          align: 'left',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        borderColor: '#000',
 | 
					        borderColor: '#000',
 | 
				
			||||||
        formatter: params => {
 | 
					        formatter: function (data) {
 | 
				
			||||||
          return `<b style="color: white">${params[0].axisValueLabel}</b><br>
 | 
					          let hashratePowerOfTen: any = selectPowerOfTen(1);
 | 
				
			||||||
            ${params[0].marker} ${formatNumber(params[0].value[1], this.locale, '1.0-0')} H/s`
 | 
					          let hashrate = data[0].data[1];
 | 
				
			||||||
        }
 | 
					          let difficultyPowerOfTen = hashratePowerOfTen;
 | 
				
			||||||
      },
 | 
					          let difficulty = data[1].data[1];
 | 
				
			||||||
      axisPointer: {
 | 
					
 | 
				
			||||||
        type: 'line',
 | 
					          if (this.isMobile()) {
 | 
				
			||||||
 | 
					            hashratePowerOfTen = selectPowerOfTen(data[0].data[1]);
 | 
				
			||||||
 | 
					            hashrate = Math.round(data[0].data[1] / hashratePowerOfTen.divider);
 | 
				
			||||||
 | 
					            difficultyPowerOfTen = selectPowerOfTen(data[1].data[1]);
 | 
				
			||||||
 | 
					            difficulty = Math.round(data[1].data[1] / difficultyPowerOfTen.divider);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return `
 | 
				
			||||||
 | 
					            <b style="color: white; margin-left: 18px">${data[0].axisValueLabel}</b><br>
 | 
				
			||||||
 | 
					            <span>${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br>
 | 
				
			||||||
 | 
					            <span>${data[1].marker} ${data[1].seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}</span>
 | 
				
			||||||
 | 
					          `;
 | 
				
			||||||
 | 
					        }.bind(this)
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      xAxis: {
 | 
					      xAxis: {
 | 
				
			||||||
        type: 'time',
 | 
					        type: 'time',
 | 
				
			||||||
        splitNumber: this.isMobile() ? 5 : 10,
 | 
					        splitNumber: this.isMobile() ? 5 : 10,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      yAxis: {
 | 
					      legend: {
 | 
				
			||||||
        type: 'value',
 | 
					        data: [
 | 
				
			||||||
        axisLabel: {
 | 
					          {
 | 
				
			||||||
          formatter: (val) => {
 | 
					            name: 'Hashrate',
 | 
				
			||||||
            const selectedPowerOfTen: any = selectPowerOfTen(val);
 | 
					            inactiveColor: 'rgb(110, 112, 121)',
 | 
				
			||||||
            const newVal = val / selectedPowerOfTen.divider;
 | 
					            textStyle: {
 | 
				
			||||||
            return `${newVal} ${selectedPowerOfTen.unit}H/s`
 | 
					              color: 'white',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            icon: 'roundRect',
 | 
				
			||||||
 | 
					            itemStyle: {
 | 
				
			||||||
 | 
					              color: '#FFB300',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: 'Difficulty',
 | 
				
			||||||
 | 
					            inactiveColor: 'rgb(110, 112, 121)',
 | 
				
			||||||
 | 
					            textStyle: {
 | 
				
			||||||
 | 
					              color: 'white',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            icon: 'roundRect',
 | 
				
			||||||
 | 
					            itemStyle: {
 | 
				
			||||||
 | 
					              color: '#D81B60',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      yAxis: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          min: function (value) {
 | 
				
			||||||
 | 
					            return value.min * 0.9;
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          type: 'value',
 | 
				
			||||||
 | 
					          name: 'Hashrate',
 | 
				
			||||||
 | 
					          axisLabel: {
 | 
				
			||||||
 | 
					            color: 'rgb(110, 112, 121)',
 | 
				
			||||||
 | 
					            formatter: (val) => {
 | 
				
			||||||
 | 
					              const selectedPowerOfTen: any = selectPowerOfTen(val);
 | 
				
			||||||
 | 
					              const newVal = Math.round(val / selectedPowerOfTen.divider);
 | 
				
			||||||
 | 
					              return `${newVal} ${selectedPowerOfTen.unit}H/s`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          splitLine: {
 | 
				
			||||||
 | 
					            show: false,
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        splitLine: {
 | 
					        {
 | 
				
			||||||
 | 
					          min: function (value) {
 | 
				
			||||||
 | 
					            return value.min * 0.9;
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          type: 'value',
 | 
				
			||||||
 | 
					          name: 'Difficulty',
 | 
				
			||||||
 | 
					          position: 'right',
 | 
				
			||||||
 | 
					          axisLabel: {
 | 
				
			||||||
 | 
					            color: 'rgb(110, 112, 121)',
 | 
				
			||||||
 | 
					            formatter: (val) => {
 | 
				
			||||||
 | 
					              const selectedPowerOfTen: any = selectPowerOfTen(val);
 | 
				
			||||||
 | 
					              const newVal = Math.round(val / selectedPowerOfTen.divider);
 | 
				
			||||||
 | 
					              return `${newVal} ${selectedPowerOfTen.unit}`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          splitLine: {
 | 
				
			||||||
 | 
					            show: false,
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      series: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: 'Hashrate',
 | 
				
			||||||
 | 
					          showSymbol: false,
 | 
				
			||||||
 | 
					          data: data.hashrates,
 | 
				
			||||||
 | 
					          type: 'line',
 | 
				
			||||||
          lineStyle: {
 | 
					          lineStyle: {
 | 
				
			||||||
            type: 'dotted',
 | 
					            width: 2,
 | 
				
			||||||
            color: '#ffffff66',
 | 
					          },
 | 
				
			||||||
            opacity: 0.25,
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          yAxisIndex: 1,
 | 
				
			||||||
 | 
					          name: 'Difficulty',
 | 
				
			||||||
 | 
					          showSymbol: false,
 | 
				
			||||||
 | 
					          data: data.difficulty,
 | 
				
			||||||
 | 
					          type: 'line',
 | 
				
			||||||
 | 
					          lineStyle: {
 | 
				
			||||||
 | 
					            width: 3,
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
      },
 | 
					      ],
 | 
				
			||||||
      series: {
 | 
					 | 
				
			||||||
        showSymbol: false,
 | 
					 | 
				
			||||||
        data: data,
 | 
					 | 
				
			||||||
        type: 'line',
 | 
					 | 
				
			||||||
        smooth: false,
 | 
					 | 
				
			||||||
        lineStyle: {
 | 
					 | 
				
			||||||
          width: 2,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      dataZoom: this.widget ? null : [{
 | 
					      dataZoom: this.widget ? null : [{
 | 
				
			||||||
        type: 'inside',
 | 
					        type: 'inside',
 | 
				
			||||||
        realtime: true,
 | 
					        realtime: true,
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
              {{ projectedBlock.feeRange[0] | number:feeRounding }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
					              {{ projectedBlock.feeRange[0] | number:feeRounding }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div *ngIf="showMiningInfo" class="block-size">
 | 
					            <div *ngIf="showMiningInfo" class="block-size">
 | 
				
			||||||
              <app-amount [satoshis]="projectedBlock.totalFees + blockSubsidy * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
 | 
					              <app-amount [satoshis]="projectedBlock.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (projectedBlock.blockSize | bytes: 2)"></div>
 | 
					            <div *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (projectedBlock.blockSize | bytes: 2)"></div>
 | 
				
			||||||
            <div class="transaction-count">
 | 
					            <div class="transaction-count">
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  network = '';
 | 
					  network = '';
 | 
				
			||||||
  now = new Date().getTime();
 | 
					  now = new Date().getTime();
 | 
				
			||||||
  showMiningInfo = false;
 | 
					  showMiningInfo = false;
 | 
				
			||||||
  blockSubsidy = 50;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  blockWidth = 125;
 | 
					  blockWidth = 125;
 | 
				
			||||||
  blockPadding = 30;
 | 
					  blockPadding = 30;
 | 
				
			||||||
@ -111,7 +110,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
            if (this.stateService.network === '') {
 | 
					            if (this.stateService.network === '') {
 | 
				
			||||||
              block.blink = specialBlocks[block.height] ? true : false;
 | 
					              block.blink = specialBlocks[block.height] ? true : false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.setBlockSubsidy(block.height);
 | 
					 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const stringifiedBlocks = JSON.stringify(mempoolBlocks);
 | 
					          const stringifiedBlocks = JSON.stringify(mempoolBlocks);
 | 
				
			||||||
@ -212,18 +210,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    return block.index;
 | 
					    return block.index;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setBlockSubsidy(blockHeight) {
 | 
					 | 
				
			||||||
    if (!['', 'testnet', 'signet'].includes(this.stateService.network)) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.blockSubsidy = 50;
 | 
					 | 
				
			||||||
    let halvenings = Math.floor(blockHeight / 210000);
 | 
					 | 
				
			||||||
    while (halvenings > 0) {
 | 
					 | 
				
			||||||
      this.blockSubsidy = this.blockSubsidy / 2;
 | 
					 | 
				
			||||||
      halvenings--;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
 | 
					  reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
 | 
				
			||||||
    const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
 | 
					    const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
 | 
				
			||||||
    const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
 | 
					    const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
 | 
				
			||||||
 | 
				
			|||||||
@ -5,11 +5,13 @@
 | 
				
			|||||||
    <!-- pool distribution -->
 | 
					    <!-- pool distribution -->
 | 
				
			||||||
    <div class="col">
 | 
					    <div class="col">
 | 
				
			||||||
      <div class="card">
 | 
					      <div class="card">
 | 
				
			||||||
        <div class="card-body">
 | 
					        <div class="card-body pool-ranking">
 | 
				
			||||||
          <h5 class="card-title" i18n="mining.pool-share">Mining Pools Share (1w)</h5>
 | 
					          <h5 class="card-title">
 | 
				
			||||||
 | 
					            <a href="" [routerLink]="['/mining/pools' | relativeUrl]" i18n="mining.pool-share">
 | 
				
			||||||
 | 
					              Mining Pools Share (1w)
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					          </h5>
 | 
				
			||||||
          <app-pool-ranking [widget]=true></app-pool-ranking>
 | 
					          <app-pool-ranking [widget]=true></app-pool-ranking>
 | 
				
			||||||
          <div class="text-center"><a href="" [routerLink]="['/mining/pools' | relativeUrl]" i18n="dashboard.view-more">View more
 | 
					 | 
				
			||||||
              »</a></div>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -18,22 +20,12 @@
 | 
				
			|||||||
    <div class="col">
 | 
					    <div class="col">
 | 
				
			||||||
      <div class="card">
 | 
					      <div class="card">
 | 
				
			||||||
        <div class="card-body">
 | 
					        <div class="card-body">
 | 
				
			||||||
          <h5 class="card-title" i18n="mining.hashrate">Hashrate (1y)</h5>
 | 
					          <h5 class="card-title">
 | 
				
			||||||
 | 
					            <a class="link" href="" [routerLink]="['/mining/hashrate' | relativeUrl]" i18n="mining.hashrate">
 | 
				
			||||||
 | 
					              Hashrate (1y)
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
 | 
					          </h5>
 | 
				
			||||||
          <app-hashrate-chart [widget]=true></app-hashrate-chart>
 | 
					          <app-hashrate-chart [widget]=true></app-hashrate-chart>
 | 
				
			||||||
          <div class="text-center"><a href="" [routerLink]="['/mining/hashrate' | relativeUrl]" i18n="dashboard.view-more">View more
 | 
					 | 
				
			||||||
              »</a></div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <!-- difficulty -->
 | 
					 | 
				
			||||||
    <div class="col">
 | 
					 | 
				
			||||||
      <div class="card">
 | 
					 | 
				
			||||||
        <div class="card-body">
 | 
					 | 
				
			||||||
          <h5 class="card-title" i18n="ning.difficulty">Difficulty (1y)</h5>
 | 
					 | 
				
			||||||
          <app-difficulty-chart [widget]=true></app-difficulty-chart>
 | 
					 | 
				
			||||||
          <div class="text-center"><a href="" [routerLink]="['/mining/difficulty' | relativeUrl]" i18n="dashboard.view-more">View more
 | 
					 | 
				
			||||||
              »</a></div>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -16,22 +16,17 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.card-title {
 | 
					.card-title {
 | 
				
			||||||
  color: #4a68b9;
 | 
					 | 
				
			||||||
  font-size: 1rem;
 | 
					  font-size: 1rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.card-title > a {
 | 
				
			||||||
 | 
					  color: #4a68b9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.card-wrapper {
 | 
					.card-body {
 | 
				
			||||||
  .card {
 | 
					  padding: 1.25rem 1rem 0.75rem 1rem;
 | 
				
			||||||
    height: auto !important;
 | 
					}
 | 
				
			||||||
  }
 | 
					.card-body.pool-ranking {
 | 
				
			||||||
  .card-body {
 | 
					  padding: 1.25rem 0.25rem 0.75rem 0.25rem;
 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex: inherit;
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    justify-content: space-around;
 | 
					 | 
				
			||||||
    padding: 22px 20px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#blockchain-container {
 | 
					#blockchain-container {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,12 @@
 | 
				
			|||||||
<div [class]="widget === false ? 'container-xl' : ''">
 | 
					<div [class]="widget === false ? 'container-xl' : ''">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="hashrate-pie" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
 | 
					  <div [class]="widget ? 'chart-widget' : 'chart'"
 | 
				
			||||||
 | 
					    echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
 | 
				
			||||||
  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
					  <div class="text-center loadingGraphs" *ngIf="isLoading">
 | 
				
			||||||
    <div class="spinner-border text-light"></div>
 | 
					    <div class="spinner-border text-light"></div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="card-header mb-0 mb-lg-4" [style]="widget === true ? 'display:none' : ''">
 | 
					  <div class="card-header mb-0 mb-lg-4 mt-md-3" [style]="widget ? 'display:none' : ''">
 | 
				
			||||||
    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(miningStatsObservable$ | async) as miningStats">
 | 
					    <form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(miningStatsObservable$ | async) as miningStats">
 | 
				
			||||||
      <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
 | 
					      <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
 | 
				
			||||||
        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1">
 | 
					        <label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1">
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,14 @@
 | 
				
			|||||||
.hashrate-pie {
 | 
					.chart {
 | 
				
			||||||
  height: 100%;
 | 
					  max-height: 400px;
 | 
				
			||||||
  min-height: 400px;
 | 
					 | 
				
			||||||
  @media (max-width: 767.98px) {
 | 
					  @media (max-width: 767.98px) {
 | 
				
			||||||
    min-height: 300px;
 | 
					    max-height: 300px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.chart-widget {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  max-height: 275px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.formRadioGroup {
 | 
					.formRadioGroup {
 | 
				
			||||||
  margin-top: 6px;
 | 
					  margin-top: 6px;
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,9 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
  isLoading = true;
 | 
					  isLoading = true;
 | 
				
			||||||
  chartOptions: EChartsOption = {};
 | 
					  chartOptions: EChartsOption = {};
 | 
				
			||||||
  chartInitOptions = {
 | 
					  chartInitOptions = {
 | 
				
			||||||
    renderer: 'svg'
 | 
					    renderer: 'svg',
 | 
				
			||||||
 | 
					    width: 'auto',
 | 
				
			||||||
 | 
					    height: 'auto',
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  chartInstance: any = undefined;
 | 
					  chartInstance: any = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -156,6 +158,11 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    network = network.charAt(0).toUpperCase() + network.slice(1);
 | 
					    network = network.charAt(0).toUpperCase() + network.slice(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let radius: any[] = ['20%', '70%'];
 | 
				
			||||||
 | 
					    if (this.isMobile() || this.widget) {
 | 
				
			||||||
 | 
					      radius = ['20%', '65%'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.chartOptions = {
 | 
					    this.chartOptions = {
 | 
				
			||||||
      title: {
 | 
					      title: {
 | 
				
			||||||
        text: this.widget ? '' : $localize`:@@mining.pool-chart-title:${network}:NETWORK: mining pools share`,
 | 
					        text: this.widget ? '' : $localize`:@@mining.pool-chart-title:${network}:NETWORK: mining pools share`,
 | 
				
			||||||
@ -169,17 +176,21 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      tooltip: {
 | 
					      tooltip: {
 | 
				
			||||||
        trigger: 'item'
 | 
					        trigger: 'item',
 | 
				
			||||||
 | 
					        textStyle: {
 | 
				
			||||||
 | 
					          align: 'left',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      series: [
 | 
					      series: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          top: this.widget ? '0%' : (this.isMobile() ? '5%' : '10%'),
 | 
					          top: this.widget ? 0 : 35,
 | 
				
			||||||
          bottom: this.widget ? '0%' : (this.isMobile() ? '0%' : '5%'),
 | 
					 | 
				
			||||||
          name: 'Mining pool',
 | 
					          name: 'Mining pool',
 | 
				
			||||||
          type: 'pie',
 | 
					          type: 'pie',
 | 
				
			||||||
          radius: this.widget ? ['20%', '60%'] : (this.isMobile() ? ['10%', '50%'] : ['20%', '70%']),
 | 
					          radius: radius,
 | 
				
			||||||
          data: this.generatePoolsChartSerieData(miningStats),
 | 
					          data: this.generatePoolsChartSerieData(miningStats),
 | 
				
			||||||
          labelLine: {
 | 
					          labelLine: {
 | 
				
			||||||
 | 
					            length: this.isMobile() ? 10 : 15,
 | 
				
			||||||
 | 
					            length2: this.isMobile() ? 0 : 15,
 | 
				
			||||||
            lineStyle: {
 | 
					            lineStyle: {
 | 
				
			||||||
              width: 2,
 | 
					              width: 2,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
				
			|||||||
@ -172,9 +172,12 @@
 | 
				
			|||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
                <td class="text-right nowrap amount">
 | 
					                <td class="text-right nowrap amount">
 | 
				
			||||||
                  <ng-template [ngIf]="vout.asset && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
 | 
					                  <ng-template [ngIf]="vout.asset && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
 | 
				
			||||||
                    <div *ngIf="assetsMinimal && assetsMinimal[vout.asset]">
 | 
					                    <div *ngIf="assetsMinimal && assetsMinimal[vout.asset] else assetNotFound">
 | 
				
			||||||
                      <ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vout }"></ng-container>
 | 
					                      <ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vout }"></ng-container>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <ng-template #assetNotFound>
 | 
				
			||||||
 | 
					                      {{ vout.value }} <a [routerLink]="['/assets/asset/' | relativeUrl, vout.asset]">{{ vout.asset | slice : 0 : 7 }}</a>
 | 
				
			||||||
 | 
					                    </ng-template>
 | 
				
			||||||
                  </ng-template>
 | 
					                  </ng-template>
 | 
				
			||||||
                  <ng-template #defaultOutput>
 | 
					                  <ng-template #defaultOutput>
 | 
				
			||||||
                    <app-amount [satoshis]="vout.value"></app-amount>
 | 
					                    <app-amount [satoshis]="vout.value"></app-amount>
 | 
				
			||||||
 | 
				
			|||||||
@ -101,6 +101,7 @@ export interface PoolStat {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BlockExtension {
 | 
					export interface BlockExtension {
 | 
				
			||||||
 | 
					  totalFees?: number;
 | 
				
			||||||
  medianFee?: number;
 | 
					  medianFee?: number;
 | 
				
			||||||
  feeRange?: number[];
 | 
					  feeRange?: number[];
 | 
				
			||||||
  reward?: number;
 | 
					  reward?: number;
 | 
				
			||||||
 | 
				
			|||||||
@ -82,7 +82,7 @@ export class MiningService {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const availableTimespanDay = (
 | 
					    const availableTimespanDay = (
 | 
				
			||||||
      (new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp / 1000)
 | 
					      (new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp)
 | 
				
			||||||
    ) / 3600 / 24;
 | 
					    ) / 3600 / 24;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user