Compare commits

..

18 Commits

Author SHA1 Message Date
transifex-integration[bot]
a62a3cc774 Translate frontend/src/locale/messages.xlf in fr
100% reviewed source file: 'frontend/src/locale/messages.xlf'
on 'fr'.
2025-01-23 06:29:40 +00:00
transifex-integration[bot]
9660723e96 Translate frontend/src/locale/messages.xlf in fr
100% reviewed source file: 'frontend/src/locale/messages.xlf'
on 'fr'.
2025-01-13 02:39:48 +00:00
softsimon
0a255d7fe5 Merge pull request #5706 from mempool/natsoni/websocket-docs
Add websocket commands doc
2025-01-13 06:55:15 +07:00
softsimon
ca0a8aee49 Merge pull request #5716 from mempool/natsoni/fix-address-amount
Fix transaction amount change for P2PK addresses
2025-01-13 06:36:46 +07:00
nymkappa
f142b421f9 Merge pull request #5623 from mempool/nymkappa/delete-irrelevant-code
[refactor] cleaning users.full_name
2025-01-12 11:38:23 +09:00
softsimon
c3686a5500 Merge pull request #5713 from mempool/mononaut/fix-missing-unsubs
clean up subscriptions & component references
2025-01-09 16:48:44 +07:00
natsoni
9fbbe4980d Fix transaction amount change for P2PK addresses 2025-01-08 16:01:59 +01:00
softsimon
0611773647 Merge pull request #5715 from mempool/natsoni/fix-first-seen-loop
Fix filter logic for first seen API fetching
2025-01-08 21:28:11 +07:00
Mononaut
7740908a4c fix access block scene after destroyed 2025-01-08 13:08:33 +00:00
natsoni
68ea7c59f3 Fix transaction filter logic for first seen fetching 2025-01-07 11:21:04 +01:00
wiz
915f7a6c27 Merge pull request #5714 from mempool/nymkappa/missing-icon
add missing icon
2025-01-07 16:34:46 +09:00
nymkappa
e18c572549 add missing icon 2025-01-07 16:31:03 +09:00
wiz
25133d8505 Merge pull request #5708 from mempool/nymkappa/sca-notification
[accelerator] print sca status for google payment
2025-01-07 15:56:35 +09:00
Mononaut
9f5666f410 explicitly destroy block scenes 2025-01-06 20:44:28 +00:00
Mononaut
6553344489 add missing rxjs unsubscriptions 2025-01-06 18:55:40 +00:00
nymkappa
37ddc29c2c [accelerator] print sca status for google payment 2024-12-25 16:38:53 +08:00
natsoni
a5c67b5ca1 Add websocket commands doc 2024-12-22 20:24:20 +01:00
nymkappa
cdc4a430cd [refactor] cleaning users.full_name 2024-10-31 09:52:34 +01:00
36 changed files with 2658 additions and 289 deletions

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}
}
/**

View File

@@ -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;
}

View File

@@ -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)),
};

View File

@@ -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 {

View File

@@ -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];

View File

@@ -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,

View File

@@ -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;
}
/**

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -32,9 +32,9 @@
<td i18n="block.weight">Weight</td>
<td [innerHTML]="'&lrm;' + (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>

View File

@@ -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(

View File

@@ -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>

View File

@@ -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];
}

View File

@@ -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">

View File

@@ -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];
}

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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.`);

View File

@@ -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);

View File

@@ -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),
)),
)

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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`;
}
}

View File

@@ -196,8 +196,6 @@ export interface BlockExtension {
minFee?: number;
maxFee?: number;
feeRange?: number[];
effectiveMedianFee?: number;
effectiveFeeRange?: number[];
reward?: number;
coinbaseRaw?: string;
matchRate?: number;

View File

@@ -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;
}

View File

@@ -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[]> {

View File

@@ -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);
}
}

View File

@@ -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>