Merge branch 'master' into nymkappa/bugfix/hashrate-native-js-timestamp
This commit is contained in:
		
						commit
						a91fa797fa
					
				@ -1,4 +1,4 @@
 | 
			
		||||
import { PoolInfo, PoolStats } from '../mempool.interfaces';
 | 
			
		||||
import { PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
 | 
			
		||||
import BlocksRepository from '../repositories/BlocksRepository';
 | 
			
		||||
import PoolsRepository from '../repositories/PoolsRepository';
 | 
			
		||||
import HashratesRepository from '../repositories/HashratesRepository';
 | 
			
		||||
@ -70,6 +70,13 @@ class Mining {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get miner reward stats
 | 
			
		||||
   */
 | 
			
		||||
  public async $getRewardStats(blockCount: number): Promise<RewardStats> {
 | 
			
		||||
    return await BlocksRepository.$getBlockStats(blockCount);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * [INDEXING] Generate weekly mining pool hashrate history
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
@ -312,6 +312,7 @@ class Server {
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate', routes.$getHistoricalHashrate)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -209,3 +209,9 @@ export interface IDifficultyAdjustment {
 | 
			
		||||
  timeAvg: number;
 | 
			
		||||
  timeOffset: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RewardStats {
 | 
			
		||||
  totalReward: number;
 | 
			
		||||
  totalFee: number;
 | 
			
		||||
  totalTx: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -354,6 +354,9 @@ class BlocksRepository {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return oldest blocks height
 | 
			
		||||
   */
 | 
			
		||||
  public async $getOldestIndexedBlockHeight(): Promise<number> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
@ -367,6 +370,29 @@ class BlocksRepository {
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get general block stats
 | 
			
		||||
   */
 | 
			
		||||
  public async $getBlockStats(blockCount: number): Promise<any> {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
      // We need to use a subquery
 | 
			
		||||
      const query = `SELECT SUM(reward) as totalReward, SUM(fees) as totalFee, SUM(tx_count) as totalTx
 | 
			
		||||
        FROM (SELECT reward, fees, tx_count FROM blocks ORDER by height DESC LIMIT ${blockCount}) as sub`;
 | 
			
		||||
 | 
			
		||||
      const [rows]: any = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 
 | 
			
		||||
      return rows[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('$getBlockStats() error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new BlocksRepository();
 | 
			
		||||
 | 
			
		||||
@ -935,6 +935,15 @@ class Routes {
 | 
			
		||||
      res.status(500).end();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getRewardStats(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await mining.$getRewardStats(parseInt(req.params.blockCount))
 | 
			
		||||
      res.json(response);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).end();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Routes();
 | 
			
		||||
 | 
			
		||||
@ -78,6 +78,7 @@ import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-st
 | 
			
		||||
import { GraphsComponent } from './components/graphs/graphs.component';
 | 
			
		||||
import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components';
 | 
			
		||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
 | 
			
		||||
import { RewardStatsComponent } from './components/reward-stats/reward-stats.component';
 | 
			
		||||
import { DataCyDirective } from './data-cy.directive';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
@ -139,6 +140,7 @@ import { DataCyDirective } from './data-cy.directive';
 | 
			
		||||
    DifficultyAdjustmentsTable,
 | 
			
		||||
    BlocksList,
 | 
			
		||||
    DataCyDirective,
 | 
			
		||||
    RewardStatsComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
 | 
			
		||||
 | 
			
		||||
@ -4,64 +4,18 @@
 | 
			
		||||
 | 
			
		||||
    <!-- Temporary stuff here - Will be moved to a component once we have more useful data to show -->
 | 
			
		||||
    <div class="col">
 | 
			
		||||
      <div class="main-title">Reward stats</div>
 | 
			
		||||
      <div class="main-title">
 | 
			
		||||
        <span i18n="mining.reward-stats">Reward stats</span> 
 | 
			
		||||
        <span style="font-size: xx-small" i18n="mining.144-blocks">(144 blocks)</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="card-wrapper">
 | 
			
		||||
        <div class="card" style="height: 123px">
 | 
			
		||||
          <div class="card-body more-padding">
 | 
			
		||||
            <div class="reward-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
 | 
			
		||||
              <div class="item">
 | 
			
		||||
                <h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
 | 
			
		||||
                <div class="card-text">
 | 
			
		||||
                  <app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
 | 
			
		||||
                  <div class="symbol">in the last 8 blocks</div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="item">
 | 
			
		||||
                <h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
 | 
			
		||||
                <div class="card-text">
 | 
			
		||||
                  {{ rewardStats.rewardPerTx | amountShortener }}
 | 
			
		||||
                  <span class="symbol">sats/tx</span>
 | 
			
		||||
                  <div class="symbol">in the last 8 blocks</div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="item">
 | 
			
		||||
                <h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
 | 
			
		||||
                <div class="card-text">
 | 
			
		||||
                  {{ rewardStats.feePerTx | amountShortener}}
 | 
			
		||||
                  <span class="symbol">sats/tx</span>
 | 
			
		||||
                  <div class="symbol">in the last 8 blocks</div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <app-reward-stats></app-reward-stats>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ng-template #loadingReward>
 | 
			
		||||
      <div class="reward-container">
 | 
			
		||||
        <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
 | 
			
		||||
          <div class="card-text skeleton">
 | 
			
		||||
            <div class="skeleton-loader"></div>
 | 
			
		||||
            <div class="skeleton-loader"></div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
 | 
			
		||||
          <div class="card-text skeleton">
 | 
			
		||||
            <div class="skeleton-loader"></div>
 | 
			
		||||
            <div class="skeleton-loader"></div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="item">
 | 
			
		||||
          <h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
 | 
			
		||||
          <div class="card-text skeleton">
 | 
			
		||||
            <div class="skeleton-loader"></div>
 | 
			
		||||
            <div class="skeleton-loader"></div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
 | 
			
		||||
    <!-- difficulty adjustment -->
 | 
			
		||||
    <div class="col">
 | 
			
		||||
 | 
			
		||||
@ -59,42 +59,6 @@
 | 
			
		||||
  padding-bottom: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reward-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  height: 76px;
 | 
			
		||||
  .shared-block {
 | 
			
		||||
    color: #ffffff66;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
  }
 | 
			
		||||
  .item {
 | 
			
		||||
    display: table-cell;
 | 
			
		||||
    padding: 0 5px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    &:nth-child(1) {
 | 
			
		||||
      display: none;
 | 
			
		||||
      @media (min-width: 485px) {
 | 
			
		||||
        display: table-cell;
 | 
			
		||||
      }
 | 
			
		||||
      @media (min-width: 768px) {
 | 
			
		||||
        display: none;
 | 
			
		||||
      }
 | 
			
		||||
      @media (min-width: 992px) {
 | 
			
		||||
        display: table-cell;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .card-text {
 | 
			
		||||
    font-size: 22px;
 | 
			
		||||
    margin-top: -9px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
  .card-text.skeleton {
 | 
			
		||||
    margin-top: 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.more-padding {
 | 
			
		||||
  padding: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,14 +14,8 @@ import { WebsocketService } from 'src/app/services/websocket.service';
 | 
			
		||||
export class MiningDashboardComponent implements OnInit {
 | 
			
		||||
  private blocks = [];
 | 
			
		||||
 | 
			
		||||
  public $rewardStats: Observable<any>;
 | 
			
		||||
  public totalReward = 0;
 | 
			
		||||
  public rewardPerTx = '~';
 | 
			
		||||
  public feePerTx = '~';
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    public stateService: StateService,
 | 
			
		||||
    private websocketService: WebsocketService,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.seoService.setTitle($localize`:@@mining.mining-dashboard:Mining Dashboard`);
 | 
			
		||||
@ -29,21 +23,5 @@ export class MiningDashboardComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.websocketService.want(['blocks', 'mempool-blocks']);
 | 
			
		||||
 | 
			
		||||
    this.$rewardStats = this.stateService.blocks$.pipe(
 | 
			
		||||
      map(([block]) => {
 | 
			
		||||
        this.blocks.unshift(block);
 | 
			
		||||
        this.blocks = this.blocks.slice(0, 8);
 | 
			
		||||
        const totalTx = this.blocks.reduce((acc, b) => acc + b.tx_count, 0);
 | 
			
		||||
        const totalFee = this.blocks.reduce((acc, b) => acc + b.extras?.totalFees ?? 0, 0);
 | 
			
		||||
        const totalReward = this.blocks.reduce((acc, b) => acc + b.extras?.reward ?? 0, 0);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          'totalReward': totalReward,
 | 
			
		||||
          'rewardPerTx': Math.round(totalReward / totalTx),
 | 
			
		||||
          'feePerTx': Math.round(totalFee / totalTx),
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,119 @@
 | 
			
		||||
<div class="fee-estimation-wrapper" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
 | 
			
		||||
  <div class="fee-estimation-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
 | 
			
		||||
      <div class="card-text" i18n-ngbTooltip="Transaction fee tooltip"
 | 
			
		||||
        ngbTooltip="Amount being paid to miners in the past 144 blocks" placement="bottom">
 | 
			
		||||
        <div class="fee-text">
 | 
			
		||||
          <app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
 | 
			
		||||
        </div>
 | 
			
		||||
        <span class="fiat">
 | 
			
		||||
          <app-fiat [value]="rewardStats.totalReward"></app-fiat>
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
 | 
			
		||||
      <div class="card-text" i18n-ngbTooltip="Transaction fee tooltip"
 | 
			
		||||
        ngbTooltip="Average miners' reward per transaction in the past 144 blocks" placement="bottom">
 | 
			
		||||
        <div class="fee-text">
 | 
			
		||||
          {{ rewardStats.rewardPerTx | amountShortener }}
 | 
			
		||||
          <span i18n="shared.sat-vbyte|sat/vB">sats/tx</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <span class="fiat">
 | 
			
		||||
          <app-fiat [value]="rewardStats.rewardPerTx"></app-fiat>
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
 | 
			
		||||
      <div class="card-text" i18n-ngbTooltip="Transaction fee tooltip"
 | 
			
		||||
        ngbTooltip="Fee paid on average for each transaction in the past 144 blocks" placement="bottom">
 | 
			
		||||
        <div class="fee-text">{{ rewardStats.feePerTx | amountShortener }}
 | 
			
		||||
          <span i18n="shared.sat-vbyte|sat/vB">sats/tx</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <span class="fiat">
 | 
			
		||||
          <app-fiat [value]="rewardStats.feePerTx"></app-fiat>
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingReward>
 | 
			
		||||
  <div class="fee-estimation-container loading-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="fees-box.low-priority">Low priority</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="fees-box.medium-priority">Medium priority</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="fees-box.high-priority">High priority</h5>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<!-- <div class="reward-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
 | 
			
		||||
  <div class="item">
 | 
			
		||||
    <h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
 | 
			
		||||
    <div class="card-text">
 | 
			
		||||
      <app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
 | 
			
		||||
      <div class="symbol" i18n="rewardStats.totalReward-desc">were rewarded to miners</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="item">
 | 
			
		||||
    <h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
 | 
			
		||||
    <div class="card-text">
 | 
			
		||||
      {{ rewardStats.rewardPerTx | amountShortener }}
 | 
			
		||||
      <span class="symbol" i18n="mining.sats-per-tx">sats/tx</span>
 | 
			
		||||
      <div class="symbol" i18n="mining.rewards-per-tx-desc">miners reward / tx count</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="item">
 | 
			
		||||
    <h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
 | 
			
		||||
    <div class="card-text">
 | 
			
		||||
      {{ rewardStats.feePerTx | amountShortener}}
 | 
			
		||||
      <span class="symbol">sats/tx</span>
 | 
			
		||||
      <div class="symbol" i18n="mining.average-fee-desc">were paid per tx</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingReward>
 | 
			
		||||
  <div class="reward-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
 | 
			
		||||
      <div class="card-text skeleton">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
 | 
			
		||||
      <div class="card-text skeleton">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
 | 
			
		||||
      <div class="card-text skeleton">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template> -->
 | 
			
		||||
@ -0,0 +1,85 @@
 | 
			
		||||
.card-title {
 | 
			
		||||
  color: #4a68b9;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  margin-bottom: 4px;  
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card-text {
 | 
			
		||||
  font-size: 22px;
 | 
			
		||||
  span {
 | 
			
		||||
    font-size: 11px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    top: -2px;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
  }
 | 
			
		||||
  .green-color {
 | 
			
		||||
    display: block;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fee-estimation-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  @media (min-width: 376px) {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }  
 | 
			
		||||
  .item {
 | 
			
		||||
    max-width: 150px;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    width: -webkit-fill-available;
 | 
			
		||||
    @media (min-width: 376px) {
 | 
			
		||||
      margin: 0 auto 0px;
 | 
			
		||||
    }    
 | 
			
		||||
    &:first-child{
 | 
			
		||||
      display: none;
 | 
			
		||||
      @media (min-width: 485px) {
 | 
			
		||||
        display: block;
 | 
			
		||||
      }    
 | 
			
		||||
      @media (min-width: 768px) {
 | 
			
		||||
        display: none;
 | 
			
		||||
      }    
 | 
			
		||||
      @media (min-width: 992px) {
 | 
			
		||||
        display: block;
 | 
			
		||||
      }    
 | 
			
		||||
    }
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      margin-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
    .card-text span {
 | 
			
		||||
      color: #ffffff66;
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      top: 0px;
 | 
			
		||||
    }
 | 
			
		||||
    .fee-text{
 | 
			
		||||
      border-bottom: 1px solid #ffffff1c;
 | 
			
		||||
      width: fit-content;
 | 
			
		||||
      margin: auto;
 | 
			
		||||
      line-height: 1.45;
 | 
			
		||||
      padding: 0px 2px;
 | 
			
		||||
    }
 | 
			
		||||
    .fiat {
 | 
			
		||||
      display: block;
 | 
			
		||||
      font-size: 14px !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading-container{
 | 
			
		||||
  min-height: 76px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card-text {
 | 
			
		||||
  .skeleton-loader {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    display: block;
 | 
			
		||||
    &:first-child {
 | 
			
		||||
      max-width: 90px;
 | 
			
		||||
      margin: 15px auto 3px;
 | 
			
		||||
    }
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      margin: 10px auto 3px;
 | 
			
		||||
      max-width: 55px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map, skip, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-reward-stats',
 | 
			
		||||
  templateUrl: './reward-stats.component.html',
 | 
			
		||||
  styleUrls: ['./reward-stats.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class RewardStatsComponent implements OnInit {
 | 
			
		||||
  public $rewardStats: Observable<any>;
 | 
			
		||||
 | 
			
		||||
  constructor(private apiService: ApiService, private stateService: StateService) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.$rewardStats = this.stateService.blocks$
 | 
			
		||||
      .pipe(
 | 
			
		||||
        // (we always receives some blocks at start so only trigger for the last one)
 | 
			
		||||
        skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1),
 | 
			
		||||
        switchMap(() => {
 | 
			
		||||
          return this.apiService.getRewardStats$()
 | 
			
		||||
            .pipe(
 | 
			
		||||
              map((stats) => {
 | 
			
		||||
                return {
 | 
			
		||||
                  totalReward: stats.totalReward,
 | 
			
		||||
                  rewardPerTx: stats.totalReward / stats.totalTx,
 | 
			
		||||
                  feePerTx: stats.totalFee / stats.totalTx,
 | 
			
		||||
                };
 | 
			
		||||
              })
 | 
			
		||||
            );
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -115,3 +115,9 @@ export interface BlockExtension {
 | 
			
		||||
export interface BlockExtended extends Block {
 | 
			
		||||
  extras?: BlockExtension;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RewardStats {
 | 
			
		||||
  totalReward: number;
 | 
			
		||||
  totalFee: number;
 | 
			
		||||
  totalTx: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { HttpClient, HttpParams } from '@angular/common/http';
 | 
			
		||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolsStats, PoolStat, BlockExtended } from '../interfaces/node-api.interface';
 | 
			
		||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolsStats, PoolStat, BlockExtended, RewardStats } from '../interfaces/node-api.interface';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { StateService } from './state.service';
 | 
			
		||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
 | 
			
		||||
@ -174,4 +174,8 @@ export class ApiService {
 | 
			
		||||
        (interval !== undefined ? `/${interval}` : '')
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getRewardStats$(blockCount: number = 144): Observable<RewardStats> {
 | 
			
		||||
    return this.httpClient.get<RewardStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ do for url in / \
 | 
			
		||||
	'/api/v1/mining/hashrate/pools/2y' \
 | 
			
		||||
	'/api/v1/mining/hashrate/pools/3y' \
 | 
			
		||||
	'/api/v1/mining/hashrate/pools/all' \
 | 
			
		||||
	'/api/v1/mining/reward-stats/144' \
 | 
			
		||||
 | 
			
		||||
	do
 | 
			
		||||
		curl -s "https://${hostname}${url}" >/dev/null
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user