Compare commits
1 Commits
mononaut/d
...
mononaut/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
283fd1c58e |
@@ -268,17 +268,15 @@ class Blocks {
|
|||||||
extras.segwitTotalWeight = 0;
|
extras.segwitTotalWeight = 0;
|
||||||
} else {
|
} else {
|
||||||
const stats: IBitcoinApi.BlockStats = await bitcoinClient.getBlockStats(block.id);
|
const stats: IBitcoinApi.BlockStats = await bitcoinClient.getBlockStats(block.id);
|
||||||
const feeStats = {
|
let feeStats = {
|
||||||
medianFee: stats.feerate_percentiles[2], // 50th percentiles
|
medianFee: stats.feerate_percentiles[2], // 50th percentiles
|
||||||
feeRange: [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(),
|
feeRange: [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(),
|
||||||
};
|
};
|
||||||
|
if (transactions?.length > 1) {
|
||||||
|
feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||||
|
}
|
||||||
extras.medianFee = feeStats.medianFee;
|
extras.medianFee = feeStats.medianFee;
|
||||||
extras.feeRange = feeStats.feeRange;
|
extras.feeRange = feeStats.feeRange;
|
||||||
if (transactions?.length > 1) {
|
|
||||||
const effectiveFeeStats = Common.calcEffectiveFeeStatistics(transactions);
|
|
||||||
extras.effectiveMedianFee = effectiveFeeStats.effective_median;
|
|
||||||
extras.effectiveFeeRange = effectiveFeeStats.effective_range;
|
|
||||||
}
|
|
||||||
extras.totalFees = stats.totalfee;
|
extras.totalFees = stats.totalfee;
|
||||||
extras.avgFee = stats.avgfee;
|
extras.avgFee = stats.avgfee;
|
||||||
extras.avgFeeRate = stats.avgfeerate;
|
extras.avgFeeRate = stats.avgfeerate;
|
||||||
@@ -1318,8 +1316,6 @@ class Blocks {
|
|||||||
avg_fee_rate: block.extras.avgFeeRate ?? null,
|
avg_fee_rate: block.extras.avgFeeRate ?? null,
|
||||||
median_fee_rate: block.extras.medianFee ?? null,
|
median_fee_rate: block.extras.medianFee ?? null,
|
||||||
fee_rate_percentiles: block.extras.feeRange ?? null,
|
fee_rate_percentiles: block.extras.feeRange ?? null,
|
||||||
effective_median_fee_rate: block.extras.effectiveMedianFee ?? null,
|
|
||||||
effective_fee_rate_percentiles: block.extras.effectiveFeeRange ?? null,
|
|
||||||
total_inputs: block.extras.totalInputs ?? null,
|
total_inputs: block.extras.totalInputs ?? null,
|
||||||
total_input_amt: block.extras.totalInputAmt ?? null,
|
total_input_amt: block.extras.totalInputAmt ?? null,
|
||||||
total_outputs: block.extras.totalOutputs ?? null,
|
total_outputs: block.extras.totalOutputs ?? null,
|
||||||
@@ -1382,17 +1378,6 @@ class Blocks {
|
|||||||
'perc_90': cleanBlock.fee_rate_percentiles[5],
|
'perc_90': cleanBlock.fee_rate_percentiles[5],
|
||||||
'max': cleanBlock.fee_rate_percentiles[6],
|
'max': cleanBlock.fee_rate_percentiles[6],
|
||||||
};
|
};
|
||||||
if (cleanBlock.effective_fee_rate_percentiles) {
|
|
||||||
cleanBlock.effective_fee_rate_percentiles = {
|
|
||||||
'min': cleanBlock.effective_fee_rate_percentiles[0],
|
|
||||||
'perc_10': cleanBlock.effective_fee_rate_percentiles[1],
|
|
||||||
'perc_25': cleanBlock.effective_fee_rate_percentiles[2],
|
|
||||||
'perc_50': cleanBlock.effective_fee_rate_percentiles[3],
|
|
||||||
'perc_75': cleanBlock.effective_fee_rate_percentiles[4],
|
|
||||||
'perc_90': cleanBlock.effective_fee_rate_percentiles[5],
|
|
||||||
'max': cleanBlock.effective_fee_rate_percentiles[6],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-org can happen after indexing so we need to always get the
|
// Re-org can happen after indexing so we need to always get the
|
||||||
// latest state from core
|
// latest state from core
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats, TransactionClassified, TransactionFlags, FeeStats } from '../mempool.interfaces';
|
import { EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats, TransactionClassified, TransactionFlags } from '../mempool.interfaces';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
||||||
import { isIP } from 'net';
|
import { isIP } from 'net';
|
||||||
@@ -856,15 +856,6 @@ export class Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static calcFeeStatistics(transactions: { txid: string, feePerVsize: number }[]): FeeStats {
|
|
||||||
// skip the coinbase, then sort the remaining fee rates
|
|
||||||
const sortedRates = transactions.slice(1).map(tx => tx.feePerVsize).sort((a, b) => a - b);
|
|
||||||
return {
|
|
||||||
median: Math.round(Common.getNthPercentile(50, sortedRates)),
|
|
||||||
range: [0, 10, 25, 50, 75, 90, 100].map(n => Math.round(Common.getNthPercentile(n, sortedRates))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static calcEffectiveFeeStatistics(transactions: { weight: number, fee: number, effectiveFeePerVsize?: number, txid: string, acceleration?: boolean }[]): EffectiveFeeStats {
|
static calcEffectiveFeeStatistics(transactions: { weight: number, fee: number, effectiveFeePerVsize?: number, txid: string, acceleration?: boolean }[]): EffectiveFeeStats {
|
||||||
const sortedTxs = transactions.map(tx => { return { txid: tx.txid, weight: tx.weight, rate: tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4)) }; }).sort((a, b) => a.rate - b.rate);
|
const sortedTxs = transactions.map(tx => { return { txid: tx.txid, weight: tx.weight, rate: tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4)) }; }).sort((a, b) => a.rate - b.rate);
|
||||||
|
|
||||||
@@ -907,8 +898,8 @@ export class Common {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
effective_median: medianFeeRate,
|
medianFee: medianFeeRate,
|
||||||
effective_range: [
|
feeRange: [
|
||||||
minFee,
|
minFee,
|
||||||
[10,25,50,75,90].map(n => Common.getNthPercentile(n, sortedTxs).rate),
|
[10,25,50,75,90].map(n => Common.getNthPercentile(n, sortedTxs).rate),
|
||||||
maxFee,
|
maxFee,
|
||||||
@@ -1159,16 +1150,16 @@ export class OnlineFeeStatsCalculator {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
minFee: this.feeRange[0].min,
|
minFee: this.feeRange[0].min,
|
||||||
effective_median: this.feeRange[Math.floor(this.feeRange.length / 2)].avg,
|
medianFee: this.feeRange[Math.floor(this.feeRange.length / 2)].avg,
|
||||||
maxFee: this.feeRange[this.feeRange.length - 1].max,
|
maxFee: this.feeRange[this.feeRange.length - 1].max,
|
||||||
effective_range: this.feeRange.map(f => f.avg),
|
feeRange: this.feeRange.map(f => f.avg),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeeStats(): EffectiveFeeStats {
|
getFeeStats(): EffectiveFeeStats {
|
||||||
const stats = this.getRawFeeStats();
|
const stats = this.getRawFeeStats();
|
||||||
stats.effective_range[0] = stats.minFee;
|
stats.feeRange[0] = stats.minFee;
|
||||||
stats.effective_range[stats.effective_range.length - 1] = stats.maxFee;
|
stats.feeRange[stats.feeRange.length - 1] = stats.maxFee;
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 95;
|
private static currentVersion = 94;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@@ -1118,16 +1118,6 @@ class DatabaseMigration {
|
|||||||
}
|
}
|
||||||
await this.updateToSchemaVersion(94);
|
await this.updateToSchemaVersion(94);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (databaseSchemaVersion < 95) {
|
|
||||||
// Version 95
|
|
||||||
await this.$executeQuery(`
|
|
||||||
ALTER TABLE \`blocks\`
|
|
||||||
ADD \`effective_median_fee\` BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
||||||
ADD \`effective_fee_span\` JSON DEFAULT NULL;
|
|
||||||
`);
|
|
||||||
await this.updateToSchemaVersion(95);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ class FeeApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private optimizeMedianFee(pBlock: MempoolBlock, nextBlock: MempoolBlock | undefined, previousFee?: number): number {
|
private optimizeMedianFee(pBlock: MempoolBlock, nextBlock: MempoolBlock | undefined, previousFee?: number): number {
|
||||||
const medianFee = pBlock.effectiveMedianFee ?? pBlock.medianFee;
|
const useFee = previousFee ? (pBlock.medianFee + previousFee) / 2 : pBlock.medianFee;
|
||||||
const useFee = previousFee ? (medianFee + previousFee) / 2 : medianFee;
|
|
||||||
if (pBlock.blockVSize <= 500000) {
|
if (pBlock.blockVSize <= 500000) {
|
||||||
return this.defaultFee;
|
return this.defaultFee;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
|
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, FeeStats, TransactionClassified, TransactionCompressed, MempoolDeltaChange, GbtCandidates, PoolTag, EffectiveFeeStats } from '../mempool.interfaces';
|
import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, TransactionClassified, TransactionCompressed, MempoolDeltaChange, GbtCandidates, PoolTag } from '../mempool.interfaces';
|
||||||
import { Common, OnlineFeeStatsCalculator } from './common';
|
import { Common, OnlineFeeStatsCalculator } from './common';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
@@ -33,8 +33,6 @@ class MempoolBlocks {
|
|||||||
totalFees: block.totalFees,
|
totalFees: block.totalFees,
|
||||||
medianFee: block.medianFee,
|
medianFee: block.medianFee,
|
||||||
feeRange: block.feeRange,
|
feeRange: block.feeRange,
|
||||||
effectiveMedianFee: block.effectiveMedianFee,
|
|
||||||
effectiveFeeRange: block.effectiveFeeRange,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -529,7 +527,7 @@ class MempoolBlocks {
|
|||||||
totalSize,
|
totalSize,
|
||||||
totalWeight,
|
totalWeight,
|
||||||
totalFees,
|
totalFees,
|
||||||
(hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getFeeStats() : undefined,
|
(hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -543,20 +541,17 @@ class MempoolBlocks {
|
|||||||
return mempoolBlocks;
|
return mempoolBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, effectiveFeeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
|
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
|
||||||
const feeStats = Common.calcFeeStatistics(transactions);
|
if (!feeStats) {
|
||||||
if (!effectiveFeeStats) {
|
feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||||
effectiveFeeStats = Common.calcEffectiveFeeStatistics(transactions);
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
blockSize: totalSize,
|
blockSize: totalSize,
|
||||||
blockVSize: (totalWeight / 4), // fractional vsize to avoid rounding errors
|
blockVSize: (totalWeight / 4), // fractional vsize to avoid rounding errors
|
||||||
nTx: transactionIds.length,
|
nTx: transactionIds.length,
|
||||||
totalFees: totalFees,
|
totalFees: totalFees,
|
||||||
medianFee: feeStats.median,
|
medianFee: feeStats.medianFee, // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||||
feeRange: feeStats.range,
|
feeRange: feeStats.feeRange, //Common.getFeesInRange(transactions, rangeLength),
|
||||||
effectiveMedianFee: effectiveFeeStats.effective_median,
|
|
||||||
effectiveFeeRange: effectiveFeeStats.effective_range,
|
|
||||||
transactionIds: transactionIds,
|
transactionIds: transactionIds,
|
||||||
transactions: transactions.map((tx) => Common.classifyTransaction(tx)),
|
transactions: transactions.map((tx) => Common.classifyTransaction(tx)),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ export interface MempoolBlock {
|
|||||||
medianFee: number;
|
medianFee: number;
|
||||||
totalFees: number;
|
totalFees: number;
|
||||||
feeRange: number[];
|
feeRange: number[];
|
||||||
effectiveMedianFee?: number;
|
|
||||||
effectiveFeeRange?: number[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MempoolBlockWithTransactions extends MempoolBlock {
|
export interface MempoolBlockWithTransactions extends MempoolBlock {
|
||||||
@@ -290,10 +288,8 @@ export const TransactionFlags = {
|
|||||||
|
|
||||||
export interface BlockExtension {
|
export interface BlockExtension {
|
||||||
totalFees: number;
|
totalFees: number;
|
||||||
medianFee: number; // core median fee rate
|
medianFee: number; // median fee rate
|
||||||
feeRange: number[]; // core fee rate percentiles
|
feeRange: number[]; // fee rate percentiles
|
||||||
effectiveMedianFee?: number; // effective median fee rate
|
|
||||||
effectiveFeeRange?: number[]; // effective fee rate percentiles
|
|
||||||
reward: number;
|
reward: number;
|
||||||
matchRate: number | null;
|
matchRate: number | null;
|
||||||
expectedFees: number | null;
|
expectedFees: number | null;
|
||||||
@@ -373,18 +369,9 @@ export interface MempoolStats {
|
|||||||
tx_count: number;
|
tx_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core fee stats
|
|
||||||
// measured in individual sats/vbyte
|
|
||||||
export interface FeeStats {
|
|
||||||
median: number; // median core fee rate
|
|
||||||
range: number[]; // 0th, 10th, 25th, 50th, 75th, 90th, 100th percentiles
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mempool effective fee stats
|
|
||||||
// measured in effective sats/vbyte
|
|
||||||
export interface EffectiveFeeStats {
|
export interface EffectiveFeeStats {
|
||||||
effective_median: number; // median effective fee rate by weight
|
medianFee: number; // median effective fee rate
|
||||||
effective_range: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
|
feeRange: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
|
export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
|
||||||
|
|||||||
@@ -315,12 +315,12 @@ class AccelerationRepository {
|
|||||||
Infinity
|
Infinity
|
||||||
);
|
);
|
||||||
const feeStats = Common.calcEffectiveFeeStatistics(template);
|
const feeStats = Common.calcEffectiveFeeStatistics(template);
|
||||||
boostRate = feeStats.effective_median;
|
boostRate = feeStats.medianFee;
|
||||||
}
|
}
|
||||||
const accelerationSummaries = accelerations.map(acc => ({
|
const accelerationSummaries = accelerations.map(acc => ({
|
||||||
...acc,
|
...acc,
|
||||||
pools: acc.pools,
|
pools: acc.pools,
|
||||||
}));
|
}))
|
||||||
for (const acc of accelerations) {
|
for (const acc of accelerations) {
|
||||||
if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) {
|
if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) {
|
||||||
const tx = blockTxs[acc.txid];
|
const tx = blockTxs[acc.txid];
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ interface DatabaseBlock {
|
|||||||
totalFees: number;
|
totalFees: number;
|
||||||
medianFee: number;
|
medianFee: number;
|
||||||
feeRange: string;
|
feeRange: string;
|
||||||
effectiveMedianFee?: number;
|
|
||||||
effectiveFeeRange?: string;
|
|
||||||
reward: number;
|
reward: number;
|
||||||
poolId: number;
|
poolId: number;
|
||||||
poolName: string;
|
poolName: string;
|
||||||
@@ -79,8 +77,6 @@ const BLOCK_DB_FIELDS = `
|
|||||||
blocks.fees AS totalFees,
|
blocks.fees AS totalFees,
|
||||||
blocks.median_fee AS medianFee,
|
blocks.median_fee AS medianFee,
|
||||||
blocks.fee_span AS feeRange,
|
blocks.fee_span AS feeRange,
|
||||||
blocks.effective_median_fee AS effectiveMedianFee,
|
|
||||||
blocks.effective_fee_span AS effectiveFeeRange,
|
|
||||||
blocks.reward,
|
blocks.reward,
|
||||||
pools.unique_id AS poolId,
|
pools.unique_id AS poolId,
|
||||||
pools.name AS poolName,
|
pools.name AS poolName,
|
||||||
@@ -112,7 +108,7 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Save indexed block data in the database
|
* Save indexed block data in the database
|
||||||
*/
|
*/
|
||||||
public async $saveBlockInDatabase(block: BlockExtended): Promise<void> {
|
public async $saveBlockInDatabase(block: BlockExtended) {
|
||||||
const truncatedCoinbaseSignature = block?.extras?.coinbaseSignature?.substring(0, 500);
|
const truncatedCoinbaseSignature = block?.extras?.coinbaseSignature?.substring(0, 500);
|
||||||
const truncatedCoinbaseSignatureAscii = block?.extras?.coinbaseSignatureAscii?.substring(0, 500);
|
const truncatedCoinbaseSignatureAscii = block?.extras?.coinbaseSignatureAscii?.substring(0, 500);
|
||||||
|
|
||||||
@@ -121,7 +117,6 @@ class BlocksRepository {
|
|||||||
height, hash, blockTimestamp, size,
|
height, hash, blockTimestamp, size,
|
||||||
weight, tx_count, coinbase_raw, difficulty,
|
weight, tx_count, coinbase_raw, difficulty,
|
||||||
pool_id, fees, fee_span, median_fee,
|
pool_id, fees, fee_span, median_fee,
|
||||||
effective_fee_span, effective_median_fee,
|
|
||||||
reward, version, bits, nonce,
|
reward, version, bits, nonce,
|
||||||
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
||||||
median_timestamp, header, coinbase_address, coinbase_addresses,
|
median_timestamp, header, coinbase_address, coinbase_addresses,
|
||||||
@@ -133,7 +128,6 @@ class BlocksRepository {
|
|||||||
?, ?, FROM_UNIXTIME(?), ?,
|
?, ?, FROM_UNIXTIME(?), ?,
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
?, ?,
|
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
?, ?, ?, ?,
|
?, ?, ?, ?,
|
||||||
FROM_UNIXTIME(?), ?, ?, ?,
|
FROM_UNIXTIME(?), ?, ?, ?,
|
||||||
@@ -161,8 +155,6 @@ class BlocksRepository {
|
|||||||
block.extras.totalFees,
|
block.extras.totalFees,
|
||||||
JSON.stringify(block.extras.feeRange),
|
JSON.stringify(block.extras.feeRange),
|
||||||
block.extras.medianFee,
|
block.extras.medianFee,
|
||||||
block.extras.effectiveFeeRange ? JSON.stringify(block.extras.effectiveFeeRange) : null,
|
|
||||||
block.extras.effectiveMedianFee,
|
|
||||||
block.extras.reward,
|
block.extras.reward,
|
||||||
block.version,
|
block.version,
|
||||||
block.bits,
|
block.bits,
|
||||||
@@ -983,9 +975,9 @@ class BlocksRepository {
|
|||||||
public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise<void> {
|
public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DB.query(`
|
await DB.query(`
|
||||||
UPDATE blocks SET effective_median_fee = ?, effective_fee_span = ?
|
UPDATE blocks SET median_fee = ?, fee_span = ?
|
||||||
WHERE hash = ?`,
|
WHERE hash = ?`,
|
||||||
[feeStats.effective_median, JSON.stringify(feeStats.effective_range), id]
|
[feeStats.medianFee, JSON.stringify(feeStats.feeRange), id]
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot update block fee stats. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot update block fee stats. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
@@ -1078,8 +1070,6 @@ class BlocksRepository {
|
|||||||
extras.totalFees = dbBlk.totalFees;
|
extras.totalFees = dbBlk.totalFees;
|
||||||
extras.medianFee = dbBlk.medianFee;
|
extras.medianFee = dbBlk.medianFee;
|
||||||
extras.feeRange = JSON.parse(dbBlk.feeRange);
|
extras.feeRange = JSON.parse(dbBlk.feeRange);
|
||||||
extras.effectiveMedianFee = dbBlk.effectiveMedianFee;
|
|
||||||
extras.effectiveFeeRange = dbBlk.effectiveFeeRange ? JSON.parse(dbBlk.effectiveFeeRange) : null;
|
|
||||||
extras.reward = dbBlk.reward;
|
extras.reward = dbBlk.reward;
|
||||||
extras.pool = {
|
extras.pool = {
|
||||||
id: dbBlk.poolId,
|
id: dbBlk.poolId,
|
||||||
|
|||||||
@@ -38,7 +38,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "blocks"
|
"component": "simpleproof",
|
||||||
|
"mobileOrder": 6,
|
||||||
|
"props": {
|
||||||
|
"label": "Executive Decrees",
|
||||||
|
"key": "el_salvador_decretos"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "addressTransactions",
|
"component": "addressTransactions",
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
<td i18n="block.weight">Weight</td>
|
<td i18n="block.weight">Weight</td>
|
||||||
<td [innerHTML]="'‎' + (block?.weight | wuBytes: 2)"></td>
|
<td [innerHTML]="'‎' + (block?.weight | wuBytes: 2)"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="block?.extras?.medianFee != undefined && block?.extras?.effectiveMedianFee != 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>~<app-fee-rate [fee]="block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee" rounding="1.0-0"></app-fee-rate></td>
|
<td>~<app-fee-rate [fee]="block?.extras?.medianFee" rounding="1.0-0"></app-fee-rate></td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-template [ngIf]="fees !== undefined">
|
<ng-template [ngIf]="fees !== undefined">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -132,11 +132,11 @@
|
|||||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||||
<td><app-fee-rate [fee]="block.extras?.minFee" [showUnit]="false" rounding="1.0-0"></app-fee-rate> - <app-fee-rate [fee]="block.extras?.maxFee" rounding="1.0-0"></app-fee-rate></td>
|
<td><app-fee-rate [fee]="block.extras?.minFee" [showUnit]="false" rounding="1.0-0"></app-fee-rate> - <app-fee-rate [fee]="block.extras?.maxFee" rounding="1.0-0"></app-fee-rate></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="block.extras?.effectiveMedianFee != undefined || 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>~<app-fee-rate [fee]="block.extras?.effectiveMedianFee ?? block.extras?.medianFee" rounding="1.0-0"></app-fee-rate>
|
<td>~<app-fee-rate [fee]="block.extras?.medianFee" rounding="1.0-0"></app-fee-rate>
|
||||||
<span class="fiat">
|
<span class="fiat">
|
||||||
<app-fiat [blockConversion]="blockConversion" [value]="(block.extras?.effectiveMedianFee ?? block.extras?.medianFee) * 140" digitsInfo="1.2-2"
|
<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"
|
i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes"
|
||||||
placement="bottom"></app-fiat>
|
placement="bottom"></app-fiat>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -792,9 +792,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMinBlockFee(block: BlockExtended): number {
|
getMinBlockFee(block: BlockExtended): number {
|
||||||
if (block?.extras?.effectiveFeeRange) {
|
|
||||||
return block.extras.effectiveFeeRange[0];
|
|
||||||
}
|
|
||||||
if (block?.extras?.feeRange) {
|
if (block?.extras?.feeRange) {
|
||||||
// heuristic to check if feeRange is adjusted for effective rates
|
// heuristic to check if feeRange is adjusted for effective rates
|
||||||
if (block.extras.medianFee === block.extras.feeRange[3]) {
|
if (block.extras.medianFee === block.extras.feeRange[3]) {
|
||||||
@@ -807,9 +804,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMaxBlockFee(block: BlockExtended): number {
|
getMaxBlockFee(block: BlockExtended): number {
|
||||||
if (block?.extras?.effectiveFeeRange) {
|
|
||||||
return block.extras.effectiveFeeRange[block.extras.effectiveFeeRange.length - 1];
|
|
||||||
}
|
|
||||||
if (block?.extras?.feeRange) {
|
if (block?.extras?.feeRange) {
|
||||||
return block.extras.feeRange[block.extras.feeRange.length - 1];
|
return block.extras.feeRange[block.extras.feeRange.length - 1];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="block-body">
|
<div class="block-body">
|
||||||
<ng-container *ngIf="!minimal">
|
<ng-container *ngIf="!minimal">
|
||||||
<div *ngIf="block?.extras; else emptyfees" [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">
|
||||||
~<app-fee-rate [fee]="block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee" unitClass="" rounding="1.0-0"></app-fee-rate>
|
~<app-fee-rate [fee]="block?.extras?.medianFee" unitClass="" rounding="1.0-0"></app-fee-rate>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #emptyfees>
|
<ng-template #emptyfees>
|
||||||
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
|
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
|
||||||
|
|||||||
@@ -414,9 +414,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMinBlockFee(block: BlockExtended): number {
|
getMinBlockFee(block: BlockExtended): number {
|
||||||
if (block?.extras?.effectiveFeeRange) {
|
|
||||||
return block.extras.effectiveFeeRange[0];
|
|
||||||
}
|
|
||||||
if (block?.extras?.feeRange) {
|
if (block?.extras?.feeRange) {
|
||||||
// heuristic to check if feeRange is adjusted for effective rates
|
// heuristic to check if feeRange is adjusted for effective rates
|
||||||
if (block.extras.medianFee === block.extras.feeRange[3]) {
|
if (block.extras.medianFee === block.extras.feeRange[3]) {
|
||||||
@@ -429,9 +426,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMaxBlockFee(block: BlockExtended): number {
|
getMaxBlockFee(block: BlockExtended): number {
|
||||||
if (block?.extras?.effectiveFeeRange) {
|
|
||||||
return block.extras.effectiveFeeRange[block.extras.effectiveFeeRange.length - 1];
|
|
||||||
}
|
|
||||||
if (block?.extras?.feeRange) {
|
if (block?.extras?.feeRange) {
|
||||||
return block.extras.feeRange[block.extras.feeRange.length - 1];
|
return block.extras.feeRange[block.extras.feeRange.length - 1];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,6 +305,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@case ('simpleproof') {
|
||||||
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<a class="title-link" href="" [routerLink]="['/sp/verified' | relativeUrl]">
|
||||||
|
<h5 class="card-title d-inline" i18n="dashboard.recent-blocks">{{ widget.props?.label }}</h5>
|
||||||
|
<span> </span>
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
|
</a>
|
||||||
|
<app-simpleproof-widget [label]="widget.props.label" [key]="widget.props.key" [widget]="true"></app-simpleproof-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,9 +57,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
|||||||
this.mempoolBlockIndex--;
|
this.mempoolBlockIndex--;
|
||||||
}
|
}
|
||||||
const ordinal = this.getOrdinal(mempoolBlocks[this.mempoolBlockIndex]);
|
const ordinal = this.getOrdinal(mempoolBlocks[this.mempoolBlockIndex]);
|
||||||
// prefer effective fee stats if available
|
|
||||||
mempoolBlocks[this.mempoolBlockIndex].feeRange = mempoolBlocks[this.mempoolBlockIndex].effectiveFeeRange ?? mempoolBlocks[this.mempoolBlockIndex].feeRange;
|
|
||||||
mempoolBlocks[this.mempoolBlockIndex].medianFee = mempoolBlocks[this.mempoolBlockIndex].effectiveMedianFee ?? mempoolBlocks[this.mempoolBlockIndex].medianFee;
|
|
||||||
this.ordinal$.next(ordinal);
|
this.ordinal$.next(ordinal);
|
||||||
this.seoService.setTitle(ordinal);
|
this.seoService.setTitle(ordinal);
|
||||||
this.seoService.setDescription($localize`:@@meta.description.mempool-block:See stats for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions.`);
|
this.seoService.setDescription($localize`:@@meta.description.mempool-block:See stats for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions.`);
|
||||||
|
|||||||
@@ -171,8 +171,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
block.index = this.blockIndex + i;
|
block.index = this.blockIndex + i;
|
||||||
block.height = lastBlock.height + i + 1;
|
block.height = lastBlock.height + i + 1;
|
||||||
block.blink = specialBlocks[block.height]?.networks.includes(this.stateService.network || 'mainnet') ? true : false;
|
block.blink = specialBlocks[block.height]?.networks.includes(this.stateService.network || 'mainnet') ? true : false;
|
||||||
block.medianFee = block.effectiveMedianFee ?? block.medianFee;
|
|
||||||
block.feeRange = block.effectiveFeeRange ?? block.feeRange;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const stringifiedBlocks = JSON.stringify(mempoolBlocks);
|
const stringifiedBlocks = JSON.stringify(mempoolBlocks);
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget}">
|
||||||
|
<div *ngIf="!widget" class="float-left" style="display: flex; width: 100%; align-items: center;">
|
||||||
|
<h1>{{ label }}</h1>
|
||||||
|
<div *ngIf="!widget && isLoading" class="spinner-border" role="status"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
@if (isLoading) {
|
||||||
|
loading!
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
} @else if (error || !verified.length) {
|
||||||
|
<div class="error-wrapper">
|
||||||
|
<span>temporarily unavailable</span>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div style="min-height: 295px">
|
||||||
|
<table class="table table-borderless" [class.table-fixed]="widget">
|
||||||
|
<thead>
|
||||||
|
<th class="filename text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.filename">Filename</th>
|
||||||
|
<th class="hash text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.hash">Hash</th>
|
||||||
|
<th class="verified text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.verified">Verified</th>
|
||||||
|
<th class="proof text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.proof">Proof</th>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="verifiedPage; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||||
|
<tr *ngFor="let item of verifiedPage">
|
||||||
|
<td class="filename text-left" [class]="widget ? 'widget' : ''">{{ item.file_name }}</td>
|
||||||
|
<td class="hash text-left" [class]="widget ? 'widget' : ''">{{ item.sha256 }}</td>
|
||||||
|
<td class="verified text-right" [class]="widget ? 'widget' : ''">
|
||||||
|
<app-timestamp [unixTime]="item.block_time" [customFormat]="'yyyy-MM-dd'" [hideTimeSince]="true"></app-timestamp>
|
||||||
|
</td>
|
||||||
|
<td class="proof text-right" [class]="widget ? 'widget' : ''">
|
||||||
|
<a [href]="item.sanitized_url" target="_blank" class="badge badge-primary badge-verify">
|
||||||
|
<span class="icon">
|
||||||
|
<img class="icon-img" src="/resources/sp.svg">
|
||||||
|
</span>
|
||||||
|
<span i18n="simpleproof.verify">Verify</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<ng-template #skeleton>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let item of [].constructor(itemsPerPage)">
|
||||||
|
<td class="filename text-left" [ngClass]="{'widget': widget}">
|
||||||
|
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||||
|
</td>
|
||||||
|
<td class="hash text-left" [ngClass]="{'widget': widget}">
|
||||||
|
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||||
|
</td>
|
||||||
|
<td class="verified text-right" [ngClass]="{'widget': widget}">
|
||||||
|
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||||
|
</td>
|
||||||
|
<td class="proof text-right" [ngClass]="{'widget': widget}">
|
||||||
|
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</ng-template>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||||
|
[collectionSize]="verified.length" [rotate]="true" [maxSize]="paginationMaxSize" [pageSize]="itemsPerPage" [(page)]="page"
|
||||||
|
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||||
|
</ngb-pagination>
|
||||||
|
|
||||||
|
<ng-template [ngIf]="!widget">
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<br>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
.spinner-wrapper, .error-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border {
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-left: -13px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-xl {
|
||||||
|
max-width: 1400px;
|
||||||
|
}
|
||||||
|
.container-xl.widget {
|
||||||
|
padding-left: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
.container-xl.legacy {
|
||||||
|
max-width: 1140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr, td, th {
|
||||||
|
border: 0px;
|
||||||
|
padding-top: 0.71rem !important;
|
||||||
|
padding-bottom: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-link {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
background-color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename {
|
||||||
|
width: 50%;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hash {
|
||||||
|
width: 25%;
|
||||||
|
max-width: 700px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
td.hash {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.widget .hash {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.hash {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verified {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
td.verified {
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proof {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-verify {
|
||||||
|
font-size: 1.05em;
|
||||||
|
font-weight: normal;
|
||||||
|
background: var(--nav-bg);
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin: -0.25em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
|
||||||
|
.icon-img {
|
||||||
|
width: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { Component, Input, SecurityContext, SimpleChanges, OnChanges } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
|
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||||
|
import { catchError, of } from 'rxjs';
|
||||||
|
|
||||||
|
export interface SimpleProof {
|
||||||
|
file_name: string;
|
||||||
|
sha256: string;
|
||||||
|
ots_verification: string;
|
||||||
|
block_height: number;
|
||||||
|
block_hash: string;
|
||||||
|
block_time: number;
|
||||||
|
simpleproof_url: string;
|
||||||
|
key?: string;
|
||||||
|
sanitized_url?: SafeResourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-simpleproof-widget',
|
||||||
|
templateUrl: './simpleproof-widget.component.html',
|
||||||
|
styleUrls: ['./simpleproof-widget.component.scss'],
|
||||||
|
})
|
||||||
|
export class SimpleProofWidgetComponent implements OnChanges {
|
||||||
|
@Input() key: string = window['__env']?.customize?.dashboard.widgets?.find(w => w.component ==='simpleproof')?.props?.key ?? '';
|
||||||
|
@Input() label: string = window['__env']?.customize?.dashboard.widgets?.find(w => w.component ==='simpleproof')?.props?.label ?? 'Verified Documents';
|
||||||
|
@Input() widget: boolean = false;
|
||||||
|
@Input() width = 300;
|
||||||
|
@Input() height = 400;
|
||||||
|
|
||||||
|
verified: SimpleProof[] = [];
|
||||||
|
verifiedPage: SimpleProof[] = [];
|
||||||
|
isLoading: boolean = true;
|
||||||
|
error: boolean = false;
|
||||||
|
page = 1;
|
||||||
|
lastPage = 1;
|
||||||
|
itemsPerPage = 15;
|
||||||
|
paginationMaxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private servicesApiService: ServicesApiServices,
|
||||||
|
public sanitizer: DomSanitizer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadVerifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes.widget) {
|
||||||
|
this.itemsPerPage = this.widget ? 6 : 15;
|
||||||
|
}
|
||||||
|
if (changes.key) {
|
||||||
|
this.loadVerifications();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadVerifications(): void {
|
||||||
|
if (this.key) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.servicesApiService.getSimpleProofs$(this.key).pipe(
|
||||||
|
catchError(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.error = true;
|
||||||
|
return of({});
|
||||||
|
}),
|
||||||
|
).subscribe((data: Record<string, SimpleProof>) => {
|
||||||
|
if (Object.keys(data).length) {
|
||||||
|
this.verified = Object.keys(data).map(key => ({
|
||||||
|
...data[key],
|
||||||
|
file_name: data[key].file_name.replace('source-', '').replace('_', ' '),
|
||||||
|
key,
|
||||||
|
sanitized_url: this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, data[key]['simpleproof-url']) ?? ''),
|
||||||
|
})).sort((a, b) => b.key.localeCompare(a.key));
|
||||||
|
this.verifiedPage = this.verified.slice((this.page - 1) * this.itemsPerPage, this.page * this.itemsPerPage);
|
||||||
|
this.isLoading = false;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChange(page: number): void {
|
||||||
|
this.page = page;
|
||||||
|
this.verifiedPage = this.verified.slice((this.page - 1) * this.itemsPerPage, this.page * this.itemsPerPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.blocksSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
|
this.blocksSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
|
||||||
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && (block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee) > 0) {
|
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block?.extras?.medianFee > 0) {
|
||||||
this.calculateRatings(block);
|
this.calculateRatings(block);
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.cacheService.loadBlock(this.tx.status.block_height);
|
this.cacheService.loadBlock(this.tx.status.block_height);
|
||||||
|
|
||||||
const foundBlock = this.cacheService.getCachedBlock(this.tx.status.block_height) || null;
|
const foundBlock = this.cacheService.getCachedBlock(this.tx.status.block_height) || null;
|
||||||
if (foundBlock && (foundBlock?.extras?.effectiveMedianFee ?? foundBlock?.extras?.medianFee) > 0) {
|
if (foundBlock && foundBlock?.extras?.medianFee > 0) {
|
||||||
this.calculateRatings(foundBlock);
|
this.calculateRatings(foundBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
calculateRatings(block: BlockExtended) {
|
calculateRatings(block: BlockExtended) {
|
||||||
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
||||||
this.medianFeeNeeded = block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee;
|
this.medianFeeNeeded = block?.extras?.medianFee;
|
||||||
|
|
||||||
// Block not filled
|
// Block not filled
|
||||||
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
|
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
|
||||||
|
|||||||
@@ -196,8 +196,6 @@ export interface BlockExtension {
|
|||||||
minFee?: number;
|
minFee?: number;
|
||||||
maxFee?: number;
|
maxFee?: number;
|
||||||
feeRange?: number[];
|
feeRange?: number[];
|
||||||
effectiveMedianFee?: number;
|
|
||||||
effectiveFeeRange?: number[];
|
|
||||||
reward?: number;
|
reward?: number;
|
||||||
coinbaseRaw?: string;
|
coinbaseRaw?: string;
|
||||||
matchRate?: number;
|
matchRate?: number;
|
||||||
|
|||||||
@@ -61,10 +61,8 @@ export interface MempoolBlock {
|
|||||||
blockVSize: number;
|
blockVSize: number;
|
||||||
nTx: number;
|
nTx: number;
|
||||||
medianFee: number;
|
medianFee: number;
|
||||||
effectiveMedianFee?: number;
|
|
||||||
totalFees: number;
|
totalFees: number;
|
||||||
feeRange: number[];
|
feeRange: number[];
|
||||||
effectiveFeeRange?: number[];
|
|
||||||
index: number;
|
index: number;
|
||||||
isStack?: boolean;
|
isStack?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { RbfList } from '@components/rbf-list/rbf-list.component';
|
|||||||
import { ServerHealthComponent } from '@components/server-health/server-health.component';
|
import { ServerHealthComponent } from '@components/server-health/server-health.component';
|
||||||
import { ServerStatusComponent } from '@components/server-health/server-status.component';
|
import { ServerStatusComponent } from '@components/server-health/server-status.component';
|
||||||
import { FaucetComponent } from '@components/faucet/faucet.component'
|
import { FaucetComponent } from '@components/faucet/faucet.component'
|
||||||
|
import { SimpleProofWidgetComponent } from './components/simpleproof-widget/simpleproof-widget.component';
|
||||||
|
|
||||||
const browserWindow = window || {};
|
const browserWindow = window || {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -130,6 +131,13 @@ if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window['__env']?.customize?.dashboard.widgets?.some(w => w.component ==='simpleproof')) {
|
||||||
|
routes[0].children.push({
|
||||||
|
path: 'sp/verified',
|
||||||
|
component: SimpleProofWidgetComponent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild(routes)
|
RouterModule.forChild(routes)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMa
|
|||||||
import { IBackendInfo } from '@interfaces/websocket.interface';
|
import { IBackendInfo } from '@interfaces/websocket.interface';
|
||||||
import { Acceleration, AccelerationHistoryParams } from '@interfaces/node-api.interface';
|
import { Acceleration, AccelerationHistoryParams } from '@interfaces/node-api.interface';
|
||||||
import { AccelerationStats } from '@components/acceleration/acceleration-stats/acceleration-stats.component';
|
import { AccelerationStats } from '@components/acceleration/acceleration-stats/acceleration-stats.component';
|
||||||
|
import { SimpleProof } from '../components/simpleproof-widget/simpleproof-widget.component';
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
username: string;
|
username: string;
|
||||||
@@ -217,4 +218,8 @@ export class ServicesApiServices {
|
|||||||
getPaymentStatus$(orderId: string): Observable<any> {
|
getPaymentStatus$(orderId: string): Observable<any> {
|
||||||
return this.httpClient.get<any>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`, { observe: 'response' });
|
return this.httpClient.get<any>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`, { observe: 'response' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSimpleProofs$(key: string): Observable<Record<string, SimpleProof>> {
|
||||||
|
return this.httpClient.get<Record<string, SimpleProof>>(`${this.stateService.env.SERVICES_API}/sp/verified/${key}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ import { CalculatorComponent } from '@components/calculator/calculator.component
|
|||||||
import { BitcoinsatoshisPipe } from '@app/shared/pipes/bitcoinsatoshis.pipe';
|
import { BitcoinsatoshisPipe } from '@app/shared/pipes/bitcoinsatoshis.pipe';
|
||||||
import { HttpErrorComponent } from '@app/shared/components/http-error/http-error.component';
|
import { HttpErrorComponent } from '@app/shared/components/http-error/http-error.component';
|
||||||
import { TwitterWidgetComponent } from '@components/twitter-widget/twitter-widget.component';
|
import { TwitterWidgetComponent } from '@components/twitter-widget/twitter-widget.component';
|
||||||
|
import { SimpleProofWidgetComponent } from '@components/simpleproof-widget/simpleproof-widget.component';
|
||||||
import { FaucetComponent } from '@components/faucet/faucet.component';
|
import { FaucetComponent } from '@components/faucet/faucet.component';
|
||||||
import { TwitterLogin } from '@components/twitter-login/twitter-login.component';
|
import { TwitterLogin } from '@components/twitter-login/twitter-login.component';
|
||||||
import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component';
|
import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component';
|
||||||
@@ -235,6 +236,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
OrdDataComponent,
|
OrdDataComponent,
|
||||||
HttpErrorComponent,
|
HttpErrorComponent,
|
||||||
TwitterWidgetComponent,
|
TwitterWidgetComponent,
|
||||||
|
SimpleProofWidgetComponent,
|
||||||
FaucetComponent,
|
FaucetComponent,
|
||||||
TwitterLogin,
|
TwitterLogin,
|
||||||
BitcoinInvoiceComponent,
|
BitcoinInvoiceComponent,
|
||||||
@@ -369,6 +371,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
|||||||
OrdDataComponent,
|
OrdDataComponent,
|
||||||
HttpErrorComponent,
|
HttpErrorComponent,
|
||||||
TwitterWidgetComponent,
|
TwitterWidgetComponent,
|
||||||
|
SimpleProofWidgetComponent,
|
||||||
TwitterLogin,
|
TwitterLogin,
|
||||||
BitcoinInvoiceComponent,
|
BitcoinInvoiceComponent,
|
||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
|
|||||||
36
frontend/src/resources/sp.svg
Normal file
36
frontend/src/resources/sp.svg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
id="Layer_1"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 492.10001 575.79999"
|
||||||
|
width="492.10001"
|
||||||
|
height="575.79999"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Generator: Adobe Illustrator 29.0.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 186) -->
|
||||||
|
<defs
|
||||||
|
id="defs184">
|
||||||
|
<style
|
||||||
|
id="style182">
|
||||||
|
.st0 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1 {
|
||||||
|
fill: #f88e2b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
id="g216"
|
||||||
|
transform="translate(-159.5,-152.1)">
|
||||||
|
<polygon
|
||||||
|
class="st0"
|
||||||
|
points="296.6,375.6 296.6,459.1 404.9,524.2 651.6,378.5 651.6,294.6 651.6,294.5 405.3,440.4 "
|
||||||
|
id="polygon212" />
|
||||||
|
<polygon
|
||||||
|
class="st1"
|
||||||
|
points="405.5,644.5 231.3,542 231.3,335.6 405.5,235 520.9,301.7 592.1,259.8 405.5,152.1 159.5,294.1 159.5,583.1 405.5,727.9 651.6,583.1 651.6,447.1 579.7,489.4 579.7,542 "
|
||||||
|
id="polygon214" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 996 B |
Reference in New Issue
Block a user