mirror of
https://github.com/mempool/mempool.git
synced 2025-01-01 03:04:27 +01:00
Merge pull request #1427 from mempool/nymkappa/feature/reward-stats-api
More dynamic mining reward
This commit is contained in:
commit
aaa8945b09
@ -1,4 +1,4 @@
|
|||||||
import { PoolInfo, PoolStats } from '../mempool.interfaces';
|
import { PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
|
||||||
import BlocksRepository from '../repositories/BlocksRepository';
|
import BlocksRepository from '../repositories/BlocksRepository';
|
||||||
import PoolsRepository from '../repositories/PoolsRepository';
|
import PoolsRepository from '../repositories/PoolsRepository';
|
||||||
import HashratesRepository from '../repositories/HashratesRepository';
|
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
|
* [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/pools/:interval', routes.$getPoolsHistoricalHashrate)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate', routes.$getHistoricalHashrate)
|
.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/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;
|
timeAvg: number;
|
||||||
timeOffset: 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> {
|
public async $getOldestIndexedBlockHeight(): Promise<number> {
|
||||||
const connection = await DB.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
@ -367,6 +370,29 @@ class BlocksRepository {
|
|||||||
throw e;
|
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();
|
export default new BlocksRepository();
|
||||||
|
@ -935,6 +935,15 @@ class Routes {
|
|||||||
res.status(500).end();
|
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();
|
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 { GraphsComponent } from './components/graphs/graphs.component';
|
||||||
import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
||||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||||
|
import { RewardStatsComponent } from './components/reward-stats/reward-stats.component';
|
||||||
import { DataCyDirective } from './data-cy.directive';
|
import { DataCyDirective } from './data-cy.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -139,6 +140,7 @@ import { DataCyDirective } from './data-cy.directive';
|
|||||||
DifficultyAdjustmentsTable,
|
DifficultyAdjustmentsTable,
|
||||||
BlocksList,
|
BlocksList,
|
||||||
DataCyDirective,
|
DataCyDirective,
|
||||||
|
RewardStatsComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
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 -->
|
<!-- Temporary stuff here - Will be moved to a component once we have more useful data to show -->
|
||||||
<div class="col">
|
<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-wrapper">
|
||||||
<div class="card" style="height: 123px">
|
<div class="card" style="height: 123px">
|
||||||
<div class="card-body more-padding">
|
<div class="card-body more-padding">
|
||||||
<div class="reward-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
|
<app-reward-stats></app-reward-stats>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- difficulty adjustment -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -59,42 +59,6 @@
|
|||||||
padding-bottom: 3px;
|
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 {
|
.more-padding {
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,8 @@ import { WebsocketService } from 'src/app/services/websocket.service';
|
|||||||
export class MiningDashboardComponent implements OnInit {
|
export class MiningDashboardComponent implements OnInit {
|
||||||
private blocks = [];
|
private blocks = [];
|
||||||
|
|
||||||
public $rewardStats: Observable<any>;
|
|
||||||
public totalReward = 0;
|
|
||||||
public rewardPerTx = '~';
|
|
||||||
public feePerTx = '~';
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
public stateService: StateService,
|
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
) {
|
) {
|
||||||
this.seoService.setTitle($localize`:@@mining.mining-dashboard:Mining Dashboard`);
|
this.seoService.setTitle($localize`:@@mining.mining-dashboard:Mining Dashboard`);
|
||||||
@ -29,21 +23,5 @@ export class MiningDashboardComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
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 {
|
export interface BlockExtended extends Block {
|
||||||
extras?: BlockExtension;
|
extras?: BlockExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RewardStats {
|
||||||
|
totalReward: number;
|
||||||
|
totalFee: number;
|
||||||
|
totalTx: number;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
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 { Observable } from 'rxjs';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||||
@ -174,4 +174,8 @@ export class ApiService {
|
|||||||
(interval !== undefined ? `/${interval}` : '')
|
(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/2y' \
|
||||||
'/api/v1/mining/hashrate/pools/3y' \
|
'/api/v1/mining/hashrate/pools/3y' \
|
||||||
'/api/v1/mining/hashrate/pools/all' \
|
'/api/v1/mining/hashrate/pools/all' \
|
||||||
|
'/api/v1/mining/reward-stats/144' \
|
||||||
|
|
||||||
do
|
do
|
||||||
curl -s "https://${hostname}${url}" >/dev/null
|
curl -s "https://${hostname}${url}" >/dev/null
|
||||||
|
Loading…
Reference in New Issue
Block a user