Compare commits
18 Commits
mononaut/d
...
translatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a62a3cc774 | ||
|
|
9660723e96 | ||
|
|
0a255d7fe5 | ||
|
|
ca0a8aee49 | ||
|
|
f142b421f9 | ||
|
|
c3686a5500 | ||
|
|
9fbbe4980d | ||
|
|
0611773647 | ||
|
|
7740908a4c | ||
|
|
68ea7c59f3 | ||
|
|
915f7a6c27 | ||
|
|
e18c572549 | ||
|
|
25133d8505 | ||
|
|
9f5666f410 | ||
|
|
6553344489 | ||
|
|
37ddc29c2c | ||
|
|
a5c67b5ca1 | ||
|
|
cdc4a430cd |
@@ -244,7 +244,7 @@ class Blocks {
|
||||
*/
|
||||
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
|
||||
const coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||
|
||||
|
||||
const blk: Partial<BlockExtended> = Object.assign({}, block);
|
||||
const extras: Partial<BlockExtension> = {};
|
||||
|
||||
@@ -268,17 +268,15 @@ class Blocks {
|
||||
extras.segwitTotalWeight = 0;
|
||||
} else {
|
||||
const stats: IBitcoinApi.BlockStats = await bitcoinClient.getBlockStats(block.id);
|
||||
const feeStats = {
|
||||
let feeStats = {
|
||||
medianFee: stats.feerate_percentiles[2], // 50th percentiles
|
||||
feeRange: [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(),
|
||||
};
|
||||
if (transactions?.length > 1) {
|
||||
feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||
}
|
||||
extras.medianFee = feeStats.medianFee;
|
||||
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.avgFee = stats.avgfee;
|
||||
extras.avgFeeRate = stats.avgfeerate;
|
||||
@@ -298,7 +296,7 @@ class Blocks {
|
||||
extras.medianFeeAmt = extras.feePercentiles[3];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extras.virtualSize = block.weight / 4.0;
|
||||
if (coinbaseTx?.vout.length > 0) {
|
||||
extras.coinbaseAddress = coinbaseTx.vout[0].scriptpubkey_address ?? null;
|
||||
@@ -1318,8 +1316,6 @@ class Blocks {
|
||||
avg_fee_rate: block.extras.avgFeeRate ?? null,
|
||||
median_fee_rate: block.extras.medianFee ?? 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_input_amt: block.extras.totalInputAmt ?? null,
|
||||
total_outputs: block.extras.totalOutputs ?? null,
|
||||
@@ -1382,17 +1378,6 @@ class Blocks {
|
||||
'perc_90': cleanBlock.fee_rate_percentiles[5],
|
||||
'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
|
||||
// latest state from core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
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 { NodeSocket } from '../repositories/NodesSocketsRepository';
|
||||
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 {
|
||||
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 {
|
||||
effective_median: medianFeeRate,
|
||||
effective_range: [
|
||||
medianFee: medianFeeRate,
|
||||
feeRange: [
|
||||
minFee,
|
||||
[10,25,50,75,90].map(n => Common.getNthPercentile(n, sortedTxs).rate),
|
||||
maxFee,
|
||||
@@ -1159,16 +1150,16 @@ export class OnlineFeeStatsCalculator {
|
||||
}
|
||||
return {
|
||||
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,
|
||||
effective_range: this.feeRange.map(f => f.avg),
|
||||
feeRange: this.feeRange.map(f => f.avg),
|
||||
};
|
||||
}
|
||||
|
||||
getFeeStats(): EffectiveFeeStats {
|
||||
const stats = this.getRawFeeStats();
|
||||
stats.effective_range[0] = stats.minFee;
|
||||
stats.effective_range[stats.effective_range.length - 1] = stats.maxFee;
|
||||
stats.feeRange[0] = stats.minFee;
|
||||
stats.feeRange[stats.feeRange.length - 1] = stats.maxFee;
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 95;
|
||||
private static currentVersion = 94;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@@ -1118,16 +1118,6 @@ class DatabaseMigration {
|
||||
}
|
||||
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 {
|
||||
const medianFee = pBlock.effectiveMedianFee ?? pBlock.medianFee;
|
||||
const useFee = previousFee ? (medianFee + previousFee) / 2 : medianFee;
|
||||
const useFee = previousFee ? (pBlock.medianFee + previousFee) / 2 : pBlock.medianFee;
|
||||
if (pBlock.blockVSize <= 500000) {
|
||||
return this.defaultFee;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
|
||||
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 config from '../config';
|
||||
import { Worker } from 'worker_threads';
|
||||
@@ -33,8 +33,6 @@ class MempoolBlocks {
|
||||
totalFees: block.totalFees,
|
||||
medianFee: block.medianFee,
|
||||
feeRange: block.feeRange,
|
||||
effectiveMedianFee: block.effectiveMedianFee,
|
||||
effectiveFeeRange: block.effectiveFeeRange,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -529,7 +527,7 @@ class MempoolBlocks {
|
||||
totalSize,
|
||||
totalWeight,
|
||||
totalFees,
|
||||
(hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getFeeStats() : undefined,
|
||||
(hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -543,20 +541,17 @@ class MempoolBlocks {
|
||||
return mempoolBlocks;
|
||||
}
|
||||
|
||||
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, effectiveFeeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
|
||||
const feeStats = Common.calcFeeStatistics(transactions);
|
||||
if (!effectiveFeeStats) {
|
||||
effectiveFeeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
|
||||
if (!feeStats) {
|
||||
feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||
}
|
||||
return {
|
||||
blockSize: totalSize,
|
||||
blockVSize: (totalWeight / 4), // fractional vsize to avoid rounding errors
|
||||
nTx: transactionIds.length,
|
||||
totalFees: totalFees,
|
||||
medianFee: feeStats.median,
|
||||
feeRange: feeStats.range,
|
||||
effectiveMedianFee: effectiveFeeStats.effective_median,
|
||||
effectiveFeeRange: effectiveFeeStats.effective_range,
|
||||
medianFee: feeStats.medianFee, // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||
feeRange: feeStats.feeRange, //Common.getFeesInRange(transactions, rangeLength),
|
||||
transactionIds: transactionIds,
|
||||
transactions: transactions.map((tx) => Common.classifyTransaction(tx)),
|
||||
};
|
||||
|
||||
@@ -73,8 +73,6 @@ export interface MempoolBlock {
|
||||
medianFee: number;
|
||||
totalFees: number;
|
||||
feeRange: number[];
|
||||
effectiveMedianFee?: number;
|
||||
effectiveFeeRange?: number[];
|
||||
}
|
||||
|
||||
export interface MempoolBlockWithTransactions extends MempoolBlock {
|
||||
@@ -290,10 +288,8 @@ export const TransactionFlags = {
|
||||
|
||||
export interface BlockExtension {
|
||||
totalFees: number;
|
||||
medianFee: number; // core median fee rate
|
||||
feeRange: number[]; // core fee rate percentiles
|
||||
effectiveMedianFee?: number; // effective median fee rate
|
||||
effectiveFeeRange?: number[]; // effective fee rate percentiles
|
||||
medianFee: number; // median fee rate
|
||||
feeRange: number[]; // fee rate percentiles
|
||||
reward: number;
|
||||
matchRate: number | null;
|
||||
expectedFees: number | null;
|
||||
@@ -373,18 +369,9 @@ export interface MempoolStats {
|
||||
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 {
|
||||
effective_median: number; // median effective fee rate by weight
|
||||
effective_range: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
|
||||
medianFee: number; // median effective fee rate
|
||||
feeRange: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
|
||||
}
|
||||
|
||||
export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
|
||||
|
||||
@@ -315,12 +315,12 @@ class AccelerationRepository {
|
||||
Infinity
|
||||
);
|
||||
const feeStats = Common.calcEffectiveFeeStatistics(template);
|
||||
boostRate = feeStats.effective_median;
|
||||
boostRate = feeStats.medianFee;
|
||||
}
|
||||
const accelerationSummaries = accelerations.map(acc => ({
|
||||
...acc,
|
||||
pools: acc.pools,
|
||||
}));
|
||||
}))
|
||||
for (const acc of accelerations) {
|
||||
if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) {
|
||||
const tx = blockTxs[acc.txid];
|
||||
|
||||
@@ -33,8 +33,6 @@ interface DatabaseBlock {
|
||||
totalFees: number;
|
||||
medianFee: number;
|
||||
feeRange: string;
|
||||
effectiveMedianFee?: number;
|
||||
effectiveFeeRange?: string;
|
||||
reward: number;
|
||||
poolId: number;
|
||||
poolName: string;
|
||||
@@ -79,8 +77,6 @@ const BLOCK_DB_FIELDS = `
|
||||
blocks.fees AS totalFees,
|
||||
blocks.median_fee AS medianFee,
|
||||
blocks.fee_span AS feeRange,
|
||||
blocks.effective_median_fee AS effectiveMedianFee,
|
||||
blocks.effective_fee_span AS effectiveFeeRange,
|
||||
blocks.reward,
|
||||
pools.unique_id AS poolId,
|
||||
pools.name AS poolName,
|
||||
@@ -112,7 +108,7 @@ class BlocksRepository {
|
||||
/**
|
||||
* 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 truncatedCoinbaseSignatureAscii = block?.extras?.coinbaseSignatureAscii?.substring(0, 500);
|
||||
|
||||
@@ -121,7 +117,6 @@ class BlocksRepository {
|
||||
height, hash, blockTimestamp, size,
|
||||
weight, tx_count, coinbase_raw, difficulty,
|
||||
pool_id, fees, fee_span, median_fee,
|
||||
effective_fee_span, effective_median_fee,
|
||||
reward, version, bits, nonce,
|
||||
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
||||
median_timestamp, header, coinbase_address, coinbase_addresses,
|
||||
@@ -133,7 +128,6 @@ class BlocksRepository {
|
||||
?, ?, FROM_UNIXTIME(?), ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
FROM_UNIXTIME(?), ?, ?, ?,
|
||||
@@ -161,8 +155,6 @@ class BlocksRepository {
|
||||
block.extras.totalFees,
|
||||
JSON.stringify(block.extras.feeRange),
|
||||
block.extras.medianFee,
|
||||
block.extras.effectiveFeeRange ? JSON.stringify(block.extras.effectiveFeeRange) : null,
|
||||
block.extras.effectiveMedianFee,
|
||||
block.extras.reward,
|
||||
block.version,
|
||||
block.bits,
|
||||
@@ -976,16 +968,16 @@ class BlocksRepository {
|
||||
|
||||
/**
|
||||
* Save indexed effective fee statistics
|
||||
*
|
||||
* @param id
|
||||
* @param feeStats
|
||||
*
|
||||
* @param id
|
||||
* @param feeStats
|
||||
*/
|
||||
public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise<void> {
|
||||
try {
|
||||
await DB.query(`
|
||||
UPDATE blocks SET effective_median_fee = ?, effective_fee_span = ?
|
||||
UPDATE blocks SET median_fee = ?, fee_span = ?
|
||||
WHERE hash = ?`,
|
||||
[feeStats.effective_median, JSON.stringify(feeStats.effective_range), id]
|
||||
[feeStats.medianFee, JSON.stringify(feeStats.feeRange), id]
|
||||
);
|
||||
} catch (e) {
|
||||
logger.err(`Cannot update block fee stats. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
@@ -1073,13 +1065,11 @@ class BlocksRepository {
|
||||
blk.weight = dbBlk.weight;
|
||||
blk.previousblockhash = dbBlk.previousblockhash;
|
||||
blk.mediantime = dbBlk.mediantime;
|
||||
|
||||
|
||||
// BlockExtension
|
||||
extras.totalFees = dbBlk.totalFees;
|
||||
extras.medianFee = dbBlk.medianFee;
|
||||
extras.feeRange = JSON.parse(dbBlk.feeRange);
|
||||
extras.effectiveMedianFee = dbBlk.effectiveMedianFee;
|
||||
extras.effectiveFeeRange = dbBlk.effectiveFeeRange ? JSON.parse(dbBlk.effectiveFeeRange) : null;
|
||||
extras.reward = dbBlk.reward;
|
||||
extras.pool = {
|
||||
id: dbBlk.poolId,
|
||||
|
||||
@@ -613,7 +613,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2));
|
||||
if (!verificationToken) {
|
||||
if (!verificationToken || !verificationToken.token) {
|
||||
console.error(`SCA verification failed`);
|
||||
this.accelerateError = 'SCA Verification Failed. Payment Declined.';
|
||||
this.processing = false;
|
||||
@@ -623,10 +623,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
this.servicesApiService.accelerateWithGooglePay$(
|
||||
this.tx.txid,
|
||||
tokenResult.token,
|
||||
verificationToken,
|
||||
verificationToken.token,
|
||||
cardTag,
|
||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||
costUSD
|
||||
costUSD,
|
||||
verificationToken.userChallenged
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.processing = false;
|
||||
@@ -752,9 +753,9 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Required in SCA Mandated Regions: Learn more at https://developer.squareup.com/docs/sca-overview
|
||||
* https://developer.squareup.com/docs/sca-overview
|
||||
*/
|
||||
async $verifyBuyer(payments, token, details, amount) {
|
||||
async $verifyBuyer(payments, token, details, amount): Promise<{token: string, userChallenged: boolean}> {
|
||||
const verificationDetails = {
|
||||
amount: amount,
|
||||
currencyCode: 'USD',
|
||||
@@ -774,7 +775,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
token,
|
||||
verificationDetails,
|
||||
);
|
||||
return verificationResults.token;
|
||||
return verificationResults;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
|
||||
aggregatedHistory$: Observable<any>;
|
||||
statsSubscription: Subscription;
|
||||
aggregatedHistorySubscription: Subscription;
|
||||
fragmentSubscription: Subscription;
|
||||
isLoading = true;
|
||||
formatNumber = formatNumber;
|
||||
timespan = '';
|
||||
@@ -79,8 +81,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
}
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
this.route.fragment.subscribe((fragment) => {
|
||||
|
||||
this.fragmentSubscription = this.route.fragment.subscribe((fragment) => {
|
||||
if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) {
|
||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||
}
|
||||
@@ -113,7 +115,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
share(),
|
||||
);
|
||||
|
||||
this.aggregatedHistory$.subscribe();
|
||||
this.aggregatedHistorySubscription = this.aggregatedHistory$.subscribe();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@@ -335,8 +337,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.statsSubscription) {
|
||||
this.statsSubscription.unsubscribe();
|
||||
}
|
||||
this.aggregatedHistorySubscription?.unsubscribe();
|
||||
this.fragmentSubscription?.unsubscribe();
|
||||
this.statsSubscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,13 +172,19 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
ngOnDestroy(): void {
|
||||
if (this.animationFrameRequest) {
|
||||
cancelAnimationFrame(this.animationFrameRequest);
|
||||
clearTimeout(this.animationHeartBeat);
|
||||
}
|
||||
clearTimeout(this.animationHeartBeat);
|
||||
if (this.canvas) {
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||
this.themeChangedSubscription?.unsubscribe();
|
||||
}
|
||||
if (this.scene) {
|
||||
this.scene.destroy();
|
||||
}
|
||||
this.vertexArray.destroy();
|
||||
this.vertexArray = null;
|
||||
this.themeChangedSubscription?.unsubscribe();
|
||||
this.searchSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
clear(direction): void {
|
||||
@@ -447,7 +453,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
}
|
||||
this.applyQueuedUpdates();
|
||||
// skip re-render if there's no change to the scene
|
||||
if (this.scene && this.gl) {
|
||||
if (this.scene && this.gl && this.vertexArray) {
|
||||
/* SET UP SHADER UNIFORMS */
|
||||
// screen dimensions
|
||||
this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight);
|
||||
@@ -489,9 +495,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
||||
if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) {
|
||||
this.doRun();
|
||||
} else {
|
||||
if (this.animationHeartBeat) {
|
||||
clearTimeout(this.animationHeartBeat);
|
||||
}
|
||||
clearTimeout(this.animationHeartBeat);
|
||||
this.animationHeartBeat = window.setTimeout(() => {
|
||||
this.start();
|
||||
}, 1000);
|
||||
|
||||
@@ -19,6 +19,7 @@ export class FastVertexArray {
|
||||
freeSlots: number[];
|
||||
lastSlot: number;
|
||||
dirty = false;
|
||||
destroyed = false;
|
||||
|
||||
constructor(length, stride) {
|
||||
this.length = length;
|
||||
@@ -32,6 +33,9 @@ export class FastVertexArray {
|
||||
}
|
||||
|
||||
insert(sprite: TxSprite): number {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.count++;
|
||||
|
||||
let position;
|
||||
@@ -45,11 +49,14 @@ export class FastVertexArray {
|
||||
}
|
||||
}
|
||||
this.sprites[position] = sprite;
|
||||
return position;
|
||||
this.dirty = true;
|
||||
return position;
|
||||
}
|
||||
|
||||
remove(index: number): void {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.count--;
|
||||
this.clearData(index);
|
||||
this.freeSlots.push(index);
|
||||
@@ -61,20 +68,26 @@ export class FastVertexArray {
|
||||
}
|
||||
|
||||
setData(index: number, dataChunk: number[]): void {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.data.set(dataChunk, (index * this.stride));
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
clearData(index: number): void {
|
||||
private clearData(index: number): void {
|
||||
this.data.fill(0, (index * this.stride), ((index + 1) * this.stride));
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
getData(index: number): Float32Array {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
return this.data.subarray(index, this.stride);
|
||||
}
|
||||
|
||||
expand(): void {
|
||||
private expand(): void {
|
||||
this.length *= 2;
|
||||
const newData = new Float32Array(this.length * this.stride);
|
||||
newData.set(this.data);
|
||||
@@ -82,7 +95,7 @@ export class FastVertexArray {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
compact(): void {
|
||||
private compact(): void {
|
||||
// New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512)
|
||||
const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count))));
|
||||
if (newLength !== this.length) {
|
||||
@@ -110,4 +123,13 @@ export class FastVertexArray {
|
||||
getVertexData(): Float32Array {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.data = null;
|
||||
this.sprites = null;
|
||||
this.freeSlots = null;
|
||||
this.lastSlot = 0;
|
||||
this.dirty = false;
|
||||
this.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export class BlockViewComponent implements OnInit, OnDestroy {
|
||||
this.isLoadingBlock = false;
|
||||
this.isLoadingOverview = true;
|
||||
}),
|
||||
shareReplay(1)
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
);
|
||||
|
||||
this.overviewSubscription = block$.pipe(
|
||||
@@ -176,5 +176,8 @@ export class BlockViewComponent implements OnInit, OnDestroy {
|
||||
if (this.queryParamsSubscription) {
|
||||
this.queryParamsSubscription.unsubscribe();
|
||||
}
|
||||
if (this.blockGraph) {
|
||||
this.blockGraph.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
<td i18n="block.weight">Weight</td>
|
||||
<td [innerHTML]="'‎' + (block?.weight | wuBytes: 2)"></td>
|
||||
</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>~<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>
|
||||
<ng-template [ngIf]="fees !== undefined">
|
||||
<tr>
|
||||
|
||||
@@ -117,7 +117,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
||||
this.openGraphService.waitOver('block-data-' + this.rawId);
|
||||
}),
|
||||
throttleTime(50, asyncScheduler, { leading: true, trailing: true }),
|
||||
shareReplay(1)
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
);
|
||||
|
||||
this.overviewSubscription = block$.pipe(
|
||||
|
||||
@@ -132,11 +132,11 @@
|
||||
<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>
|
||||
</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>~<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">
|
||||
<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"
|
||||
placement="bottom"></app-fiat>
|
||||
</span>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { ActivatedRoute, ParamMap, Params, Router } from '@angular/router';
|
||||
import { ElectrsApiService } from '@app/services/electrs-api.service';
|
||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter, take } from 'rxjs/operators';
|
||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { SeoService } from '@app/services/seo.service';
|
||||
@@ -68,6 +68,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||
numUnexpected: number = 0;
|
||||
mode: 'projected' | 'actual' = 'projected';
|
||||
currentQueryParams: Params;
|
||||
|
||||
overviewSubscription: Subscription;
|
||||
accelerationsSubscription: Subscription;
|
||||
@@ -80,8 +81,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
timeLtr: boolean;
|
||||
childChangeSubscription: Subscription;
|
||||
auditPrefSubscription: Subscription;
|
||||
isAuditEnabledSubscription: Subscription;
|
||||
oobSubscription: Subscription;
|
||||
|
||||
priceSubscription: Subscription;
|
||||
blockConversion: Price;
|
||||
|
||||
@@ -118,7 +119,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.setAuditAvailable(this.auditSupported);
|
||||
|
||||
if (this.auditSupported) {
|
||||
this.isAuditEnabledFromParam().subscribe(auditParam => {
|
||||
this.isAuditEnabledSubscription = this.isAuditEnabledFromParam().subscribe(auditParam => {
|
||||
if (this.auditParamEnabled) {
|
||||
this.auditModeEnabled = auditParam;
|
||||
} else {
|
||||
@@ -281,7 +282,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}),
|
||||
throttleTime(300, asyncScheduler, { leading: true, trailing: true }),
|
||||
shareReplay(1)
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
);
|
||||
|
||||
this.overviewSubscription = this.block$.pipe(
|
||||
@@ -363,6 +364,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
.subscribe((network) => this.network = network);
|
||||
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||
this.currentQueryParams = params;
|
||||
if (params.showDetails === 'true') {
|
||||
this.showDetails = true;
|
||||
} else {
|
||||
@@ -414,6 +416,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy(): void {
|
||||
this.stateService.markBlock$.next({});
|
||||
this.overviewSubscription?.unsubscribe();
|
||||
this.accelerationsSubscription?.unsubscribe();
|
||||
this.keyNavigationSubscription?.unsubscribe();
|
||||
this.blocksSubscription?.unsubscribe();
|
||||
this.cacheBlocksSubscription?.unsubscribe();
|
||||
@@ -421,8 +424,16 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.queryParamsSubscription?.unsubscribe();
|
||||
this.timeLtrSubscription?.unsubscribe();
|
||||
this.childChangeSubscription?.unsubscribe();
|
||||
this.priceSubscription?.unsubscribe();
|
||||
this.auditPrefSubscription?.unsubscribe();
|
||||
this.isAuditEnabledSubscription?.unsubscribe();
|
||||
this.oobSubscription?.unsubscribe();
|
||||
this.priceSubscription?.unsubscribe();
|
||||
this.blockGraphProjected.forEach(graph => {
|
||||
graph.destroy();
|
||||
});
|
||||
this.blockGraphActual.forEach(graph => {
|
||||
graph.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
// TODO - Refactor this.fees/this.reward for liquid because it is not
|
||||
@@ -733,19 +744,18 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
toggleAuditMode(): void {
|
||||
this.stateService.hideAudit.next(this.auditModeEnabled);
|
||||
|
||||
this.route.queryParams.subscribe(params => {
|
||||
const queryParams = { ...params };
|
||||
delete queryParams['audit'];
|
||||
const queryParams = { ...this.currentQueryParams };
|
||||
delete queryParams['audit'];
|
||||
|
||||
let newUrl = this.router.url.split('?')[0];
|
||||
const queryString = new URLSearchParams(queryParams).toString();
|
||||
if (queryString) {
|
||||
newUrl += '?' + queryString;
|
||||
}
|
||||
|
||||
this.location.replaceState(newUrl);
|
||||
});
|
||||
let newUrl = this.router.url.split('?')[0];
|
||||
const queryString = new URLSearchParams(queryParams).toString();
|
||||
if (queryString) {
|
||||
newUrl += '?' + queryString;
|
||||
}
|
||||
this.location.replaceState(newUrl);
|
||||
|
||||
// avoid duplicate subscriptions
|
||||
this.auditPrefSubscription?.unsubscribe();
|
||||
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
|
||||
this.auditModeEnabled = !hide;
|
||||
this.showAudit = this.auditAvailable && this.auditModeEnabled;
|
||||
@@ -762,7 +772,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
return this.route.queryParams.pipe(
|
||||
map(params => {
|
||||
this.auditParamEnabled = 'audit' in params;
|
||||
|
||||
|
||||
return this.auditParamEnabled ? !(params['audit'] === 'false') : true;
|
||||
})
|
||||
);
|
||||
@@ -792,9 +802,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getMinBlockFee(block: BlockExtended): number {
|
||||
if (block?.extras?.effectiveFeeRange) {
|
||||
return block.extras.effectiveFeeRange[0];
|
||||
}
|
||||
if (block?.extras?.feeRange) {
|
||||
// heuristic to check if feeRange is adjusted for effective rates
|
||||
if (block.extras.medianFee === block.extras.feeRange[3]) {
|
||||
@@ -807,9 +814,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getMaxBlockFee(block: BlockExtended): number {
|
||||
if (block?.extras?.effectiveFeeRange) {
|
||||
return block.extras.effectiveFeeRange[block.extras.effectiveFeeRange.length - 1];
|
||||
}
|
||||
if (block?.extras?.feeRange) {
|
||||
return block.extras.feeRange[block.extras.feeRange.length - 1];
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="block-body">
|
||||
<ng-container *ngIf="!minimal">
|
||||
<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>
|
||||
<ng-template #emptyfees>
|
||||
<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 {
|
||||
if (block?.extras?.effectiveFeeRange) {
|
||||
return block.extras.effectiveFeeRange[0];
|
||||
}
|
||||
if (block?.extras?.feeRange) {
|
||||
// heuristic to check if feeRange is adjusted for effective rates
|
||||
if (block.extras.medianFee === block.extras.feeRange[3]) {
|
||||
@@ -429,9 +426,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
getMaxBlockFee(block: BlockExtended): number {
|
||||
if (block?.extras?.effectiveFeeRange) {
|
||||
return block.extras.effectiveFeeRange[block.extras.effectiveFeeRange.length - 1];
|
||||
}
|
||||
if (block?.extras?.feeRange) {
|
||||
return block.extras.feeRange[block.extras.feeRange.length - 1];
|
||||
}
|
||||
|
||||
@@ -162,6 +162,9 @@ export class EightBlocksComponent implements OnInit, OnDestroy {
|
||||
this.cacheBlocksSubscription?.unsubscribe();
|
||||
this.networkChangedSubscription?.unsubscribe();
|
||||
this.queryParamsSubscription?.unsubscribe();
|
||||
this.blockGraphs.forEach(graph => {
|
||||
graph.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
shiftTestBlocks(): void {
|
||||
|
||||
@@ -120,6 +120,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.blockGraph?.destroy();
|
||||
this.blockSub.unsubscribe();
|
||||
this.timeLtrSubscription.unsubscribe();
|
||||
this.websocketService.stopTrackMempoolBlock();
|
||||
|
||||
@@ -57,9 +57,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||
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.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.`);
|
||||
|
||||
@@ -171,8 +171,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
block.index = this.blockIndex + i;
|
||||
block.height = lastBlock.height + i + 1;
|
||||
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);
|
||||
|
||||
@@ -240,7 +240,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
retry({ count: 2, delay: 2000 }),
|
||||
// Try again until we either get a valid response, or the transaction is confirmed
|
||||
repeat({ delay: 2000 }),
|
||||
filter((transactionTimes) => transactionTimes?.length && transactionTimes[0] > 0 && !this.tx.status?.confirmed),
|
||||
filter((transactionTimes) => transactionTimes?.[0] > 0 || this.tx.status?.confirmed),
|
||||
take(1),
|
||||
)),
|
||||
)
|
||||
|
||||
@@ -202,12 +202,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
for (const address of this.addresses) {
|
||||
switch (address.length) {
|
||||
case 130: {
|
||||
if (v.scriptpubkey === '21' + address + 'ac') {
|
||||
if (v.scriptpubkey === '41' + address + 'ac') {
|
||||
return v.value;
|
||||
}
|
||||
} break;
|
||||
case 66: {
|
||||
if (v.scriptpubkey === '41' + address + 'ac') {
|
||||
if (v.scriptpubkey === '21' + address + 'ac') {
|
||||
return v.value;
|
||||
}
|
||||
} break;
|
||||
@@ -224,12 +224,12 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
for (const address of this.addresses) {
|
||||
switch (address.length) {
|
||||
case 130: {
|
||||
if (v.prevout?.scriptpubkey === '21' + address + 'ac') {
|
||||
if (v.prevout?.scriptpubkey === '41' + address + 'ac') {
|
||||
return v.prevout?.value;
|
||||
}
|
||||
} break;
|
||||
case 66: {
|
||||
if (v.prevout?.scriptpubkey === '41' + address + 'ac') {
|
||||
if (v.prevout?.scriptpubkey === '21' + address + 'ac') {
|
||||
return v.prevout?.value;
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -30,7 +30,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
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.cd.markForCheck();
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.cacheService.loadBlock(this.tx.status.block_height);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
calculateRatings(block: BlockExtended) {
|
||||
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
|
||||
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Env, StateService } from '@app/services/state.service';
|
||||
import { restApiDocsData } from '@app/docs/api-docs/api-docs-data';
|
||||
import { restApiDocsData, wsApiDocsData } from '@app/docs/api-docs/api-docs-data';
|
||||
import { faqData } from '@app/docs/api-docs/api-docs-data';
|
||||
|
||||
@Component({
|
||||
@@ -28,6 +28,8 @@ export class ApiDocsNavComponent implements OnInit {
|
||||
this.auditEnabled = this.env.AUDIT;
|
||||
if (this.whichTab === 'rest') {
|
||||
this.tabData = restApiDocsData;
|
||||
} else if (this.whichTab === 'websocket') {
|
||||
this.tabData = wsApiDocsData;
|
||||
} else if (this.whichTab === 'faq') {
|
||||
this.tabData = faqData;
|
||||
}
|
||||
|
||||
@@ -108,18 +108,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="websocketAPI" *ngIf="( whichTab === 'websocket' )">
|
||||
<div class="api-category">
|
||||
<div class="websocket">
|
||||
<div class="endpoint">
|
||||
<div class="subtitle" i18n="Api docs endpoint">Endpoint</div>
|
||||
{{ wrapUrl(network.val, wsDocs, true) }}
|
||||
<div id="websocketAPI" *ngIf="whichTab === 'websocket'">
|
||||
|
||||
<div id="doc-nav-desktop" class="hide-on-mobile" [ngClass]="desktopDocsNavPosition">
|
||||
<app-api-docs-nav (navLinkClickEvent)="anchorLinkClick( $event )" [network]="{ val: network$ | async }" [whichTab]="whichTab"></app-api-docs-nav>
|
||||
</div>
|
||||
|
||||
<div class="doc-content">
|
||||
|
||||
<div id="enterprise-cta-mobile" *ngIf="officialMempoolInstance && showMobileEnterpriseUpsell">
|
||||
<p>Get higher API limits with <span class="no-line-break">Mempool Enterprise®</span></p>
|
||||
<div class="button-group">
|
||||
<a class="btn btn-small btn-secondary" (click)="showMobileEnterpriseUpsell = false">No Thanks</a>
|
||||
<a class="btn btn-small btn-purple" href="https://mempool.space/enterprise">More Info <fa-icon [icon]="['fas', 'angle-right']" [styles]="{'font-size': '12px'}"></fa-icon></a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div i18n="api-docs.websocket.websocket">Default push: <code>{{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }}</code> to express what you want pushed. Available: <code>blocks</code>, <code>mempool-blocks</code>, <code>live-2h-chart</code>, and <code>stats</code>.<br><br>Push transactions related to address: <code>{{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }}</code> to receive all new transactions containing that address as input or output. Returns an array of transactions. <code>address-transactions</code> for new mempool transactions, and <code>block-transactions</code> for new block confirmed transactions.</div>
|
||||
</div>
|
||||
|
||||
<p class="doc-welcome-note">Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title-websocket">Websocket service</ng-container> running at {{ websocketUrl(network.val) }}.</p>
|
||||
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that usage limits apply to our WebSocket API. Consider an <a href="https://mempool.space/enterprise">enterprise sponsorship</a> if you need higher API limits, such as higher tracking limits.</p>
|
||||
|
||||
<div class="doc-item-container" *ngFor="let item of wsDocs">
|
||||
<div *ngIf="!item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance )">
|
||||
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
||||
<div *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" class="endpoint-container" id="{{ item.fragment }}">
|
||||
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick({event: $event, fragment: item.fragment})">{{ item.title }} <span>{{ item.category }}</span></a>
|
||||
<div class="endpoint-content">
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Description</div>
|
||||
<div [innerHTML]="item.description.default" i18n></div>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="subtitle" i18n>Payload</div>
|
||||
<pre><code [innerText]="item.payload"></code></pre>
|
||||
</div>
|
||||
<app-code-template [hostname]="hostname" [baseNetworkUrl]="baseNetworkUrl" [method]="item.httpRequestMethod" [code]="item.codeExample.default" [network]="network.val" [showCodeExample]="item.showJsExamples"></app-code-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-code-template [method]="'websocket'" [hostname]="hostname" [code]="wsDocs" [network]="network.val" [showCodeExample]="wsDocs.showJsExamples"></app-code-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -470,3 +470,21 @@ dd {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--bg);
|
||||
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
font-size: 87.5%;
|
||||
color: #f18920;
|
||||
background-color: var(--bg);
|
||||
padding: 30px;
|
||||
code{
|
||||
background-color: transparent;
|
||||
white-space: break-spaces;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
||||
if (document.getElementById( targetId + "-tab-header" )) {
|
||||
tabHeaderHeight = document.getElementById( targetId + "-tab-header" ).scrollHeight;
|
||||
}
|
||||
if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) ) && targetId ) {
|
||||
if( ( window.innerWidth <= 992 ) && ( ( this.whichTab === 'rest' ) || ( this.whichTab === 'faq' ) || ( this.whichTab === 'websocket' ) ) && targetId ) {
|
||||
const endpointContainerEl = document.querySelector<HTMLElement>( "#" + targetId );
|
||||
const endpointContentEl = document.querySelector<HTMLElement>( "#" + targetId + " .endpoint-content" );
|
||||
const endPointContentElHeight = endpointContentEl.clientHeight;
|
||||
@@ -207,13 +207,29 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
||||
text = text.replace('%{' + indexNumber + '}', curlText);
|
||||
}
|
||||
|
||||
if (websocket) {
|
||||
const wsHostname = this.hostname.replace('https://', 'wss://');
|
||||
wsHostname.replace('http://', 'ws://');
|
||||
return `${wsHostname}${curlNetwork}${text}`;
|
||||
}
|
||||
return `${this.hostname}${curlNetwork}${text}`;
|
||||
}
|
||||
|
||||
websocketUrl(network: string) {
|
||||
let curlNetwork = '';
|
||||
if (this.env.BASE_MODULE === 'mempool') {
|
||||
if (!['', 'mainnet'].includes(network)) {
|
||||
curlNetwork = `/${network}`;
|
||||
}
|
||||
} else if (this.env.BASE_MODULE === 'liquid') {
|
||||
if (!['', 'liquid'].includes(network)) {
|
||||
curlNetwork = `/${network}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (network === this.env.ROOT_NETWORK) {
|
||||
curlNetwork = '';
|
||||
}
|
||||
|
||||
let wsHostname = this.hostname.replace('https://', 'wss://');
|
||||
wsHostname = wsHostname.replace('http://', 'ws://');
|
||||
return `${wsHostname}${curlNetwork}/api/v1/ws`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -196,8 +196,6 @@ export interface BlockExtension {
|
||||
minFee?: number;
|
||||
maxFee?: number;
|
||||
feeRange?: number[];
|
||||
effectiveMedianFee?: number;
|
||||
effectiveFeeRange?: number[];
|
||||
reward?: number;
|
||||
coinbaseRaw?: string;
|
||||
matchRate?: number;
|
||||
|
||||
@@ -61,10 +61,8 @@ export interface MempoolBlock {
|
||||
blockVSize: number;
|
||||
nTx: number;
|
||||
medianFee: number;
|
||||
effectiveMedianFee?: number;
|
||||
totalFees: number;
|
||||
feeRange: number[];
|
||||
effectiveFeeRange?: number[];
|
||||
index: number;
|
||||
isStack?: boolean;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ export interface IUser {
|
||||
subscription_tag: string;
|
||||
status: 'pending' | 'verified' | 'disabled';
|
||||
features: string | null;
|
||||
fullName: string | null;
|
||||
countryCode: string | null;
|
||||
imageMd5: string;
|
||||
ogRank: number | null;
|
||||
@@ -143,8 +142,8 @@ export class ServicesApiServices {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
||||
}
|
||||
|
||||
accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
||||
accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number, userChallenged: boolean) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged });
|
||||
}
|
||||
|
||||
getAccelerations$(): Observable<Acceleration[]> {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
|
||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
|
||||
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
|
||||
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, faCircleXmark, faCalendarCheck, faMoneyBillTrendUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import { MenuComponent } from '@components/menu/menu.component';
|
||||
import { PreviewTitleComponent } from '@components/master-page-preview/preview-title.component';
|
||||
@@ -451,5 +451,6 @@ export class SharedModule {
|
||||
library.addIcons(faTimeline);
|
||||
library.addIcons(faCircleXmark);
|
||||
library.addIcons(faCalendarCheck);
|
||||
library.addIcons(faMoneyBillTrendUp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1567,7 +1567,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="bdb8bbb38e4ca3c73e19dc4167fbe4aec316f818" datatype="html">
|
||||
<source>Total Bid Boost</source>
|
||||
<target>Augmentation totale des frais</target>
|
||||
<target>Total frais ajoutés</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html</context>
|
||||
<context context-type="linenumber">11</context>
|
||||
@@ -1728,7 +1728,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="57cde27765d527a0d9195212fa5a7ce06408c827" datatype="html">
|
||||
<source>Bid Boost</source>
|
||||
<target>Augmentation des frais</target>
|
||||
<target>Frais ajoutés</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/acceleration/accelerations-list/accelerations-list.component.html</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
@@ -6739,7 +6739,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="date-base.just-now" datatype="html">
|
||||
<source>Just now</source>
|
||||
<target>Juste maintenant</target>
|
||||
<target>À l'instant</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/time/time.component.ts</context>
|
||||
<context context-type="linenumber">111</context>
|
||||
@@ -9847,7 +9847,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8e623d3cfecb7c560c114390db53c1f430ffd0de" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> confirmation</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>confirmation</target>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> confirmation</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/shared/components/confirmations/confirmations.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
|
||||
Reference in New Issue
Block a user