Merge branch 'master' into mononaut/mobile-chain-jumping
This commit is contained in:
commit
0147af1325
@ -27,7 +27,7 @@
|
|||||||
"package": "npm run build && rm -rf package && mv dist package && mv node_modules package && npm run package-rm-build-deps",
|
"package": "npm run build && rm -rf package && mv dist package && mv node_modules package && npm run package-rm-build-deps",
|
||||||
"package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint)",
|
"package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint)",
|
||||||
"start": "node --max-old-space-size=2048 dist/index.js",
|
"start": "node --max-old-space-size=2048 dist/index.js",
|
||||||
"start-production": "node --max-old-space-size=4096 dist/index.js",
|
"start-production": "node --max-old-space-size=16384 dist/index.js",
|
||||||
"test": "./node_modules/.bin/jest --coverage",
|
"test": "./node_modules/.bin/jest --coverage",
|
||||||
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
||||||
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 52;
|
private static currentVersion = 53;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -468,6 +468,11 @@ class DatabaseMigration {
|
|||||||
logger.warn('' + (e instanceof Error ? e.message : e));
|
logger.warn('' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 53) {
|
||||||
|
await this.$executeQuery('ALTER TABLE statistics MODIFY mempool_byte_weight bigint(20) UNSIGNED NOT NULL');
|
||||||
|
await this.updateToSchemaVersion(53);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -362,7 +362,13 @@ class NodesApi {
|
|||||||
public async $searchNodeByPublicKeyOrAlias(search: string) {
|
public async $searchNodeByPublicKeyOrAlias(search: string) {
|
||||||
try {
|
try {
|
||||||
const publicKeySearch = search.replace('%', '') + '%';
|
const publicKeySearch = search.replace('%', '') + '%';
|
||||||
const aliasSearch = search.replace(/[-_.]/g, ' ').replace(/[^a-zA-Z0-9 ]/g, '').split(' ').map((search) => '+' + search + '*').join(' ');
|
const aliasSearch = search
|
||||||
|
.replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash".
|
||||||
|
.replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9.
|
||||||
|
.split(' ')
|
||||||
|
.filter(key => key.length)
|
||||||
|
.map((search) => '+' + search + '*').join(' ');
|
||||||
|
// %keyword% is wildcard search and can't be indexed so it's slower as the node database grow. keyword% can be indexed but then you can't search for "Nicehash" and get result for ln.nicehash.com. So we use fulltext index for words "ln, nicehash, com" and nicehash* will find it instantly.
|
||||||
const query = `SELECT public_key, alias, capacity, channels, status FROM nodes WHERE public_key LIKE ? OR MATCH alias_search AGAINST (? IN BOOLEAN MODE) ORDER BY capacity DESC LIMIT 10`;
|
const query = `SELECT public_key, alias, capacity, channels, status FROM nodes WHERE public_key LIKE ? OR MATCH alias_search AGAINST (? IN BOOLEAN MODE) ORDER BY capacity DESC LIMIT 10`;
|
||||||
const [rows]: any = await DB.query(query, [publicKeySearch, aliasSearch]);
|
const [rows]: any = await DB.query(query, [publicKeySearch, aliasSearch]);
|
||||||
return rows;
|
return rows;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Application, Request, Response } from 'express';
|
import { Application, Request, Response } from 'express';
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import audits from '../audit';
|
|
||||||
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
|
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
|
||||||
import BlocksRepository from '../../repositories/BlocksRepository';
|
import BlocksRepository from '../../repositories/BlocksRepository';
|
||||||
import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
|
import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
|
||||||
import HashratesRepository from '../../repositories/HashratesRepository';
|
import HashratesRepository from '../../repositories/HashratesRepository';
|
||||||
import bitcoinClient from '../bitcoin/bitcoin-client';
|
import bitcoinClient from '../bitcoin/bitcoin-client';
|
||||||
import mining from "./mining";
|
import mining from "./mining";
|
||||||
|
import PricesRepository from '../../repositories/PricesRepository';
|
||||||
|
|
||||||
class MiningRoutes {
|
class MiningRoutes {
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
@ -32,9 +32,18 @@ class MiningRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'historical-price', this.$getHistoricalPrice)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $getHistoricalPrice(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
res.status(200).send(await PricesRepository.$getHistoricalPrice());
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async $getPool(req: Request, res: Response): Promise<void> {
|
private async $getPool(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const stats = await mining.$getPoolStat(req.params.slug);
|
const stats = await mining.$getPoolStat(req.params.slug);
|
||||||
|
@ -100,6 +100,7 @@ class Mining {
|
|||||||
rank: rank++,
|
rank: rank++,
|
||||||
emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0,
|
emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0,
|
||||||
slug: poolInfo.slug,
|
slug: poolInfo.slug,
|
||||||
|
avgMatchRate: poolInfo.avgMatchRate !== null ? Math.round(100 * poolInfo.avgMatchRate) / 100 : null,
|
||||||
};
|
};
|
||||||
poolsStats.push(poolStat);
|
poolsStats.push(poolStat);
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ export interface PoolInfo {
|
|||||||
link: string;
|
link: string;
|
||||||
blockCount: number;
|
blockCount: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
avgMatchRate: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PoolStats extends PoolInfo {
|
export interface PoolStats extends PoolInfo {
|
||||||
|
@ -521,7 +521,7 @@ class BlocksRepository {
|
|||||||
CAST(AVG(blocks.height) as INT) as avgHeight,
|
CAST(AVG(blocks.height) as INT) as avgHeight,
|
||||||
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
||||||
CAST(AVG(fees) as INT) as avgFees,
|
CAST(AVG(fees) as INT) as avgFees,
|
||||||
prices.*
|
prices.USD
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN blocks_prices on blocks_prices.height = blocks.height
|
JOIN blocks_prices on blocks_prices.height = blocks.height
|
||||||
JOIN prices on prices.id = blocks_prices.price_id
|
JOIN prices on prices.id = blocks_prices.price_id
|
||||||
@ -550,7 +550,7 @@ class BlocksRepository {
|
|||||||
CAST(AVG(blocks.height) as INT) as avgHeight,
|
CAST(AVG(blocks.height) as INT) as avgHeight,
|
||||||
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
|
||||||
CAST(AVG(reward) as INT) as avgRewards,
|
CAST(AVG(reward) as INT) as avgRewards,
|
||||||
prices.*
|
prices.USD
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN blocks_prices on blocks_prices.height = blocks.height
|
JOIN blocks_prices on blocks_prices.height = blocks.height
|
||||||
JOIN prices on prices.id = blocks_prices.price_id
|
JOIN prices on prices.id = blocks_prices.price_id
|
||||||
|
@ -27,16 +27,25 @@ class PoolsRepository {
|
|||||||
public async $getPoolsInfo(interval: string | null = null): Promise<PoolInfo[]> {
|
public async $getPoolsInfo(interval: string | null = null): Promise<PoolInfo[]> {
|
||||||
interval = Common.getSqlInterval(interval);
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
let query = `SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link, slug
|
let query = `
|
||||||
|
SELECT
|
||||||
|
COUNT(blocks.height) As blockCount,
|
||||||
|
pool_id AS poolId,
|
||||||
|
pools.name AS name,
|
||||||
|
pools.link AS link,
|
||||||
|
slug,
|
||||||
|
AVG(blocks_audits.match_rate) AS avgMatchRate
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools on pools.id = pool_id`;
|
JOIN pools on pools.id = pool_id
|
||||||
|
LEFT JOIN blocks_audits ON blocks_audits.height = blocks.height
|
||||||
|
`;
|
||||||
|
|
||||||
if (interval) {
|
if (interval) {
|
||||||
query += ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
query += ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` GROUP BY pool_id
|
query += ` GROUP BY pool_id
|
||||||
ORDER BY COUNT(height) DESC`;
|
ORDER BY COUNT(blocks.height) DESC`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows] = await DB.query(query);
|
const [rows] = await DB.query(query);
|
||||||
|
@ -3,6 +3,31 @@ import logger from '../logger';
|
|||||||
import { IConversionRates } from '../mempool.interfaces';
|
import { IConversionRates } from '../mempool.interfaces';
|
||||||
import priceUpdater from '../tasks/price-updater';
|
import priceUpdater from '../tasks/price-updater';
|
||||||
|
|
||||||
|
export interface ApiPrice {
|
||||||
|
time?: number,
|
||||||
|
USD: number,
|
||||||
|
EUR: number,
|
||||||
|
GBP: number,
|
||||||
|
CAD: number,
|
||||||
|
CHF: number,
|
||||||
|
AUD: number,
|
||||||
|
JPY: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExchangeRates {
|
||||||
|
USDEUR: number,
|
||||||
|
USDGBP: number,
|
||||||
|
USDCAD: number,
|
||||||
|
USDCHF: number,
|
||||||
|
USDAUD: number,
|
||||||
|
USDJPY: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Conversion {
|
||||||
|
prices: ApiPrice[],
|
||||||
|
exchangeRates: ExchangeRates;
|
||||||
|
}
|
||||||
|
|
||||||
class PricesRepository {
|
class PricesRepository {
|
||||||
public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
|
public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
|
||||||
if (prices.USD === 0) {
|
if (prices.USD === 0) {
|
||||||
@ -60,6 +85,34 @@ class PricesRepository {
|
|||||||
}
|
}
|
||||||
return rates[0];
|
return rates[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getHistoricalPrice(): Promise<Conversion | null> {
|
||||||
|
try {
|
||||||
|
const [rates]: any[] = await DB.query(`SELECT *, UNIX_TIMESTAMP(time) as time FROM prices ORDER BY time DESC`);
|
||||||
|
if (!rates) {
|
||||||
|
throw Error(`Cannot get average historical price from the database`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fiat exchange rates
|
||||||
|
const latestPrice: ApiPrice = rates[0];
|
||||||
|
const exchangeRates: ExchangeRates = {
|
||||||
|
USDEUR: Math.round(latestPrice.EUR / latestPrice.USD * 100) / 100,
|
||||||
|
USDGBP: Math.round(latestPrice.GBP / latestPrice.USD * 100) / 100,
|
||||||
|
USDCAD: Math.round(latestPrice.CAD / latestPrice.USD * 100) / 100,
|
||||||
|
USDCHF: Math.round(latestPrice.CHF / latestPrice.USD * 100) / 100,
|
||||||
|
USDAUD: Math.round(latestPrice.AUD / latestPrice.USD * 100) / 100,
|
||||||
|
USDJPY: Math.round(latestPrice.JPY / latestPrice.USD * 100) / 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
prices: rates,
|
||||||
|
exchangeRates: exchangeRates
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot fetch averaged historical prices from the db. Reason ${e instanceof Error ? e.message : e}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new PricesRepository();
|
export default new PricesRepository();
|
||||||
|
@ -72,7 +72,7 @@ export const chartColors = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const poolsColor = {
|
export const poolsColor = {
|
||||||
'unknown': '#9C9C9C',
|
'unknown': '#FDD835',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
||||||
|
@ -7,6 +7,7 @@ import { AppComponent } from './components/app/app.component';
|
|||||||
import { ElectrsApiService } from './services/electrs-api.service';
|
import { ElectrsApiService } from './services/electrs-api.service';
|
||||||
import { StateService } from './services/state.service';
|
import { StateService } from './services/state.service';
|
||||||
import { CacheService } from './services/cache.service';
|
import { CacheService } from './services/cache.service';
|
||||||
|
import { PriceService } from './services/price.service';
|
||||||
import { EnterpriseService } from './services/enterprise.service';
|
import { EnterpriseService } from './services/enterprise.service';
|
||||||
import { WebsocketService } from './services/websocket.service';
|
import { WebsocketService } from './services/websocket.service';
|
||||||
import { AudioService } from './services/audio.service';
|
import { AudioService } from './services/audio.service';
|
||||||
@ -26,6 +27,7 @@ const providers = [
|
|||||||
ElectrsApiService,
|
ElectrsApiService,
|
||||||
StateService,
|
StateService,
|
||||||
CacheService,
|
CacheService,
|
||||||
|
PriceService,
|
||||||
WebsocketService,
|
WebsocketService,
|
||||||
AudioService,
|
AudioService,
|
||||||
SeoService,
|
SeoService,
|
||||||
|
@ -1,7 +1,19 @@
|
|||||||
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
||||||
<span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ (conversions ? conversions[currency] : 0) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
|
<span class="fiat" *ngIf="blockConversion; else noblockconversion">
|
||||||
|
{{ addPlus && satoshis >= 0 ? '+' : '' }}
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(blockConversion.price[currency] > 0 ? blockConversion.price[currency] : null) ??
|
||||||
|
(blockConversion.price['USD'] * blockConversion.exchangeRates['USD' + currency]) ?? 0
|
||||||
|
) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<ng-template #noblockconversion>
|
||||||
|
<span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ (conversions ? conversions[currency] : 0) * satoshis / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #viewFiatVin>
|
|
||||||
|
<ng-template #viewFiatVin>
|
||||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
||||||
<span i18n="shared.confidential">Confidential</span>
|
<span i18n="shared.confidential">Confidential</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
import { Price } from 'src/app/services/price.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-amount',
|
selector: 'app-amount',
|
||||||
@ -21,6 +22,7 @@ export class AmountComponent implements OnInit, OnDestroy {
|
|||||||
@Input() digitsInfo = '1.8-8';
|
@Input() digitsInfo = '1.8-8';
|
||||||
@Input() noFiat = false;
|
@Input() noFiat = false;
|
||||||
@Input() addPlus = false;
|
@Input() addPlus = false;
|
||||||
|
@Input() blockConversion: Price;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" formControlName="dateSpan"> 24h
|
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||||
</label>
|
</label>
|
||||||
|
@ -78,3 +78,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]" formControlName="dateSpan"> 1M
|
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||||
</label>
|
</label>
|
||||||
|
@ -78,3 +78,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -1,19 +1,17 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
import { EChartsOption, graphic } from 'echarts';
|
import { EChartsOption, graphic } from 'echarts';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
|
import { download, formatterXAxis } from '../../shared/graphs.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { MiningService } from '../../services/mining.service';
|
import { MiningService } from '../../services/mining.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
||||||
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
||||||
import { fiatCurrencies } from '../../app.constants';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-fees-graph',
|
selector: 'app-block-fees-graph',
|
||||||
@ -47,7 +45,6 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
timespan = '';
|
timespan = '';
|
||||||
chartInstance: any = undefined;
|
chartInstance: any = undefined;
|
||||||
|
|
||||||
currencySubscription: Subscription;
|
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -57,21 +54,13 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private stateService: StateService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private fiatShortenerPipe: FiatShortenerPipe,
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
private fiatCurrencyPipe: FiatCurrencyPipe,
|
private fiatCurrencyPipe: FiatCurrencyPipe,
|
||||||
) {
|
) {
|
||||||
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');
|
||||||
|
this.currency = 'USD';
|
||||||
this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => {
|
|
||||||
if (fiat && fiatCurrencies[fiat]?.indexed) {
|
|
||||||
this.currency = fiat;
|
|
||||||
} else {
|
|
||||||
this.currency = 'USD';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -92,6 +81,7 @@ export class BlockFeesGraphComponent implements OnInit {
|
|||||||
.pipe(
|
.pipe(
|
||||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||||
switchMap((timespan) => {
|
switchMap((timespan) => {
|
||||||
|
this.isLoading = true;
|
||||||
this.storageService.setValue('miningWindowPreference', timespan);
|
this.storageService.setValue('miningWindowPreference', timespan);
|
||||||
this.timespan = timespan;
|
this.timespan = timespan;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 24h
|
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||||
</label>
|
</label>
|
||||||
|
@ -78,3 +78,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 1M
|
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||||
</label>
|
</label>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 3Y
|
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
|
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
|
||||||
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"> ALL
|
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" formControlName="dateSpan"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -78,3 +78,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -1,19 +1,17 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
import { EChartsOption, graphic } from 'echarts';
|
import { EChartsOption, graphic } from 'echarts';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from '../../shared/graphs.utils';
|
import { download, formatterXAxis } from '../../shared/graphs.utils';
|
||||||
import { MiningService } from '../../services/mining.service';
|
import { MiningService } from '../../services/mining.service';
|
||||||
import { StateService } from '../../services/state.service';
|
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
import { FiatShortenerPipe } from '../../shared/pipes/fiat-shortener.pipe';
|
||||||
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
import { FiatCurrencyPipe } from '../../shared/pipes/fiat-currency.pipe';
|
||||||
import { fiatCurrencies } from '../../app.constants';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-rewards-graph',
|
selector: 'app-block-rewards-graph',
|
||||||
@ -47,7 +45,6 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
timespan = '';
|
timespan = '';
|
||||||
chartInstance: any = undefined;
|
chartInstance: any = undefined;
|
||||||
|
|
||||||
currencySubscription: Subscription;
|
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -56,19 +53,12 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private stateService: StateService,
|
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private fiatShortenerPipe: FiatShortenerPipe,
|
private fiatShortenerPipe: FiatShortenerPipe,
|
||||||
private fiatCurrencyPipe: FiatCurrencyPipe,
|
private fiatCurrencyPipe: FiatCurrencyPipe,
|
||||||
) {
|
) {
|
||||||
this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => {
|
this.currency = 'USD';
|
||||||
if (fiat && fiatCurrencies[fiat]?.indexed) {
|
|
||||||
this.currency = fiat;
|
|
||||||
} else {
|
|
||||||
this.currency = 'USD';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -80,7 +70,7 @@ export class BlockRewardsGraphComponent implements OnInit {
|
|||||||
this.route
|
this.route
|
||||||
.fragment
|
.fragment
|
||||||
.subscribe((fragment) => {
|
.subscribe((fragment) => {
|
||||||
if (['3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
|
if (['1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(blockSizesWeightsObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(blockSizesWeightsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" formControlName="dateSpan"> 24h
|
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||||
</label>
|
</label>
|
||||||
|
@ -78,3 +78,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -124,7 +124,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-fiat [blockConversion]="blockConversion" [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2"
|
||||||
|
i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes"
|
||||||
|
placement="bottom"></app-fiat>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||||
<tr>
|
<tr>
|
||||||
@ -132,13 +138,13 @@
|
|||||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||||
<span class="fiat">
|
<span class="fiat">
|
||||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
<app-fiat [blockConversion]="blockConversion" [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<ng-template #liquidTotalFees>
|
<ng-template #liquidTotalFees>
|
||||||
<td>
|
<td>
|
||||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
[blockConversion]="blockConversion" [value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||||
</td>
|
</td>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</tr>
|
</tr>
|
||||||
@ -147,7 +153,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||||
<span class="fiat">
|
<span class="fiat">
|
||||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
<app-fiat [blockConversion]="blockConversion" [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -13,6 +13,7 @@ import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces
|
|||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||||
import { detectWebGL } from '../../shared/graphs.utils';
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
|
import { PriceService, Price } from 'src/app/services/price.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block',
|
selector: 'app-block',
|
||||||
@ -81,6 +82,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
timeLtr: boolean;
|
timeLtr: boolean;
|
||||||
childChangeSubscription: Subscription;
|
childChangeSubscription: Subscription;
|
||||||
auditPrefSubscription: Subscription;
|
auditPrefSubscription: Subscription;
|
||||||
|
|
||||||
|
priceSubscription: Subscription;
|
||||||
|
blockConversion: Price;
|
||||||
|
|
||||||
@ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
|
@ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
|
||||||
@ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
|
@ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
|
||||||
@ -94,7 +98,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private apiService: ApiService
|
private apiService: ApiService,
|
||||||
|
private priceService: PriceService,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = detectWebGL();
|
this.webGlEnabled = detectWebGL();
|
||||||
}
|
}
|
||||||
@ -432,6 +437,19 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.priceSubscription) {
|
||||||
|
this.priceSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
this.priceSubscription = block$.pipe(
|
||||||
|
switchMap((block) => {
|
||||||
|
return this.priceService.getPrices().pipe(
|
||||||
|
tap(() => {
|
||||||
|
this.blockConversion = this.priceService.getPriceForTimestamp(block.timestamp);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
|
@ -14,20 +14,26 @@
|
|||||||
}}</a>
|
}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="block-body">
|
<div class="block-body">
|
||||||
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
|
<div *ngIf="block?.extras; else emptyfees" [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
|
||||||
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
|
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
|
||||||
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
<ng-template #emptyfees>
|
||||||
|
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
|
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
|
||||||
*ngIf="block?.extras?.feeRange">
|
*ngIf="block?.extras?.feeRange; else emptyfeespan">
|
||||||
{{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{
|
{{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{
|
||||||
block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container
|
block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container
|
||||||
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
|
<ng-template #emptyfeespan>
|
||||||
*ngIf="!block?.extras?.feeRange">
|
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fee-span">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</ng-template>
|
||||||
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
|
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
|
||||||
class="block-size">
|
class="block-size">
|
||||||
<app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
<app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
||||||
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" [attr.data-cy]="'3m'" formControlName="dateSpan"> 3M
|
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" [attr.data-cy]="'3m'" formControlName="dateSpan"> 3M
|
||||||
</label>
|
</label>
|
||||||
|
@ -131,4 +131,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
max-width: 80px;
|
max-width: 80px;
|
||||||
margin: 15px auto 3px;
|
margin: 15px auto 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(hashrateObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
|
||||||
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]" [attr.data-cy]="'6m'" formControlName="dateSpan"> 6M
|
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]" [attr.data-cy]="'6m'" formControlName="dateSpan"> 6M
|
||||||
</label>
|
</label>
|
||||||
|
@ -82,3 +82,8 @@
|
|||||||
.loadingGraphs.widget {
|
.loadingGraphs.widget {
|
||||||
top: 75%;
|
top: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -5,7 +5,7 @@ import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/op
|
|||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { poolsColor } from '../../app.constants';
|
import { chartColors, poolsColor } from '../../app.constants';
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { MiningService } from '../../services/mining.service';
|
import { MiningService } from '../../services/mining.service';
|
||||||
import { download } from '../../shared/graphs.utils';
|
import { download } from '../../shared/graphs.utils';
|
||||||
@ -173,6 +173,7 @@ export class HashrateChartPoolsComponent implements OnInit {
|
|||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
title: title,
|
title: title,
|
||||||
animation: false,
|
animation: false,
|
||||||
|
color: chartColors.filter(color => color !== '#FDD835'),
|
||||||
grid: {
|
grid: {
|
||||||
right: this.right,
|
right: this.right,
|
||||||
left: this.left,
|
left: this.left,
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup"
|
<form [formGroup]="radioGroupForm" class="formRadioGroup"
|
||||||
*ngIf="!widget && (miningStatsObservable$ | async) as stats">
|
*ngIf="!widget && (miningStatsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.totalBlockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.totalBlockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/pools' | relativeUrl]" [attr.data-cy]="'24h'" formControlName="dateSpan"> 24h
|
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/pools' | relativeUrl]" [attr.data-cy]="'24h'" formControlName="dateSpan"> 24h
|
||||||
</label>
|
</label>
|
||||||
@ -92,6 +92,8 @@
|
|||||||
<th class="" i18n="mining.pool-name">Pool</th>
|
<th class="" i18n="mining.pool-name">Pool</th>
|
||||||
<th class="" *ngIf="this.miningWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
|
<th class="" *ngIf="this.miningWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
|
||||||
<th class="" i18n="master-page.blocks">Blocks</th>
|
<th class="" i18n="master-page.blocks">Blocks</th>
|
||||||
|
<th *ngIf="auditAvailable" class="health text-right widget" i18n="latest-blocks.avg_health"
|
||||||
|
i18n-ngbTooltip="latest-blocks.avg_health" ngbTooltip="Avg Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Avg Health</th>
|
||||||
<th class="d-none d-md-block" i18n="mining.empty-blocks">Empty blocks</th>
|
<th class="d-none d-md-block" i18n="mining.empty-blocks">Empty blocks</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -102,9 +104,23 @@
|
|||||||
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
|
<img width="25" height="25" src="{{ pool.logo }}" [alt]="pool.name + ' mining pool logo'" onError="this.src = '/resources/mining-pools/default.svg'">
|
||||||
</td>
|
</td>
|
||||||
<td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
|
<td class=""><a [routerLink]="[('/mining/pool/' + pool.slug) | relativeUrl]">{{ pool.name }}</a></td>
|
||||||
<td class="" *ngIf="this.miningWindowPreference === '24h' && !isLoading">{{ pool.lastEstimatedHashrate }} {{
|
<td class="" *ngIf="this.miningWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{
|
||||||
miningStats.miningUnits.hashrateUnit }}</td>
|
miningStats.miningUnits.hashrateUnit }}</td>
|
||||||
<td class="">{{ pool['blockText'] }}</td>
|
<td class="d-flex justify-content-center">
|
||||||
|
{{ pool.blockCount }}<span class="d-none d-md-block"> ({{ pool.share }}%)</span>
|
||||||
|
</td>
|
||||||
|
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||||
|
<a
|
||||||
|
class="health-badge badge"
|
||||||
|
[class.badge-success]="pool.avgMatchRate >= 99"
|
||||||
|
[class.badge-warning]="pool.avgMatchRate >= 75 && pool.avgMatchRate < 99"
|
||||||
|
[class.badge-danger]="pool.avgMatchRate < 75"
|
||||||
|
*ngIf="pool.avgMatchRate != null; else nullHealth"
|
||||||
|
>{{ pool.avgMatchRate }}%</a>
|
||||||
|
<ng-template #nullHealth>
|
||||||
|
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="border-top: 1px solid #555">
|
<tr style="border-top: 1px solid #555">
|
||||||
|
@ -139,3 +139,8 @@
|
|||||||
max-width: 80px;
|
max-width: 80px;
|
||||||
margin: 15px auto 3px;
|
margin: 15px auto 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
@ -4,7 +4,6 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||||
import { concat, Observable } from 'rxjs';
|
import { concat, Observable } from 'rxjs';
|
||||||
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { SinglePoolStats } from '../../interfaces/node-api.interface';
|
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { StorageService } from '../..//services/storage.service';
|
import { StorageService } from '../..//services/storage.service';
|
||||||
import { MiningService, MiningStats } from '../../services/mining.service';
|
import { MiningService, MiningStats } from '../../services/mining.service';
|
||||||
@ -26,6 +25,8 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
miningWindowPreference: string;
|
miningWindowPreference: string;
|
||||||
radioGroupForm: UntypedFormGroup;
|
radioGroupForm: UntypedFormGroup;
|
||||||
|
|
||||||
|
auditAvailable = false;
|
||||||
|
indexingAvailable = false;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
chartOptions: EChartsOption = {};
|
chartOptions: EChartsOption = {};
|
||||||
chartInitOptions = {
|
chartInitOptions = {
|
||||||
@ -60,6 +61,10 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
|
|
||||||
|
this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' &&
|
||||||
|
this.stateService.env.MINING_DASHBOARD === true);
|
||||||
|
this.auditAvailable = this.indexingAvailable && this.stateService.env.AUDIT;
|
||||||
|
|
||||||
this.route
|
this.route
|
||||||
.fragment
|
.fragment
|
||||||
.subscribe((fragment) => {
|
.subscribe((fragment) => {
|
||||||
@ -73,6 +78,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
.pipe(
|
.pipe(
|
||||||
startWith(this.radioGroupForm.controls.dateSpan.value), // (trigger when the page loads)
|
startWith(this.radioGroupForm.controls.dateSpan.value), // (trigger when the page loads)
|
||||||
tap((value) => {
|
tap((value) => {
|
||||||
|
this.isLoading = true;
|
||||||
this.timespan = value;
|
this.timespan = value;
|
||||||
if (!this.widget) {
|
if (!this.widget) {
|
||||||
this.storageService.setValue('miningWindowPreference', value);
|
this.storageService.setValue('miningWindowPreference', value);
|
||||||
@ -92,7 +98,6 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(data => {
|
map(data => {
|
||||||
data.pools = data.pools.map((pool: SinglePoolStats) => this.formatPoolUI(pool));
|
|
||||||
data['minersLuck'] = (100 * (data.blockCount / 1008)).toFixed(2); // luck 1w
|
data['minersLuck'] = (100 * (data.blockCount / 1008)).toFixed(2); // luck 1w
|
||||||
return data;
|
return data;
|
||||||
}),
|
}),
|
||||||
@ -104,11 +109,6 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatPoolUI(pool: SinglePoolStats) {
|
|
||||||
pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`;
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
generatePoolsChartSerieData(miningStats) {
|
generatePoolsChartSerieData(miningStats) {
|
||||||
let poolShareThreshold = 0.5;
|
let poolShareThreshold = 0.5;
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
@ -219,7 +219,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
animation: false,
|
animation: false,
|
||||||
color: chartColors,
|
color: chartColors.filter(color => color !== '#FDD835'),
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
|
@ -61,12 +61,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="latestBlock && tx.status.block_height <= latestBlock.height - 8">
|
|
||||||
<td class="td-width" i18n="transaction.included-in-block|Transaction included in block">Included in block</td>
|
|
||||||
<td>
|
|
||||||
<a [routerLink]="['/block/' | relativeUrl, tx.status.block_hash]" [state]="{ data: { blockHeight: tx.status.block_height } }">{{ tx.status.block_height }}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<ng-template [ngIf]="transactionTime > 0">
|
<ng-template [ngIf]="transactionTime > 0">
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
|
<td i18n="transaction.confirmed|Transaction Confirmed state">Confirmed</td>
|
||||||
@ -475,7 +469,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
||||||
<td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></td>
|
<td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="blockConversion" [value]="tx.fee"></app-fiat></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||||
|
@ -8,10 +8,11 @@ import {
|
|||||||
retryWhen,
|
retryWhen,
|
||||||
delay,
|
delay,
|
||||||
map,
|
map,
|
||||||
mergeMap
|
mergeMap,
|
||||||
|
tap
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { Transaction } from '../../interfaces/electrs.interface';
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
|
import { of, merge, Subscription, Observable, Subject, timer, from, throwError } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { CacheService } from '../../services/cache.service';
|
import { CacheService } from '../../services/cache.service';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
@ -21,6 +22,7 @@ import { SeoService } from '../../services/seo.service';
|
|||||||
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
|
||||||
import { LiquidUnblinding } from './liquid-ublinding';
|
import { LiquidUnblinding } from './liquid-ublinding';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
import { Price, PriceService } from 'src/app/services/price.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction',
|
selector: 'app-transaction',
|
||||||
@ -69,7 +71,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
hideFlow: boolean = this.stateService.hideFlow.value;
|
hideFlow: boolean = this.stateService.hideFlow.value;
|
||||||
overrideFlowPreference: boolean = null;
|
overrideFlowPreference: boolean = null;
|
||||||
flowEnabled: boolean;
|
flowEnabled: boolean;
|
||||||
|
blockConversion: Price;
|
||||||
tooltipPosition: { x: number, y: number };
|
tooltipPosition: { x: number, y: number };
|
||||||
|
|
||||||
@ViewChild('graphContainer')
|
@ViewChild('graphContainer')
|
||||||
@ -85,7 +87,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private audioService: AudioService,
|
private audioService: AudioService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService
|
private seoService: SeoService,
|
||||||
|
private priceService: PriceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -323,6 +326,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.fetchRbfHistory$.next(this.tx.txid);
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.priceService.getPrices().pipe(
|
||||||
|
tap(() => {
|
||||||
|
this.blockConversion = this.priceService.getPriceForTimestamp(tx.status.block_time);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
setTimeout(() => { this.applyFragment(); }, 0);
|
setTimeout(() => { this.applyFragment(); }, 0);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #defaultOutput>
|
<ng-template #defaultOutput>
|
||||||
<span *ngIf="vin.lazy" class="skeleton-loader"></span>
|
<span *ngIf="vin.lazy" class="skeleton-loader"></span>
|
||||||
<app-amount *ngIf="vin.prevout" [satoshis]="vin.prevout.value"></app-amount>
|
<app-amount [blockConversion]="tx.price" *ngIf="vin.prevout" [satoshis]="vin.prevout.value"></app-amount>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -216,7 +216,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #defaultOutput>
|
<ng-template #defaultOutput>
|
||||||
<app-amount [satoshis]="vout.value"></app-amount>
|
<app-amount [blockConversion]="tx.price" [satoshis]="vout.value"></app-amount>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td class="arrow-td">
|
<td class="arrow-td">
|
||||||
@ -283,7 +283,9 @@
|
|||||||
|
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
<div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase && tx.fee !== -1">
|
<div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase && tx.fee !== -1">
|
||||||
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
|
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span
|
||||||
|
class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol"
|
||||||
|
i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [blockConversion]="tx.price" [value]="tx.fee"></app-fiat></span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="float-left mt-2-5 grey-info-text" *ngIf="tx.fee === -1" i18n="transactions-list.load-to-reveal-fee-info">Show more inputs to reveal fee data</div>
|
<div class="float-left mt-2-5 grey-info-text" *ngIf="tx.fee === -1" i18n="transactions-list.load-to-reveal-fee-info">Show more inputs to reveal fee data</div>
|
||||||
|
|
||||||
@ -301,12 +303,12 @@
|
|||||||
<button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
|
<button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
|
||||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
||||||
<ng-template #defaultAmount>
|
<ng-template #defaultAmount>
|
||||||
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
<app-amount [blockConversion]="tx.price" [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
<ng-template #viewingAddress>
|
<ng-template #viewingAddress>
|
||||||
<button type="button" class="btn btn-sm mt-2 ml-2" (click)="switchCurrency()" [ngClass]="{'btn-success': tx['addressValue'] >= 0, 'btn-danger': tx['addressValue'] < 0}">
|
<button type="button" class="btn btn-sm mt-2 ml-2" (click)="switchCurrency()" [ngClass]="{'btn-success': tx['addressValue'] >= 0, 'btn-danger': tx['addressValue'] < 0}">
|
||||||
<app-amount [satoshis]="tx['addressValue']" [addPlus]="true"></app-amount>
|
<app-amount [blockConversion]="tx.price" [satoshis]="tx['addressValue']" [addPlus]="true"></app-amount>
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ import { AssetsService } from '../../services/assets.service';
|
|||||||
import { filter, map, tap, switchMap } from 'rxjs/operators';
|
import { filter, map, tap, switchMap } from 'rxjs/operators';
|
||||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { PriceService } from 'src/app/services/price.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transactions-list',
|
selector: 'app-transactions-list',
|
||||||
@ -50,6 +51,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private assetsService: AssetsService,
|
private assetsService: AssetsService,
|
||||||
private ref: ChangeDetectorRef,
|
private ref: ChangeDetectorRef,
|
||||||
|
private priceService: PriceService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -147,6 +149,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
tx['addressValue'] = addressIn - addressOut;
|
tx['addressValue'] = addressIn - addressOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.priceService.getPrices().pipe(
|
||||||
|
tap(() => {
|
||||||
|
tx['price'] = this.priceService.getPriceForTimestamp(tx.status.block_time);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
});
|
});
|
||||||
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid);
|
||||||
if (txIds.length) {
|
if (txIds.length) {
|
||||||
|
@ -196,8 +196,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
this.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands);
|
this.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands);
|
||||||
|
|
||||||
this.middle = {
|
this.middle = {
|
||||||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.25} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.25}`,
|
||||||
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
style: `stroke-width: ${this.combinedWeight + 0.5}; stroke: ${this.gradient[1]}`
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hasLine = this.inputs.reduce((line, put) => line || !put.zeroValue, false)
|
this.hasLine = this.inputs.reduce((line, put) => line || !put.zeroValue, false)
|
||||||
@ -254,7 +254,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
const lineParams = weights.map((w, i) => {
|
const lineParams = weights.map((w, i) => {
|
||||||
return {
|
return {
|
||||||
weight: w,
|
weight: w,
|
||||||
thickness: xputs[i].value === 0 ? this.zeroValueThickness : Math.max(this.minWeight - 1, w) + 1,
|
thickness: xputs[i].value === 0 ? this.zeroValueThickness : Math.min(this.combinedWeight + 0.5, Math.max(this.minWeight - 1, w) + 1),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
innerY: 0,
|
innerY: 0,
|
||||||
outerY: 0,
|
outerY: 0,
|
||||||
@ -266,7 +266,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
// bounds of the middle segment
|
// bounds of the middle segment
|
||||||
const innerTop = (this.height / 2) - (this.combinedWeight / 2);
|
const innerTop = (this.height / 2) - (this.combinedWeight / 2);
|
||||||
const innerBottom = innerTop + this.combinedWeight;
|
const innerBottom = innerTop + this.combinedWeight + 0.5;
|
||||||
// tracks the visual bottom of the endpoints of the previous line
|
// tracks the visual bottom of the endpoints of the previous line
|
||||||
let lastOuter = 0;
|
let lastOuter = 0;
|
||||||
let lastInner = innerTop;
|
let lastInner = innerTop;
|
||||||
@ -291,7 +291,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
// set the vertical position of the (center of the) outer side of the line
|
// set the vertical position of the (center of the) outer side of the line
|
||||||
line.outerY = lastOuter + (line.thickness / 2);
|
line.outerY = lastOuter + (line.thickness / 2);
|
||||||
line.innerY = Math.min(innerBottom + (line.thickness / 2), Math.max(innerTop + (line.thickness / 2), lastInner + (line.weight / 2)));
|
line.innerY = Math.min(innerBottom - (line.thickness / 2), Math.max(innerTop + (line.thickness / 2), lastInner + (line.weight / 2)));
|
||||||
|
|
||||||
// special case to center single input/outputs
|
// special case to center single input/outputs
|
||||||
if (xputs.length === 1) {
|
if (xputs.length === 1) {
|
||||||
|
@ -1 +1,14 @@
|
|||||||
<span class="green-color" *ngIf="(conversions$ | async) as conversions">{{ (conversions ? conversions[currency] : 0) * value / 100000000 | fiatCurrency : digitsInfo : currency }}</span>
|
<span class="green-color" *ngIf="blockConversion; else noblockconversion">
|
||||||
|
{{
|
||||||
|
(
|
||||||
|
(blockConversion.price[currency] > 0 ? blockConversion.price[currency] : null) ??
|
||||||
|
(blockConversion.price['USD'] * blockConversion.exchangeRates['USD' + currency]) ?? 0
|
||||||
|
) * value / 100000000 | fiatCurrency : digitsInfo : currency
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ng-template #noblockconversion>
|
||||||
|
<span class="green-color" *ngIf="(conversions$ | async) as conversions">
|
||||||
|
{{ (conversions[currency] ?? conversions['USD'] ?? 0) * value / 100000000 | fiatCurrency : digitsInfo : currency }}
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
import { Price } from '../services/price.service';
|
||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -15,6 +16,7 @@ export class FiatComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
@Input() value: number;
|
@Input() value: number;
|
||||||
@Input() digitsInfo = '1.2-2';
|
@Input() digitsInfo = '1.2-2';
|
||||||
|
@Input() blockConversion: Price;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Price } from '../services/price.service';
|
||||||
import { IChannel } from './node-api.interface';
|
import { IChannel } from './node-api.interface';
|
||||||
|
|
||||||
export interface Transaction {
|
export interface Transaction {
|
||||||
@ -23,6 +24,7 @@ export interface Transaction {
|
|||||||
_deduced?: boolean;
|
_deduced?: boolean;
|
||||||
_outspends?: Outspend[];
|
_outspends?: Outspend[];
|
||||||
_channels?: TransactionChannels;
|
_channels?: TransactionChannels;
|
||||||
|
price?: Price;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionChannels {
|
export interface TransactionChannels {
|
||||||
|
@ -73,6 +73,7 @@ export interface SinglePoolStats {
|
|||||||
emptyBlockRatio: string;
|
emptyBlockRatio: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
avgMatchRate: number;
|
||||||
}
|
}
|
||||||
export interface PoolsStats {
|
export interface PoolsStats {
|
||||||
blockCount: number;
|
blockCount: number;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { Observable, switchMap, tap, zip } from 'rxjs';
|
import { delay, Observable, switchMap, tap, zip } from 'rxjs';
|
||||||
import { AssetsService } from '../../services/assets.service';
|
import { AssetsService } from '../../services/assets.service';
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
@ -75,6 +75,7 @@ export class NodesChannelsMap implements OnInit {
|
|||||||
|
|
||||||
this.channelsObservable = this.activatedRoute.paramMap
|
this.channelsObservable = this.activatedRoute.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
|
delay(100),
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
if (this.style === 'channelpage' && this.channel.length === 0 || !this.hasLocation) {
|
if (this.style === 'channelpage' && this.channel.length === 0 || !this.hasLocation) {
|
||||||
|
@ -6,6 +6,7 @@ import { Observable } from 'rxjs';
|
|||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||||
import { Outspend, Transaction } from '../interfaces/electrs.interface';
|
import { Outspend, Transaction } from '../interfaces/electrs.interface';
|
||||||
|
import { Conversion } from './price.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -303,4 +304,8 @@ export class ApiService {
|
|||||||
(style !== undefined ? `?style=${style}` : '')
|
(style !== undefined ? `?style=${style}` : '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHistoricalPrice$(): Observable<Conversion> {
|
||||||
|
return this.httpClient.get<Conversion>( this.apiBaseUrl + this.apiBasePath + '/api/v1/historical-price');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
125
frontend/src/app/services/price.service.ts
Normal file
125
frontend/src/app/services/price.service.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map, Observable, of, shareReplay } from 'rxjs';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
|
||||||
|
// nodejs backend interfaces
|
||||||
|
export interface ApiPrice {
|
||||||
|
time?: number,
|
||||||
|
USD: number,
|
||||||
|
EUR: number,
|
||||||
|
GBP: number,
|
||||||
|
CAD: number,
|
||||||
|
CHF: number,
|
||||||
|
AUD: number,
|
||||||
|
JPY: number,
|
||||||
|
}
|
||||||
|
export interface ExchangeRates {
|
||||||
|
USDEUR: number,
|
||||||
|
USDGBP: number,
|
||||||
|
USDCAD: number,
|
||||||
|
USDCHF: number,
|
||||||
|
USDAUD: number,
|
||||||
|
USDJPY: number,
|
||||||
|
}
|
||||||
|
export interface Conversion {
|
||||||
|
prices: ApiPrice[],
|
||||||
|
exchangeRates: ExchangeRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
// frontend interface
|
||||||
|
export interface Price {
|
||||||
|
price: ApiPrice,
|
||||||
|
exchangeRates: ExchangeRates,
|
||||||
|
}
|
||||||
|
export interface ConversionDict {
|
||||||
|
prices: { [timestamp: number]: ApiPrice }
|
||||||
|
exchangeRates: ExchangeRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PriceService {
|
||||||
|
historicalPrice: ConversionDict = {
|
||||||
|
prices: null,
|
||||||
|
exchangeRates: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private apiService: ApiService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmptyPrice(): Price {
|
||||||
|
return {
|
||||||
|
price: {
|
||||||
|
USD: 0, EUR: 0, GBP: 0, CAD: 0, CHF: 0, AUD: 0, JPY: 0,
|
||||||
|
},
|
||||||
|
exchangeRates: {
|
||||||
|
USDEUR: 0, USDGBP: 0, USDCAD: 0, USDCHF: 0, USDAUD: 0, USDJPY: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch prices from the nodejs backend only once
|
||||||
|
*/
|
||||||
|
getPrices(): Observable<void> {
|
||||||
|
if (this.historicalPrice.prices) {
|
||||||
|
return of(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.apiService.getHistoricalPrice$().pipe(
|
||||||
|
map((conversion: Conversion) => {
|
||||||
|
if (!this.historicalPrice.prices) {
|
||||||
|
this.historicalPrice.prices = Object();
|
||||||
|
}
|
||||||
|
for (const price of conversion.prices) {
|
||||||
|
this.historicalPrice.prices[price.time] = {
|
||||||
|
USD: price.USD, EUR: price.EUR, GBP: price.GBP, CAD: price.CAD,
|
||||||
|
CHF: price.CHF, AUD: price.AUD, JPY: price.JPY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.historicalPrice.exchangeRates = conversion.exchangeRates;
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
shareReplay(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: The first block with a price we have is block 68952 (using MtGox price history)
|
||||||
|
*
|
||||||
|
* @param blockTimestamp
|
||||||
|
*/
|
||||||
|
getPriceForTimestamp(blockTimestamp: number): Price | null {
|
||||||
|
if (!blockTimestamp) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceTimestamps = Object.keys(this.historicalPrice.prices);
|
||||||
|
priceTimestamps.push(Number.MAX_SAFE_INTEGER.toString());
|
||||||
|
priceTimestamps.sort().reverse();
|
||||||
|
|
||||||
|
// Small trick here. Because latest blocks have higher timestamps than our
|
||||||
|
// latest price timestamp (we only insert once every hour), we have no price for them.
|
||||||
|
// Therefore we want to fallback to the websocket price by returning an undefined `price` field.
|
||||||
|
// Since this.historicalPrice.prices[Number.MAX_SAFE_INTEGER] does not exists
|
||||||
|
// it will return `undefined` and automatically use the websocket price.
|
||||||
|
// This way we can differenciate blocks without prices like the genesis block
|
||||||
|
// vs ones without a price (yet) like the latest blocks
|
||||||
|
|
||||||
|
for (const t of priceTimestamps) {
|
||||||
|
const priceTimestamp = parseInt(t, 10);
|
||||||
|
if (blockTimestamp > priceTimestamp) {
|
||||||
|
return {
|
||||||
|
price: this.historicalPrice.prices[priceTimestamp],
|
||||||
|
exchangeRates: this.historicalPrice.exchangeRates,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getEmptyPrice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ listen=1
|
|||||||
discover=1
|
discover=1
|
||||||
par=16
|
par=16
|
||||||
dbcache=8192
|
dbcache=8192
|
||||||
maxmempool=1337
|
maxmempool=4096
|
||||||
mempoolexpiry=999999
|
mempoolexpiry=999999
|
||||||
maxconnections=42
|
maxconnections=42
|
||||||
onion=127.0.0.1:9050
|
onion=127.0.0.1:9050
|
||||||
|
@ -106,7 +106,7 @@ build_backend()
|
|||||||
-e "s!__MEMPOOL_SIGNET_LIGHTNING_PASS__!${MEMPOOL_SIGNET_LIGHTNING_PASS}!" \
|
-e "s!__MEMPOOL_SIGNET_LIGHTNING_PASS__!${MEMPOOL_SIGNET_LIGHTNING_PASS}!" \
|
||||||
-e "s!__MEMPOOL_LIQUID_USER__!${MEMPOOL_LIQUID_USER}!" \
|
-e "s!__MEMPOOL_LIQUID_USER__!${MEMPOOL_LIQUID_USER}!" \
|
||||||
-e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
|
-e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
|
||||||
-e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${LIQUIDTESTNET_USER}!" \
|
-e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${MEMPOOL_LIQUIDTESTNET_USER}!" \
|
||||||
-e "s!__MEMPOOL_LIQUIDTESTNET_PASS__!${MEMPOOL_LIQUIDTESTNET_PASS}!" \
|
-e "s!__MEMPOOL_LIQUIDTESTNET_PASS__!${MEMPOOL_LIQUIDTESTNET_PASS}!" \
|
||||||
-e "s!__MEMPOOL_BISQ_USER__!${MEMPOOL_BISQ_USER}!" \
|
-e "s!__MEMPOOL_BISQ_USER__!${MEMPOOL_BISQ_USER}!" \
|
||||||
-e "s!__MEMPOOL_BISQ_PASS__!${MEMPOOL_BISQ_PASS}!" \
|
-e "s!__MEMPOOL_BISQ_PASS__!${MEMPOOL_BISQ_PASS}!" \
|
||||||
|
@ -45,6 +45,6 @@
|
|||||||
"PORT": 3306,
|
"PORT": 3306,
|
||||||
"USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__",
|
"USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__",
|
||||||
"PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__",
|
"PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__",
|
||||||
"PASSWORD": "mempool_mainnet_lightning"
|
"DATABASE": "mempool_mainnet_lightning"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,14 +120,16 @@ do for url in / \
|
|||||||
|
|
||||||
do
|
do
|
||||||
warm "https://${hostname}${url}"
|
warm "https://${hostname}${url}"
|
||||||
|
sleep 0.25 # wait 250ms between queries to not DoS mariadb
|
||||||
done
|
done
|
||||||
|
|
||||||
for slug in $slugs
|
for slug in $slugs
|
||||||
do
|
do
|
||||||
warm "https://${hostname}/api/v1/mining/pool/${slug}"
|
warm "https://${hostname}/api/v1/mining/pool/${slug}"
|
||||||
|
sleep 0.25 # wait 250ms between queries to not DoS mariadb
|
||||||
warm "https://${hostname}/api/v1/mining/pool/${slug}/hashrate"
|
warm "https://${hostname}/api/v1/mining/pool/${slug}/hashrate"
|
||||||
|
sleep 0.25 # wait 250ms between queries to not DoS mariadb
|
||||||
warm "https://${hostname}/api/v1/mining/pool/${slug}/blocks"
|
warm "https://${hostname}/api/v1/mining/pool/${slug}/blocks"
|
||||||
|
sleep 0.25 # wait 250ms between queries to not DoS mariadb
|
||||||
done
|
done
|
||||||
|
|
||||||
sleep 10
|
|
||||||
done
|
done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user