Compare commits
73 Commits
mononaut/f
...
hunicus/ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9afe07906d | ||
|
|
df8ec419bd | ||
|
|
d5985503e0 | ||
|
|
725526d733 | ||
|
|
c81509d762 | ||
|
|
07f16165ff | ||
|
|
536f29eb0d | ||
|
|
81ec54fcb3 | ||
|
|
64d6bda728 | ||
|
|
7ab05d815d | ||
|
|
2156fb2a83 | ||
|
|
4dec152df0 | ||
|
|
720c2b8807 | ||
|
|
7b127ebe8b | ||
|
|
585309f9f0 | ||
|
|
bfea535df1 | ||
|
|
65189d6f3e | ||
|
|
02b5fadf44 | ||
|
|
ea52d40a10 | ||
|
|
cbd4ea1aa8 | ||
|
|
fa1cf7abb9 | ||
|
|
6a5afd7f95 | ||
|
|
0ddd9b2487 | ||
|
|
ee1e75f978 | ||
|
|
97c8ace8f7 | ||
|
|
a3ca79e995 | ||
|
|
962c9543d0 | ||
|
|
52bcef54be | ||
|
|
22030027b4 | ||
|
|
53a9093a70 | ||
|
|
6ef793f202 | ||
|
|
b227ff8e1b | ||
|
|
db6b9ffa6e | ||
|
|
5a0c809fa7 | ||
|
|
f6caed3ffd | ||
|
|
6141516eb2 | ||
|
|
3b295b5482 | ||
|
|
adc395fc3d | ||
|
|
0c52898010 | ||
|
|
4fe6a74d20 | ||
|
|
7fca18d7be | ||
|
|
208756bdd2 | ||
|
|
82a072bd87 | ||
|
|
6d76d11837 | ||
|
|
982f1e007a | ||
|
|
a4bf545993 | ||
|
|
fe51f6a504 | ||
|
|
f5d311ca44 | ||
|
|
b81dd99825 | ||
|
|
f08457aba7 | ||
|
|
c601e5dcb4 | ||
|
|
11b82b3459 | ||
|
|
d322665789 | ||
|
|
49529627f8 | ||
|
|
107746feec | ||
|
|
e5bef55d47 | ||
|
|
17dd02ed4e | ||
|
|
7bb34fe090 | ||
|
|
abbaee0274 | ||
|
|
3d1cd3193a | ||
|
|
be53cd8b48 | ||
|
|
4b20ea7232 | ||
|
|
ffd7831efc | ||
|
|
f8636d20c2 | ||
|
|
3b4dd7e633 | ||
|
|
ea101e65bb | ||
|
|
8a713c3880 | ||
|
|
42d5650bc0 | ||
|
|
822934828a | ||
|
|
d7333ec858 | ||
|
|
1df2c89cdb | ||
|
|
6602bddb2b | ||
|
|
bca35600ff |
@@ -14,11 +14,11 @@ describe('Mempool Difficulty Adjustment', () => {
|
||||
750134, // Current block height
|
||||
0.6280047707459726, // Previous retarget % (Passed through)
|
||||
'mainnet', // Network (if testnet, next value is non-zero)
|
||||
0, // If not testnet, not used
|
||||
0, // Latest block timestamp in seconds (only used if difficulty already locked in)
|
||||
],
|
||||
{ // Expected Result
|
||||
progressPercent: 9.027777777777777,
|
||||
difficultyChange: 12.562233927411782,
|
||||
difficultyChange: 13.180707740199772,
|
||||
estimatedRetargetDate: 1661895424692,
|
||||
remainingBlocks: 1834,
|
||||
remainingTime: 977591692,
|
||||
@@ -41,7 +41,7 @@ describe('Mempool Difficulty Adjustment', () => {
|
||||
],
|
||||
{ // Expected Result is same other than timeOffset
|
||||
progressPercent: 9.027777777777777,
|
||||
difficultyChange: 12.562233927411782,
|
||||
difficultyChange: 13.180707740199772,
|
||||
estimatedRetargetDate: 1661895424692,
|
||||
remainingBlocks: 1834,
|
||||
remainingTime: 977591692,
|
||||
@@ -54,6 +54,29 @@ describe('Mempool Difficulty Adjustment', () => {
|
||||
expectedBlocks: 161.68833333333333,
|
||||
},
|
||||
],
|
||||
[ // Vector 3 (mainnet lock-in (epoch ending 788255))
|
||||
[ // Inputs
|
||||
dt('2023-04-20T09:57:33.000Z'), // Last DA time (in seconds)
|
||||
dt('2023-05-04T14:54:09.000Z'), // Current time (now) (in seconds)
|
||||
788255, // Current block height
|
||||
1.7220298879531821, // Previous retarget % (Passed through)
|
||||
'mainnet', // Network (if testnet, next value is non-zero)
|
||||
dt('2023-05-04T14:54:26.000Z'), // Latest block timestamp in seconds
|
||||
],
|
||||
{ // Expected Result
|
||||
progressPercent: 99.95039682539682,
|
||||
difficultyChange: -1.4512637555574193,
|
||||
estimatedRetargetDate: 1683212658129,
|
||||
remainingBlocks: 1,
|
||||
remainingTime: 609129,
|
||||
previousRetarget: 1.7220298879531821,
|
||||
previousTime: 1681984653,
|
||||
nextRetargetHeight: 788256,
|
||||
timeAvg: 609129,
|
||||
timeOffset: 0,
|
||||
expectedBlocks: 2045.66,
|
||||
},
|
||||
],
|
||||
] as [[number, number, number, number, string, number], DifficultyAdjustment][];
|
||||
|
||||
for (const vector of vectors) {
|
||||
|
||||
@@ -6,14 +6,15 @@ const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first
|
||||
|
||||
class Audit {
|
||||
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
|
||||
: { censored: string[], added: string[], fresh: string[], score: number, similarity: number } {
|
||||
: { censored: string[], added: string[], fresh: string[], sigop: string[], score: number, similarity: number } {
|
||||
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
||||
return { censored: [], added: [], fresh: [], score: 0, similarity: 1 };
|
||||
return { censored: [], added: [], fresh: [], sigop: [], score: 0, similarity: 1 };
|
||||
}
|
||||
|
||||
const matches: string[] = []; // present in both mined block and template
|
||||
const added: string[] = []; // present in mined block, not in template
|
||||
const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN
|
||||
const sigop: string[] = []; // missing, but possibly has an adjusted vsize due to high sigop count
|
||||
const isCensored = {}; // missing, without excuse
|
||||
const isDisplaced = {};
|
||||
let displacedWeight = 0;
|
||||
@@ -37,6 +38,8 @@ class Audit {
|
||||
// tx is recent, may have reached the miner too late for inclusion
|
||||
if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) {
|
||||
fresh.push(txid);
|
||||
} else if (this.isPossibleHighSigop(mempool[txid])) {
|
||||
sigop.push(txid);
|
||||
} else {
|
||||
isCensored[txid] = true;
|
||||
}
|
||||
@@ -137,10 +140,19 @@ class Audit {
|
||||
censored: Object.keys(isCensored),
|
||||
added,
|
||||
fresh,
|
||||
sigop,
|
||||
score,
|
||||
similarity,
|
||||
};
|
||||
}
|
||||
|
||||
// Detect transactions with a possibly adjusted vsize due to high sigop count
|
||||
// very rough heuristic based on number of OP_CHECKMULTISIG outputs
|
||||
// will miss cases with other sources of sigops
|
||||
isPossibleHighSigop(tx: TransactionExtended): boolean {
|
||||
const numBareMultisig = tx.vout.reduce((count, vout) => count + (vout.scriptpubkey_asm.includes('OP_CHECKMULTISIG') ? 1 : 0), 0);
|
||||
return (numBareMultisig * 400) > tx.vsize;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Audit();
|
||||
@@ -130,8 +130,9 @@ class BitcoinRoutes {
|
||||
|
||||
private getInitData(req: Request, res: Response) {
|
||||
try {
|
||||
const result = websocketHandler.getInitData();
|
||||
res.json(result);
|
||||
const result = websocketHandler.getSerializedInitData();
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Ancestor, CpfpInfo, CpfpSummary, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
|
||||
import { Ancestor, CpfpInfo, CpfpSummary, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
||||
import { isIP } from 'net';
|
||||
@@ -442,3 +442,119 @@ export class Common {
|
||||
return sortedDistribution[Math.floor((sortedDistribution.length - 1) * (n / 100))];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to calculate average fee rates of a list of transactions
|
||||
* at certain weight percentiles, in a single pass
|
||||
*
|
||||
* init with:
|
||||
* maxWeight - the total weight to measure percentiles relative to (e.g. 4MW for a single block)
|
||||
* percentileBandWidth - how many weight units to average over for each percentile (as a % of maxWeight)
|
||||
* percentiles - an array of weight percentiles to compute, in %
|
||||
*
|
||||
* then call .processNext(tx) for each transaction, in descending order
|
||||
*
|
||||
* retrieve the final results with .getFeeStats()
|
||||
*/
|
||||
export class OnlineFeeStatsCalculator {
|
||||
private maxWeight: number;
|
||||
private percentiles = [10,25,50,75,90];
|
||||
|
||||
private bandWidthPercent = 2;
|
||||
private bandWidth: number = 0;
|
||||
private bandIndex = 0;
|
||||
private leftBound = 0;
|
||||
private rightBound = 0;
|
||||
private inBand = false;
|
||||
private totalBandFee = 0;
|
||||
private totalBandWeight = 0;
|
||||
private minBandRate = Infinity;
|
||||
private maxBandRate = 0;
|
||||
|
||||
private feeRange: { avg: number, min: number, max: number }[] = [];
|
||||
private totalWeight: number = 0;
|
||||
|
||||
constructor (maxWeight: number, percentileBandWidth?: number, percentiles?: number[]) {
|
||||
this.maxWeight = maxWeight;
|
||||
if (percentiles && percentiles.length) {
|
||||
this.percentiles = percentiles;
|
||||
}
|
||||
if (percentileBandWidth != null) {
|
||||
this.bandWidthPercent = percentileBandWidth;
|
||||
}
|
||||
this.bandWidth = this.maxWeight * (this.bandWidthPercent / 100);
|
||||
// add min/max percentiles aligned to the ends of the range
|
||||
this.percentiles.unshift(this.bandWidthPercent / 2);
|
||||
this.percentiles.push(100 - (this.bandWidthPercent / 2));
|
||||
this.setNextBounds();
|
||||
}
|
||||
|
||||
processNext(tx: { weight: number, fee: number, effectiveFeePerVsize?: number, feePerVsize?: number, rate?: number, txid: string }): void {
|
||||
let left = this.totalWeight;
|
||||
const right = this.totalWeight + tx.weight;
|
||||
if (!this.inBand && right <= this.leftBound) {
|
||||
this.totalWeight += tx.weight;
|
||||
return;
|
||||
}
|
||||
|
||||
while (left < right) {
|
||||
if (right > this.leftBound) {
|
||||
this.inBand = true;
|
||||
const txRate = (tx.rate || tx.effectiveFeePerVsize || tx.feePerVsize || 0);
|
||||
const weight = Math.min(right, this.rightBound) - Math.max(left, this.leftBound);
|
||||
this.totalBandFee += (txRate * weight);
|
||||
this.totalBandWeight += weight;
|
||||
this.maxBandRate = Math.max(this.maxBandRate, txRate);
|
||||
this.minBandRate = Math.min(this.minBandRate, txRate);
|
||||
}
|
||||
left = Math.min(right, this.rightBound);
|
||||
|
||||
if (left >= this.rightBound) {
|
||||
this.inBand = false;
|
||||
const avgBandFeeRate = this.totalBandWeight ? (this.totalBandFee / this.totalBandWeight) : 0;
|
||||
this.feeRange.unshift({ avg: avgBandFeeRate, min: this.minBandRate, max: this.maxBandRate });
|
||||
this.bandIndex++;
|
||||
this.setNextBounds();
|
||||
this.totalBandFee = 0;
|
||||
this.totalBandWeight = 0;
|
||||
this.minBandRate = Infinity;
|
||||
this.maxBandRate = 0;
|
||||
}
|
||||
}
|
||||
this.totalWeight += tx.weight;
|
||||
}
|
||||
|
||||
private setNextBounds(): void {
|
||||
const nextPercentile = this.percentiles[this.bandIndex];
|
||||
if (nextPercentile != null) {
|
||||
this.leftBound = ((nextPercentile / 100) * this.maxWeight) - (this.bandWidth / 2);
|
||||
this.rightBound = this.leftBound + this.bandWidth;
|
||||
} else {
|
||||
this.leftBound = Infinity;
|
||||
this.rightBound = Infinity;
|
||||
}
|
||||
}
|
||||
|
||||
getRawFeeStats(): WorkingEffectiveFeeStats {
|
||||
if (this.totalBandWeight > 0) {
|
||||
const avgBandFeeRate = this.totalBandWeight ? (this.totalBandFee / this.totalBandWeight) : 0;
|
||||
this.feeRange.unshift({ avg: avgBandFeeRate, min: this.minBandRate, max: this.maxBandRate });
|
||||
}
|
||||
while (this.feeRange.length < this.percentiles.length) {
|
||||
this.feeRange.unshift({ avg: 0, min: 0, max: 0 });
|
||||
}
|
||||
return {
|
||||
minFee: this.feeRange[0].min,
|
||||
medianFee: this.feeRange[Math.floor(this.feeRange.length / 2)].avg,
|
||||
maxFee: this.feeRange[this.feeRange.length - 1].max,
|
||||
feeRange: this.feeRange.map(f => f.avg),
|
||||
};
|
||||
}
|
||||
|
||||
getFeeStats(): EffectiveFeeStats {
|
||||
const stats = this.getRawFeeStats();
|
||||
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 = 59;
|
||||
private static currentVersion = 60;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@@ -516,6 +516,11 @@ class DatabaseMigration {
|
||||
// https://github.com/mempool/mempool/issues/3360
|
||||
await this.$executeQuery(`TRUNCATE prices`);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 60 && isBitcoin === true) {
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD sigop_txs JSON DEFAULT "[]"');
|
||||
await this.updateToSchemaVersion(60);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,11 +34,12 @@ export function calcDifficultyAdjustment(
|
||||
const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
|
||||
const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
|
||||
const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
|
||||
const actualTimespan = (blocksInEpoch === 2015 ? latestBlockTimestamp : nowSeconds) - DATime;
|
||||
|
||||
let difficultyChange = 0;
|
||||
let timeAvgSecs = blocksInEpoch ? diffSeconds / blocksInEpoch : BLOCK_SECONDS_TARGET;
|
||||
|
||||
difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100;
|
||||
difficultyChange = (BLOCK_SECONDS_TARGET / (actualTimespan / (blocksInEpoch + 1)) - 1) * 100;
|
||||
// Max increase is x4 (+300%)
|
||||
if (difficultyChange > 300) {
|
||||
difficultyChange = 300;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logger from '../logger';
|
||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats } from '../mempool.interfaces';
|
||||
import { Common, OnlineFeeStatsCalculator } from './common';
|
||||
import config from '../config';
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
@@ -104,6 +104,8 @@ class MempoolBlocks {
|
||||
|
||||
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] {
|
||||
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||
let feeStatsCalculator: OnlineFeeStatsCalculator = new OnlineFeeStatsCalculator(config.MEMPOOL.BLOCK_WEIGHT_UNITS);
|
||||
let onlineStats = false;
|
||||
let blockSize = 0;
|
||||
let blockWeight = 0;
|
||||
let blockVsize = 0;
|
||||
@@ -111,7 +113,7 @@ class MempoolBlocks {
|
||||
const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
|
||||
let transactionIds: string[] = [];
|
||||
let transactions: TransactionExtended[] = [];
|
||||
transactionsSorted.forEach((tx) => {
|
||||
transactionsSorted.forEach((tx, index) => {
|
||||
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
|
||||
|| mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
||||
tx.position = {
|
||||
@@ -126,6 +128,9 @@ class MempoolBlocks {
|
||||
transactions.push(tx);
|
||||
}
|
||||
transactionIds.push(tx.txid);
|
||||
if (onlineStats) {
|
||||
feeStatsCalculator.processNext(tx);
|
||||
}
|
||||
} else {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees));
|
||||
blockVsize = 0;
|
||||
@@ -133,6 +138,16 @@ class MempoolBlocks {
|
||||
block: mempoolBlocks.length,
|
||||
vsize: blockVsize + (tx.vsize / 2),
|
||||
};
|
||||
|
||||
if (mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
||||
const stackWeight = transactionsSorted.slice(index).reduce((total, tx) => total + (tx.weight || 0), 0);
|
||||
if (stackWeight > config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
||||
onlineStats = true;
|
||||
feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5);
|
||||
feeStatsCalculator.processNext(tx);
|
||||
}
|
||||
}
|
||||
|
||||
blockVsize += tx.vsize;
|
||||
blockWeight = tx.weight;
|
||||
blockSize = tx.size;
|
||||
@@ -142,7 +157,8 @@ class MempoolBlocks {
|
||||
}
|
||||
});
|
||||
if (transactions.length) {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees));
|
||||
const feeStats = onlineStats ? feeStatsCalculator.getRawFeeStats() : undefined;
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees, feeStats));
|
||||
}
|
||||
|
||||
return mempoolBlocks;
|
||||
@@ -310,7 +326,16 @@ class MempoolBlocks {
|
||||
}
|
||||
}
|
||||
|
||||
const readyBlocks: { transactionIds, transactions, totalSize, totalWeight, totalFees }[] = [];
|
||||
let hasBlockStack = blocks.length >= 8;
|
||||
let stackWeight;
|
||||
let feeStatsCalculator: OnlineFeeStatsCalculator | void;
|
||||
if (hasBlockStack) {
|
||||
stackWeight = blocks[blocks.length - 1].reduce((total, tx) => total + (mempool[tx]?.weight || 0), 0);
|
||||
hasBlockStack = stackWeight > config.MEMPOOL.BLOCK_WEIGHT_UNITS;
|
||||
feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5);
|
||||
}
|
||||
|
||||
const readyBlocks: { transactionIds, transactions, totalSize, totalWeight, totalFees, feeStats }[] = [];
|
||||
const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
|
||||
// update this thread's mempool with the results
|
||||
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
||||
@@ -333,6 +358,11 @@ class MempoolBlocks {
|
||||
};
|
||||
mempoolTx.cpfpChecked = true;
|
||||
|
||||
// online calculation of stack-of-blocks fee stats
|
||||
if (hasBlockStack && blockIndex === blocks.length - 1 && feeStatsCalculator) {
|
||||
feeStatsCalculator.processNext(mempoolTx);
|
||||
}
|
||||
|
||||
totalSize += mempoolTx.size;
|
||||
totalVsize += mempoolTx.vsize;
|
||||
totalWeight += mempoolTx.weight;
|
||||
@@ -348,7 +378,8 @@ class MempoolBlocks {
|
||||
transactions,
|
||||
totalSize,
|
||||
totalWeight,
|
||||
totalFees
|
||||
totalFees,
|
||||
feeStats: (hasBlockStack && blockIndex === blocks.length - 1 && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -382,7 +413,9 @@ class MempoolBlocks {
|
||||
}
|
||||
}
|
||||
|
||||
const mempoolBlocks = readyBlocks.map(b => this.dataToMempoolBlocks(b.transactionIds, b.transactions, b.totalSize, b.totalWeight, b.totalFees));
|
||||
const mempoolBlocks = readyBlocks.map((b, index) => {
|
||||
return this.dataToMempoolBlocks(b.transactionIds, b.transactions, b.totalSize, b.totalWeight, b.totalFees, b.feeStats);
|
||||
});
|
||||
|
||||
if (saveResults) {
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||
@@ -393,8 +426,10 @@ class MempoolBlocks {
|
||||
return mempoolBlocks;
|
||||
}
|
||||
|
||||
private dataToMempoolBlocks(transactionIds: string[], transactions: TransactionExtended[], totalSize: number, totalWeight: number, totalFees: number): MempoolBlockWithTransactions {
|
||||
const feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||
private dataToMempoolBlocks(transactionIds: string[], transactions: TransactionExtended[], 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
|
||||
|
||||
@@ -26,7 +26,7 @@ class MiningRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlocksHealth)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores', this.$getBlockAuditScores)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores/:height', this.$getBlockAuditScores)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
|
||||
@@ -244,15 +244,15 @@ class MiningRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
private async $getHistoricalBlockPrediction(req: Request, res: Response) {
|
||||
private async $getHistoricalBlocksHealth(req: Request, res: Response) {
|
||||
try {
|
||||
const blockPredictions = await mining.$getBlockPredictionsHistory(req.params.interval);
|
||||
const blockCount = await BlocksAuditsRepository.$getPredictionsCount();
|
||||
const blocksHealth = await mining.$getBlocksHealthHistory(req.params.interval);
|
||||
const blockCount = await BlocksAuditsRepository.$getBlocksHealthCount();
|
||||
res.header('Pragma', 'public');
|
||||
res.header('Cache-control', 'public');
|
||||
res.header('X-total-count', blockCount.toString());
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(blockPredictions.map(prediction => [prediction.time, prediction.height, prediction.match_rate]));
|
||||
res.json(blocksHealth.map(health => [health.time, health.height, health.match_rate]));
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
|
||||
import PricesRepository from '../../repositories/PricesRepository';
|
||||
import { bitcoinCoreApi } from '../bitcoin/bitcoin-api-factory';
|
||||
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
|
||||
import database from '../../database';
|
||||
|
||||
class Mining {
|
||||
private blocksPriceIndexingRunning = false;
|
||||
@@ -21,10 +20,10 @@ class Mining {
|
||||
public lastWeeklyHashrateIndexingDate: number | null = null;
|
||||
|
||||
/**
|
||||
* Get historical block predictions match rate
|
||||
* Get historical blocks health
|
||||
*/
|
||||
public async $getBlockPredictionsHistory(interval: string | null = null): Promise<any> {
|
||||
return await BlocksAuditsRepository.$getBlockPredictionsHistory(
|
||||
public async $getBlocksHealthHistory(interval: string | null = null): Promise<any> {
|
||||
return await BlocksAuditsRepository.$getBlocksHealthHistory(
|
||||
this.getTimeRange(interval),
|
||||
Common.getSqlInterval(interval)
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ class RbfCache {
|
||||
}
|
||||
|
||||
const newTx = Common.stripTransaction(newTxExtended) as RbfTransaction;
|
||||
const newTime = newTxExtended.firstSeen || Date.now();
|
||||
const newTime = newTxExtended.firstSeen || (Date.now() / 1000);
|
||||
newTx.rbf = newTxExtended.vin.some((v) => v.sequence < 0xfffffffe);
|
||||
this.txs.set(newTx.txid, newTxExtended);
|
||||
|
||||
@@ -59,7 +59,7 @@ class RbfCache {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const replacedTime = replacedTxExtended.firstSeen || Date.now();
|
||||
const replacedTime = replacedTxExtended.firstSeen || (Date.now() / 1000);
|
||||
replacedTrees.push({
|
||||
tx: replacedTx,
|
||||
time: replacedTime,
|
||||
@@ -74,7 +74,7 @@ class RbfCache {
|
||||
const treeId = replacedTrees[0].tx.txid;
|
||||
const newTree = {
|
||||
tx: newTx,
|
||||
time: newTxExtended.firstSeen || Date.now(),
|
||||
time: newTime,
|
||||
fullRbf,
|
||||
replaces: replacedTrees
|
||||
};
|
||||
|
||||
@@ -30,6 +30,9 @@ class WebsocketHandler {
|
||||
private numConnected = 0;
|
||||
private numDisconnected = 0;
|
||||
|
||||
private initData: { [key: string]: string } = {};
|
||||
private serializedInitData: string = '{}';
|
||||
|
||||
constructor() { }
|
||||
|
||||
setWebsocketServer(wss: WebSocket.Server) {
|
||||
@@ -38,6 +41,41 @@ class WebsocketHandler {
|
||||
|
||||
setExtraInitProperties(property: string, value: any) {
|
||||
this.extraInitProperties[property] = value;
|
||||
this.setInitDataFields(this.extraInitProperties);
|
||||
}
|
||||
|
||||
private setInitDataFields(data: { [property: string]: any }): void {
|
||||
for (const property of Object.keys(data)) {
|
||||
if (data[property] != null) {
|
||||
this.initData[property] = JSON.stringify(data[property]);
|
||||
} else {
|
||||
delete this.initData[property];
|
||||
}
|
||||
}
|
||||
this.serializedInitData = '{'
|
||||
+ Object.keys(this.initData).map(key => `"${key}": ${this.initData[key]}`).join(', ')
|
||||
+ '}';
|
||||
}
|
||||
|
||||
private updateInitData(): void {
|
||||
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||
this.setInitDataFields({
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
||||
'blocks': _blocks,
|
||||
'conversions': priceUpdater.getLatestPrices(),
|
||||
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
|
||||
'transactions': memPool.getLatestTransactions(),
|
||||
'backendInfo': backendInfo.getBackendInfo(),
|
||||
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
||||
'da': da?.previousTime ? da : undefined,
|
||||
'fees': feeApi.getRecommendedFee(),
|
||||
});
|
||||
}
|
||||
|
||||
public getSerializedInitData(): string {
|
||||
return this.serializedInitData;
|
||||
}
|
||||
|
||||
setupConnectionHandling() {
|
||||
@@ -157,11 +195,13 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (parsedMessage.action === 'init') {
|
||||
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||
if (!_blocks) {
|
||||
if (!this.initData['blocks']?.length || !this.initData['da']) {
|
||||
this.updateInitData();
|
||||
}
|
||||
if (!this.initData['blocks']?.length) {
|
||||
return;
|
||||
}
|
||||
client.send(JSON.stringify(this.getInitData(_blocks)));
|
||||
client.send(this.serializedInitData);
|
||||
}
|
||||
|
||||
if (parsedMessage.action === 'ping') {
|
||||
@@ -210,11 +250,14 @@ class WebsocketHandler {
|
||||
throw new Error('WebSocket.Server is not set');
|
||||
}
|
||||
|
||||
this.setInitDataFields({ 'loadingIndicators': indicators });
|
||||
|
||||
const response = JSON.stringify({ loadingIndicators: indicators });
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
client.send(JSON.stringify({ loadingIndicators: indicators }));
|
||||
client.send(response);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -223,34 +266,17 @@ class WebsocketHandler {
|
||||
throw new Error('WebSocket.Server is not set');
|
||||
}
|
||||
|
||||
this.setInitDataFields({ 'conversions': conversionRates });
|
||||
|
||||
const response = JSON.stringify({ conversions: conversionRates });
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
client.send(JSON.stringify({ conversions: conversionRates }));
|
||||
client.send(response);
|
||||
});
|
||||
}
|
||||
|
||||
getInitData(_blocks?: BlockExtended[]) {
|
||||
if (!_blocks) {
|
||||
_blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||
}
|
||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||
return {
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
||||
'blocks': _blocks,
|
||||
'conversions': priceUpdater.getLatestPrices(),
|
||||
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
|
||||
'transactions': memPool.getLatestTransactions(),
|
||||
'backendInfo': backendInfo.getBackendInfo(),
|
||||
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
||||
'da': da?.previousTime ? da : undefined,
|
||||
'fees': feeApi.getRecommendedFee(),
|
||||
...this.extraInitProperties
|
||||
};
|
||||
}
|
||||
|
||||
handleNewStatistic(stats: OptimizedStatistic) {
|
||||
if (!this.wss) {
|
||||
throw new Error('WebSocket.Server is not set');
|
||||
@@ -258,6 +284,10 @@ class WebsocketHandler {
|
||||
|
||||
this.printLogs();
|
||||
|
||||
const response = JSON.stringify({
|
||||
'live-2h-chart': stats
|
||||
});
|
||||
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
@@ -267,9 +297,7 @@ class WebsocketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
client.send(JSON.stringify({
|
||||
'live-2h-chart': stats
|
||||
}));
|
||||
client.send(response);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,6 +334,43 @@ class WebsocketHandler {
|
||||
}
|
||||
const recommendedFees = feeApi.getRecommendedFee();
|
||||
|
||||
// update init data
|
||||
this.updateInitData();
|
||||
|
||||
// cache serialized objects to avoid stringify-ing the same thing for every client
|
||||
const responseCache = { ...this.initData };
|
||||
function getCachedResponse(key: string, data): string {
|
||||
if (!responseCache[key]) {
|
||||
responseCache[key] = JSON.stringify(data);
|
||||
}
|
||||
return responseCache[key];
|
||||
}
|
||||
|
||||
// pre-compute new tracked outspends
|
||||
const outspendCache: { [txid: string]: { [vout: number]: { vin: number, txid: string } } } = {};
|
||||
const trackedTxs = new Set<string>();
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client['track-tx']) {
|
||||
trackedTxs.add(client['track-tx']);
|
||||
}
|
||||
});
|
||||
if (trackedTxs.size > 0) {
|
||||
for (const tx of newTransactions) {
|
||||
for (let i = 0; i < tx.vin.length; i++) {
|
||||
const vin = tx.vin[i];
|
||||
if (trackedTxs.has(vin.txid)) {
|
||||
if (!outspendCache[vin.txid]) {
|
||||
outspendCache[vin.txid] = { [vin.vout]: { vin: i, txid: tx.txid }};
|
||||
} else {
|
||||
outspendCache[vin.txid][vin.vout] = { vin: i, txid: tx.txid };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const latestTransactions = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
||||
|
||||
this.wss.clients.forEach(async (client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
@@ -314,17 +379,17 @@ class WebsocketHandler {
|
||||
const response = {};
|
||||
|
||||
if (client['want-stats']) {
|
||||
response['mempoolInfo'] = mempoolInfo;
|
||||
response['vBytesPerSecond'] = vBytesPerSecond;
|
||||
response['transactions'] = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
||||
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
||||
response['vBytesPerSecond'] = getCachedResponse('vBytesPerSecond', vBytesPerSecond);
|
||||
response['transactions'] = getCachedResponse('transactions', latestTransactions);
|
||||
if (da?.previousTime) {
|
||||
response['da'] = da;
|
||||
response['da'] = getCachedResponse('da', da);
|
||||
}
|
||||
response['fees'] = recommendedFees;
|
||||
response['fees'] = getCachedResponse('fees', recommendedFees);
|
||||
}
|
||||
|
||||
if (client['want-mempool-blocks']) {
|
||||
response['mempool-blocks'] = mBlocks;
|
||||
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||
}
|
||||
|
||||
if (client['track-mempool-tx']) {
|
||||
@@ -333,12 +398,12 @@ class WebsocketHandler {
|
||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||
try {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
response['tx'] = fullTx;
|
||||
response['tx'] = JSON.stringify(fullTx);
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
} else {
|
||||
response['tx'] = tx;
|
||||
response['tx'] = JSON.stringify(tx);
|
||||
}
|
||||
client['track-mempool-tx'] = null;
|
||||
}
|
||||
@@ -378,7 +443,7 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (foundTransactions.length) {
|
||||
response['address-transactions'] = foundTransactions;
|
||||
response['address-transactions'] = JSON.stringify(foundTransactions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,65 +472,60 @@ class WebsocketHandler {
|
||||
});
|
||||
|
||||
if (foundTransactions.length) {
|
||||
response['address-transactions'] = foundTransactions;
|
||||
response['address-transactions'] = JSON.stringify(foundTransactions);
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-tx']) {
|
||||
const trackTxid = client['track-tx'];
|
||||
const outspends: object = {};
|
||||
newTransactions.forEach((tx) => tx.vin.forEach((vin, i) => {
|
||||
if (vin.txid === trackTxid) {
|
||||
outspends[vin.vout] = {
|
||||
vin: i,
|
||||
txid: tx.txid,
|
||||
};
|
||||
}
|
||||
}));
|
||||
const outspends = outspendCache[trackTxid];
|
||||
|
||||
if (Object.keys(outspends).length) {
|
||||
response['utxoSpent'] = outspends;
|
||||
if (outspends && Object.keys(outspends).length) {
|
||||
response['utxoSpent'] = JSON.stringify(outspends);
|
||||
}
|
||||
|
||||
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
||||
if (rbfReplacedBy) {
|
||||
response['rbfTransaction'] = {
|
||||
response['rbfTransaction'] = JSON.stringify({
|
||||
txid: rbfReplacedBy,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const rbfChange = rbfChanges.map[client['track-tx']];
|
||||
if (rbfChange) {
|
||||
response['rbfInfo'] = rbfChanges.trees[rbfChange];
|
||||
response['rbfInfo'] = JSON.stringify(rbfChanges.trees[rbfChange]);
|
||||
}
|
||||
|
||||
const mempoolTx = newMempool[trackTxid];
|
||||
if (mempoolTx && mempoolTx.position) {
|
||||
response['txPosition'] = {
|
||||
response['txPosition'] = JSON.stringify({
|
||||
txid: trackTxid,
|
||||
position: mempoolTx.position,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-mempool-block'] >= 0) {
|
||||
const index = client['track-mempool-block'];
|
||||
if (mBlockDeltas[index]) {
|
||||
response['projected-block-transactions'] = {
|
||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||
index: index,
|
||||
delta: mBlockDeltas[index],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
||||
response['rbfLatest'] = rbfReplacements;
|
||||
response['rbfLatest'] = getCachedResponse('rbfLatest', rbfReplacements);
|
||||
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
|
||||
response['rbfLatest'] = fullRbfReplacements;
|
||||
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
|
||||
}
|
||||
|
||||
if (Object.keys(response).length) {
|
||||
client.send(JSON.stringify(response));
|
||||
const serializedResponse = '{'
|
||||
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
||||
+ '}';
|
||||
client.send(serializedResponse);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -497,7 +557,7 @@ class WebsocketHandler {
|
||||
}
|
||||
|
||||
if (Common.indexingEnabled() && memPool.isInSync()) {
|
||||
const { censored, added, fresh, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
||||
const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
||||
const matchRate = Math.round(score * 100 * 100) / 100;
|
||||
|
||||
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
|
||||
@@ -524,6 +584,7 @@ class WebsocketHandler {
|
||||
addedTxs: added,
|
||||
missingTxs: censored,
|
||||
freshTxs: fresh,
|
||||
sigopTxs: sigop,
|
||||
matchRate: matchRate,
|
||||
});
|
||||
|
||||
@@ -556,6 +617,19 @@ class WebsocketHandler {
|
||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||
const fees = feeApi.getRecommendedFee();
|
||||
|
||||
// update init data
|
||||
this.updateInitData();
|
||||
|
||||
const responseCache = { ...this.initData };
|
||||
function getCachedResponse(key, data): string {
|
||||
if (!responseCache[key]) {
|
||||
responseCache[key] = JSON.stringify(data);
|
||||
}
|
||||
return responseCache[key];
|
||||
}
|
||||
|
||||
const mempoolInfo = memPool.getMempoolInfo();
|
||||
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
@@ -565,28 +639,27 @@ class WebsocketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = {
|
||||
'block': block,
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
'da': da?.previousTime ? da : undefined,
|
||||
'fees': fees,
|
||||
};
|
||||
const response = {};
|
||||
response['block'] = getCachedResponse('block', block);
|
||||
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
||||
response['da'] = getCachedResponse('da', da?.previousTime ? da : undefined);
|
||||
response['fees'] = getCachedResponse('fees', fees);
|
||||
|
||||
if (mBlocks && client['want-mempool-blocks']) {
|
||||
response['mempool-blocks'] = mBlocks;
|
||||
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||
}
|
||||
|
||||
if (client['track-tx']) {
|
||||
const trackTxid = client['track-tx'];
|
||||
if (txIds.indexOf(trackTxid) > -1) {
|
||||
response['txConfirmed'] = true;
|
||||
response['txConfirmed'] = 'true';
|
||||
} else {
|
||||
const mempoolTx = _memPool[trackTxid];
|
||||
if (mempoolTx && mempoolTx.position) {
|
||||
response['txPosition'] = {
|
||||
response['txPosition'] = JSON.stringify({
|
||||
txid: trackTxid,
|
||||
position: mempoolTx.position,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,7 +687,7 @@ class WebsocketHandler {
|
||||
};
|
||||
});
|
||||
|
||||
response['block-transactions'] = foundTransactions;
|
||||
response['block-transactions'] = JSON.stringify(foundTransactions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,21 +724,24 @@ class WebsocketHandler {
|
||||
};
|
||||
});
|
||||
|
||||
response['block-transactions'] = foundTransactions;
|
||||
response['block-transactions'] = JSON.stringify(foundTransactions);
|
||||
}
|
||||
}
|
||||
|
||||
if (client['track-mempool-block'] >= 0) {
|
||||
const index = client['track-mempool-block'];
|
||||
if (mBlockDeltas && mBlockDeltas[index]) {
|
||||
response['projected-block-transactions'] = {
|
||||
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||
index: index,
|
||||
delta: mBlockDeltas[index],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
client.send(JSON.stringify(response));
|
||||
const serializedResponse = '{'
|
||||
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
||||
+ '}';
|
||||
client.send(serializedResponse);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface BlockAudit {
|
||||
hash: string,
|
||||
missingTxs: string[],
|
||||
freshTxs: string[],
|
||||
sigopTxs: string[],
|
||||
addedTxs: string[],
|
||||
matchRate: number,
|
||||
}
|
||||
@@ -237,6 +238,11 @@ export interface EffectiveFeeStats {
|
||||
feeRange: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
|
||||
}
|
||||
|
||||
export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
|
||||
minFee: number;
|
||||
maxFee: number;
|
||||
}
|
||||
|
||||
export interface CpfpSummary {
|
||||
transactions: TransactionExtended[];
|
||||
clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[];
|
||||
|
||||
@@ -6,9 +6,9 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces';
|
||||
class BlocksAuditRepositories {
|
||||
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
||||
try {
|
||||
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, match_rate)
|
||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
||||
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), audit.matchRate]);
|
||||
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, match_rate)
|
||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
||||
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), audit.matchRate]);
|
||||
} catch (e: any) {
|
||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
||||
@@ -19,7 +19,7 @@ class BlocksAuditRepositories {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getBlockPredictionsHistory(div: number, interval: string | null): Promise<any> {
|
||||
public async $getBlocksHealthHistory(div: number, interval: string | null): Promise<any> {
|
||||
try {
|
||||
let query = `SELECT UNIX_TIMESTAMP(time) as time, height, match_rate FROM blocks_audits`;
|
||||
|
||||
@@ -32,17 +32,17 @@ class BlocksAuditRepositories {
|
||||
const [rows] = await DB.query(query);
|
||||
return rows;
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot fetch block prediction history. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
logger.err(`Cannot fetch blocks health history. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getPredictionsCount(): Promise<number> {
|
||||
public async $getBlocksHealthCount(): Promise<number> {
|
||||
try {
|
||||
const [rows] = await DB.query(`SELECT count(hash) as count FROM blocks_audits`);
|
||||
return rows[0].count;
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot fetch block prediction history. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
logger.err(`Cannot fetch blocks health count. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class BlocksAuditRepositories {
|
||||
const [rows]: any[] = await DB.query(
|
||||
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
||||
blocks.weight, blocks.tx_count,
|
||||
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, match_rate as matchRate
|
||||
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, match_rate as matchRate
|
||||
FROM blocks_audits
|
||||
JOIN blocks ON blocks.hash = blocks_audits.hash
|
||||
JOIN blocks_summaries ON blocks_summaries.id = blocks_audits.hash
|
||||
@@ -63,6 +63,7 @@ class BlocksAuditRepositories {
|
||||
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
||||
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
||||
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
||||
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
||||
rows[0].transactions = JSON.parse(rows[0].transactions);
|
||||
rows[0].template = JSON.parse(rows[0].template);
|
||||
|
||||
|
||||
3
contributors/vostrnad.txt
Normal file
3
contributors/vostrnad.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022.
|
||||
|
||||
Signed: vostrnad
|
||||
@@ -504,9 +504,17 @@ describe('Mainnet', () => {
|
||||
|
||||
describe('RBF transactions', () => {
|
||||
it('shows RBF transactions properly (mobile)', () => {
|
||||
cy.intercept('/api/v1/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f/cached', {
|
||||
fixture: 'mainnet_tx_cached.json'
|
||||
}).as('cached_tx');
|
||||
|
||||
cy.intercept('/api/v1/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5/rbf', {
|
||||
fixture: 'mainnet_rbf_new.json'
|
||||
}).as('rbf');
|
||||
|
||||
cy.viewport('iphone-xr');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
|
||||
cy.visit('/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f');
|
||||
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
@@ -524,22 +532,30 @@ describe('Mainnet', () => {
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.alert-mempool').should('be.visible');
|
||||
cy.get('.alert-mempool').invoke('css', 'width').then((alertWidth) => {
|
||||
cy.get('.alert').should('be.visible');
|
||||
cy.get('.alert').invoke('css', 'width').then((alertWidth) => {
|
||||
cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth);
|
||||
});
|
||||
|
||||
cy.get('.btn-success').then(getRectangle).then((rectA) => {
|
||||
cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
|
||||
cy.get('.btn-danger').then(getRectangle).then((rectA) => {
|
||||
cy.get('.alert').then(getRectangle).then((rectB) => {
|
||||
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows RBF transactions properly (desktop)', () => {
|
||||
cy.intercept('/api/v1/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f/cached', {
|
||||
fixture: 'mainnet_tx_cached.json'
|
||||
}).as('cached_tx');
|
||||
|
||||
cy.intercept('/api/v1/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5/rbf', {
|
||||
fixture: 'mainnet_rbf_new.json'
|
||||
}).as('rbf');
|
||||
|
||||
cy.viewport('macbook-16');
|
||||
cy.mockMempoolSocket();
|
||||
cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5');
|
||||
cy.visit('/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f');
|
||||
|
||||
cy.waitForSkeletonGone();
|
||||
|
||||
@@ -557,17 +573,17 @@ describe('Mainnet', () => {
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('.alert-mempool').should('be.visible');
|
||||
cy.get('.alert').should('be.visible');
|
||||
|
||||
const alertLocator = '.alert-mempool';
|
||||
const alertLocator = '.alert';
|
||||
const tableLocator = '.container-xl > :nth-child(3)';
|
||||
|
||||
cy.get(tableLocator).invoke('css', 'width').then((firstWidth) => {
|
||||
cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth);
|
||||
});
|
||||
|
||||
cy.get('.btn-success').then(getRectangle).then((rectA) => {
|
||||
cy.get('.alert-mempool').then(getRectangle).then((rectB) => {
|
||||
cy.get('.btn-danger').then(getRectangle).then((rectA) => {
|
||||
cy.get('.alert').then(getRectangle).then((rectB) => {
|
||||
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,52 +1,4 @@
|
||||
{
|
||||
"rbfTransaction": {
|
||||
"txid": "8913ec7ba0ede285dbd120e46f6d61a28f2903c10814a6f6c4f97d0edf3e1f46",
|
||||
"version": 2,
|
||||
"locktime": 632699,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "02238126a63ea2669c5f378012180ef8b54402a949316f9b2f1352c51730a086",
|
||||
"vout": 0,
|
||||
"prevout": {
|
||||
"scriptpubkey": "a914f8e495456956c833e5e8c69b9a9dc041aa14c72f87",
|
||||
"scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 f8e495456956c833e5e8c69b9a9dc041aa14c72f OP_EQUAL",
|
||||
"scriptpubkey_type": "p2sh",
|
||||
"scriptpubkey_address": "3QP3LMD8veT5GtWV83Nosif2Bhr73857VB",
|
||||
"value": 25000000
|
||||
},
|
||||
"scriptsig": "22002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
|
||||
"scriptsig_asm": "OP_PUSHBYTES_34 002043288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
|
||||
"witness": [
|
||||
"",
|
||||
"3044022009e2d3a8e645f65bc89c8492cd9c08e6fb02609fd402214884a754a1970145340220575bb325429def59f3a3f1e22d9740a3feecbe97438ff3bb5796b2c46b3c477f01",
|
||||
"3044022039c34372882da8fc1c1243bd72b5e7e5e6870301ef56bdebb87bc647fb50f9b5022071a704ee77d742f78b10e45be675d4c45a5f31e884139e75c975144fde70e41701",
|
||||
"522102346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf70421021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b52ae"
|
||||
],
|
||||
"is_coinbase": false,
|
||||
"sequence": 4294967293,
|
||||
"inner_redeemscript_asm": "OP_0 OP_PUSHBYTES_32 43288fbbc0fc5efa86c229dbb7d88ab78d57957c65b5d5ceaece70838976ad1b",
|
||||
"inner_witnessscript_asm": "OP_PUSHNUM_2 OP_PUSHBYTES_33 02346eb7133f11e0dc279bc592d5ac948a91676372a6144c9ae2085625d7fbf704 OP_PUSHBYTES_33 021b9508a458f9d59be4eb8cc87ad582c3b494106fb1d4ec22801569be0700eb7b OP_PUSHNUM_2 OP_CHECKMULTISIG"
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"scriptpubkey": "a914fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f87",
|
||||
"scriptpubkey_asm": "OP_HASH160 OP_PUSHBYTES_20 fd4e5e59dd5cf2dc48eaedf1a2a1650ca1ce9d7f OP_EQUAL",
|
||||
"scriptpubkey_type": "p2sh",
|
||||
"scriptpubkey_address": "3QnNmDhZS7toHA7bhhbTPBdtpLJoeecq5c",
|
||||
"value": 13986350
|
||||
},
|
||||
{
|
||||
"scriptpubkey": "76a914edc93d0446deec1c2d514f3a490f050096e74e0e88ac",
|
||||
"scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edc93d0446deec1c2d514f3a490f050096e74e0e OP_EQUALVERIFY OP_CHECKSIG",
|
||||
"scriptpubkey_type": "p2pkh",
|
||||
"scriptpubkey_address": "1NgJDkTUqJxxCAAZrrsC87kWag5kphrRtM",
|
||||
"value": 11000000
|
||||
}
|
||||
],
|
||||
"size": 372,
|
||||
"weight": 828,
|
||||
"fee": 1.5,
|
||||
"status": { "confirmed": false }
|
||||
}
|
||||
}
|
||||
"txReplaced": {
|
||||
"txid": "8913ec7ba0ede285dbd120e46f6d61a28f2903c10814a6f6c4f97d0edf3e1f46"
|
||||
}}
|
||||
31
frontend/cypress/fixtures/mainnet_rbf_new.json
Normal file
31
frontend/cypress/fixtures/mainnet_rbf_new.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"replacements": {
|
||||
"tx": {
|
||||
"txid": "f22735aaa8eb84bcae3e7705f78609c6f5f0cd7dfc34ae03094e61f2dab0cc64",
|
||||
"fee": 13843,
|
||||
"vsize": 109.25,
|
||||
"value": 253003805,
|
||||
"rate": 36.04666732302845,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1683865345,
|
||||
"fullRbf": false,
|
||||
"replaces": [
|
||||
{
|
||||
"tx": {
|
||||
"txid": "21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f",
|
||||
"fee": 8794,
|
||||
"vsize": 109.25,
|
||||
"value": 253008854,
|
||||
"rate": 35.05247612484001,
|
||||
"rbf": true
|
||||
},
|
||||
"time": 1683864993,
|
||||
"interval": 352,
|
||||
"fullRbf": false,
|
||||
"replaces": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"replaces": null
|
||||
}
|
||||
60
frontend/cypress/fixtures/mainnet_tx_cached.json
Normal file
60
frontend/cypress/fixtures/mainnet_tx_cached.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"vsize": 109,
|
||||
"feePerVsize": 80.49427917620137,
|
||||
"effectiveFeePerVsize": 35.05247612484001,
|
||||
"txid": "21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f",
|
||||
"version": 2,
|
||||
"locktime": 0,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "1e3bd5c634781a6ba8bb3d3385b14739bf38cad5332d5fbc5c0ab775e54b9aef",
|
||||
"vout": 144,
|
||||
"prevout": {
|
||||
"scriptpubkey": "0014d98654186b90d95da7e31a30929f5b5b6a0af250",
|
||||
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 d98654186b90d95da7e31a30929f5b5b6a0af250",
|
||||
"scriptpubkey_type": "v0_p2wpkh",
|
||||
"scriptpubkey_address": "bc1qmxr9gxrtjrv4mflrrgcf986mtd4q4ujss432tk",
|
||||
"value": 253017648
|
||||
},
|
||||
"scriptsig": "",
|
||||
"scriptsig_asm": "",
|
||||
"witness": [
|
||||
"30440220448e8f58fcdea87c1969d58438b49da5b43712380bc4c68b02d22cf6b164907302207b2ed660f1a5b3b74f712961ffb3f3a7d1ac6e48b269ea6ff15df985042211f301",
|
||||
"02e39a1f3583e382cec1a1fab6a3f5950b6403c953fada58d809127a497f502ebe"
|
||||
],
|
||||
"is_coinbase": false,
|
||||
"sequence": 4294967293
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"scriptpubkey": "0014edb5167da7e97c73d7931eb2130ac3e34e6845a9",
|
||||
"scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 edb5167da7e97c73d7931eb2130ac3e34e6845a9",
|
||||
"scriptpubkey_type": "v0_p2wpkh",
|
||||
"scriptpubkey_address": "bc1qak63vld8a97884unr6epxzkrud8xs3dfdqswy2",
|
||||
"value": 253008854
|
||||
}
|
||||
],
|
||||
"size": 191,
|
||||
"weight": 437,
|
||||
"fee": 8794,
|
||||
"status": {
|
||||
"confirmed": false
|
||||
},
|
||||
"firstSeen": 1683864993,
|
||||
"uid": 298353,
|
||||
"position": {
|
||||
"block": 0,
|
||||
"vsize": 886207.5
|
||||
},
|
||||
"cpfpChecked": true,
|
||||
"ancestors": [
|
||||
{
|
||||
"txid": "1e3bd5c634781a6ba8bb3d3385b14739bf38cad5332d5fbc5c0ab775e54b9aef",
|
||||
"fee": 169220,
|
||||
"weight": 19877
|
||||
}
|
||||
],
|
||||
"descendants": [],
|
||||
"bestDescendant": null
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { ClockMempoolComponent as ClockMempoolComponent } from './components/clo
|
||||
import { AddressComponent } from './components/address/address.component';
|
||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||
import { AboutComponent } from './components/about/about.component';
|
||||
import { AcceleratorLandingComponent } from './components/accelerator-landing/accelerator-landing.component';
|
||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
|
||||
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
|
||||
@@ -271,6 +272,10 @@ let routes: Routes = [
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'accelerator',
|
||||
component: AcceleratorLandingComponent,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
component: BlocksList,
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
.pagination-container {
|
||||
float: none;
|
||||
margin-bottom: 200px;
|
||||
@media(min-width: 400px){
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.container-xl {
|
||||
padding-bottom: 110px;
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
<h5 class="card-title">US Dollar - BTC/USD</h5>
|
||||
<div class="chart-container">
|
||||
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
|
||||
<app-lightweight-charts [height]="300" [data]="hlocData.hloc" [volumeData]="hlocData.volume" [precision]="2"></app-lightweight-charts>
|
||||
<app-lightweight-charts [height]="300" [data]="hlocData.hloc" [volumeData]="hlocData.volume" [precision]="2"></app-lightweight-charts>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
</ng-template>
|
||||
</td>
|
||||
<td>{{ ticker.volume?.num_trades ? ticker.volume?.num_trades : 0 }}</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -105,9 +105,6 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<app-language-selector></app-language-selector>
|
||||
|
||||
<app-global-footer></app-global-footer>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingTmpl>
|
||||
@@ -124,4 +121,4 @@
|
||||
|
||||
<ng-template #loading>
|
||||
<div class="skeleton-loader shorter"></div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
@@ -393,27 +393,6 @@
|
||||
|
||||
<div class="footer-links">
|
||||
<a href="/3rdpartylicenses.txt">Third-party Licenses</a>
|
||||
<div class="social-icons">
|
||||
<a target="_blank" href="https://github.com/mempool/mempool">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://twitter.com/mempool">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://youtube.com/@mempool">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="youtube" class="svg-inline--fa fa-youtube fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"/></svg>
|
||||
</a>
|
||||
<a target="_blank" href="https://bitcointv.com/c/mempool/videos" class="bitcointv">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" focusable="false" viewBox="0 0 440 440"><path d="M225.57,2.08l-.69-.45a4.22,4.22,0,0,0-5.72,1.23L182.33,46.09a4,4,0,0,0,.88,5.81l9.38,6.38L173.48,97.49a4.22,4.22,0,0,0,2.45,4.19s3.55.7,4.53-1l41.92-40.56a3.62,3.62,0,0,0-1.51-5.1l-10.55-6.12L227.44,6.79A4.26,4.26,0,0,0,225.57,2.08Z" fill="currentColor"></path><path d="M118.52,401.83c-62.51,0-113.37-51-113.37-113.67V214.68C5.15,152,56,101,118.52,101H342.08a24.82,24.82,0,0,1,24.76,24.83V377a24.81,24.81,0,0,1-24.76,24.82Z"></path><path d="M342.08,105.18a20.65,20.65,0,0,1,20.61,20.66V377a20.66,20.66,0,0,1-20.61,20.66H118.52C58.3,397.67,9.31,348.55,9.31,288.16V214.68c0-60.38,49-109.5,109.21-109.5H342.08m0-8.34H118.52C53.62,96.84,1,149.6,1,214.68v73.48C1,353.24,53.62,406,118.52,406H342.08A29,29,0,0,0,371,377V125.84a29,29,0,0,0-28.92-29Z" fill="currentColor"></path><path fill="currentColor" d="M344.69,346.23A25.84,25.84,0,1,0,335,369.87l-10.22-10.2a11.69,11.69,0,1,1,4.77-5.12l10.31,10.28A25.84,25.84,0,0,0,344.69,346.23Z"></path><path fill="currentColor" d="M315.82,257.61a25.67,25.67,0,0,0-12.53,5.22L315,274.49a9.58,9.58,0,0,1,2.11-.73A9.72,9.72,0,1,1,309.4,283a9.4,9.4,0,0,1,.75-3.41L298.4,267.84a25.77,25.77,0,1,0,17.42-10.23Z"></path><path fill="currentColor" d="M313,214a7.76,7.76,0,1,1,1.41,10.91,7.62,7.62,0,0,1-2.19-2.69l-18.67-.14a25.94,25.94,0,1,0,.05-7l18.64.14A7.4,7.4,0,0,1,313,214Z"></path><path fill="currentColor" d="M341.2,144.08h-6.32c-1.67,0-3.61,1.87-3.61,4.29s1.94,4.29,3.61,4.29h6.32c1.67,0,3.61-1.87,3.61-4.29S342.87,144.08,341.2,144.08Z"></path><path fill="currentColor" d="M301.75,144.08h-6.44c-1.67,0-3.61,1.87-3.61,4.29s1.94,4.29,3.61,4.29h6.44c1.67,0,3.61-1.87,3.61-4.29S303.42,144.08,301.75,144.08Z"></path><path fill="currentColor" d="M321.77,144.08h-7c-1.67,0-3.62,1.87-3.62,4.29s1.95,4.29,3.62,4.29h7c1.67,0,3.62-1.87,3.62-4.29S323.44,144.08,321.77,144.08Z"></path><ellipse fill="currentColor" cx="295.97" cy="127.61" rx="4.27" ry="4.29"></ellipse><path fill="currentColor" d="M340.54,131.9a4.29,4.29,0,1,0-4.27-4.29A4.28,4.28,0,0,0,340.54,131.9Z"></path><path fill="currentColor" d="M318.26,131.9a4.29,4.29,0,1,0-4.27-4.29A4.29,4.29,0,0,0,318.26,131.9Z"></path><ellipse fill="currentColor" cx="295.97" cy="169.13" rx="4.27" ry="4.29"></ellipse><path fill="currentColor" d="M340.54,164.84a4.3,4.3,0,1,0,4.27,4.29A4.29,4.29,0,0,0,340.54,164.84Z"></path><path fill="currentColor" d="M318.26,164.84a4.3,4.3,0,1,0,4.28,4.29A4.29,4.29,0,0,0,318.26,164.84Z"></path><path d="M108.62,256.87c8.36-1,7.68-7.76,3.14-17-3.64-7.4-9.74-16.39-15.75-25.36-14.23-21.23-27.69-42.23-5.35-41.07,19.55,1,42.9,18.63,68.22,36.74,31.1,22.24,65.16,45.21,98.81,39.11a151.19,151.19,0,0,1,20-2.37V221a92,92,0,0,0-91.91-92.16H124.33A92,92,0,0,0,32.42,221v17.59c17.71,3.81,31,9.94,43.8,14.15C86.6,256.16,96.69,258.31,108.62,256.87Z"></path><path d="M273.37,310.79c-35-15.26-76.67-32.1-104-23.59-3.15,1-5,2.3-6,3.85-3.35,5.31,4.67,13.57,14.89,22.17,7.17,6,15.36,12.21,21.44,17.64,11.47,10.26,15.35,17.84-9.89,16.62-29.75-1.44-49.18-13.75-71.18-24l-.29-.14a165.84,165.84,0,0,0-22.93-8.91c-15.74-4.67-34.22-6.79-58.51-3.28A91.93,91.93,0,0,0,124.33,375h61.45A92,92,0,0,0,273.37,310.79Z"></path><path fill="currentColor" d="M257.69,249.31C224,255.41,190,232.44,158.88,210.2c-25.32-18.11-48.67-35.72-68.22-36.74C68.32,172.3,81.78,193.3,96,214.53c6,9,12.11,18,15.75,25.36,4.54,9.22,5.22,16-3.14,17-11.93,1.44-22-.71-32.4-4.13-12.8-4.21-26.09-10.34-43.8-14.15v44.26c0,1.26.14,2.48.19,3.72a91.8,91.8,0,0,0,2.9,19.62c.43,1.67.84,3.34,1.37,5,24.29-3.51,42.77-1.39,58.51,3.28a165.84,165.84,0,0,1,22.93,8.91c.39-.12.76-.26,1.14-.39l-.85.53c22,10.25,41.43,22.56,71.18,24,25.24,1.22,21.36-6.36,9.89-16.62-6.08-5.43-14.27-11.61-21.44-17.64-10.22-8.6-18.24-16.86-14.89-22.17,1-1.55,2.87-2.87,6-3.85,27.33-8.51,69,8.33,104,23.59.32-1,.56-2.05.84-3.07a92.33,92.33,0,0,0,3.48-24.87V246.94A151.19,151.19,0,0,0,257.69,249.31Z"></path><path fill="currentColor" d="M192,137a78,78,0,0,1,77.78,78v73.91a78,78,0,0,1-77.78,78H118.51a78,78,0,0,1-77.78-78V215a78,78,0,0,1,77.78-78H192m0-8.33H118.51A86.21,86.21,0,0,0,32.42,215v73.91a86.21,86.21,0,0,0,86.09,86.33H192a86.21,86.21,0,0,0,86.09-86.33V215A86.21,86.21,0,0,0,192,128.64Z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-version" *ngIf="officialMempoolSpace">
|
||||
{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [<a href="https://github.com/mempool/mempool/commit/{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}">{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}</a>]
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<div id="hero">
|
||||
<div class="bg-effect top" style="background-image: url(/resources/accelerator-landing/wind.svg);"></div>
|
||||
<h2>Stuck Bitcoin transaction?<br>Get it confirmed quickly with Mempool Accelerator™</h2>
|
||||
<!--<p>Simple & reliable process with provably fair pricing.</p>-->
|
||||
<p>Get your transaction confirmed quicker through mempool.space's network of mining pool partners—fairly, reliably, and transparently.</p>
|
||||
<video src="/resources/accelerator-landing/pre.mp4" poster="" controls loop playsinline [autoplay]="true" [muted]="true"></video>
|
||||
</div>
|
||||
|
||||
<div class="cta panel">
|
||||
<a href="/signup" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)">Join the Waitlist</a>
|
||||
</div>
|
||||
|
||||
<div class="explainer panel">
|
||||
<div class="point">
|
||||
<svg width="72" height="72" viewBox="0 0 390.677 504" fill="#7647c7" xmlns="http://www.w3.org/2000/svg"><path d="M268.162 44.289c.906-4.664 2.062-14.18 5.98-16.25 6.77-3.64 25.328 3.246 11.762-3.668-16.52-8.176-5.925-15.012-4.66-24.371-11.262 8.973-12.23 14.621-24 4.344-12.746-12.273 5.102 9.851-.707 14.758-3.707 4.87-14.508 8.808-11.848 9.351 24.266-4.093 16.891 3.454 23.473 15.836zm45.062 219.2c-1.813-6.324 10.074-22.637 1.164-13.512-3.445 2.812-6.937 7.117-11.473 7.062-5.047.336-16.824-14.027-13.613-7.609 6.106 12.801 7 15.574-1.809 21.258-2.636 2.156-5.582 2.8-6.957 4.625 3.938.824 12.57-2.781 17.406.617 4.016 3.602 4.68 11.711 5.72 15.512 1.16 1.223.964-1.922 1.292-2.773 3.125-22.16 12.125-11.691 21.992-14.617-4.133-2.254-12.832-6.328-13.723-10.562zm-191.4-163.89c.21.297.605.371.656-.402.805-3.168.93-9.118 4.395-11.355 6.754-2.575 18.324 2.933 6.758-3.446-9.07-4.316-3.332-11.219-1.614-16.152-9.129 5.434-8.21 11.16-19.05 1.301-5.77-5.683 5.433 9.79.726 12.453-2.43 2.91-9.867 5.809-8.113 6.235 14.422-2.508 13.71.625 16.242 11.367zm70.609-37.258c11.598-3.254 7.906 8.445 9.715 6.094 1.16-9.051 4.097-6.227 9.25-6.52-1.047-1.28-4.676-1.976-5.66-4.34-.621-2.199 1.253-4.984 1.843-6.68-6.343 4.688-5.761 5.333-11.715.013 3.383 9.246 1.942 6.546-3.433 11.434zm-74.285 106.19c2.742-9.61 2.488-6.313 9.57-7.145-11.633-4.078-.726-12.867-4.648-10.375-6.758 8.5-10.91-2.812-10.871.157.766 2.023 3.27 5.312 1.07 7.5-12.145 8.625 4.477-4.047 4.88 9.863zm204.16-107.66c11.598-3.254 7.906 8.445 9.715 6.094 1.16-9.051 4.097-6.227 9.25-6.52-1.047-1.28-4.672-1.976-5.66-4.34-.621-2.199 1.253-4.984 1.843-6.68-6.343 4.688-5.761 5.333-11.715.013 3.383 9.25 1.95 6.55-3.433 11.434zm64.059 133.48c-8.379 10.652-14.203-8.176-9.465 4.66 1.602 3.488-3.77 5.012-5.297 6.57 7.801-.593 7.875-.722 9.418 6.204.555 2.39 1.086-5.997 2.801-6.215 1.965-1.418 7.04.492 6.848-.508-11.398-4.238-1.434-11.512-4.305-10.711zm-28.039-69.969c-1.836-3.633 6.223-15.484 1.758-11.031-12.18 14.789-18.512-4.047-19.266.125 1.21 3.121 4.906 8.2 3.023 12.137-2.535 3.898-8.86 5.562-9.004 7.004 13.961-1.434 13.641-1.172 16.441 11.055.801 3.336 2.067-10.465 4.328-10.824 3.5-3.05 13.625.531 12.441-1.25-2.425-1.48-8.043-3.61-9.722-7.215zm-59.516-39.199c.59-1.379 2.367-3.488.129-4.91-2.488-.758-3.2 1.98-4.602 2.855-61.734 59.137-63.812 54.7-124.71 1.618-11.234-8.813.453 10.812 1.977 14.449 6.242 13.113 19.168 43.133 17.887 53.445-.711 13.625-9.012 20.926-18.312 27.91-14.992 11.608-32.258 21.706-43.418 27.854-5.824 7.051 14.19.84 16.87 1.262 13.923-1.875 43.794-4.312 53.923-.762L1.586 490.194c-2.777 3.97-1.813 9.442 2.156 12.22 3.969 2.777 9.442 1.812 12.22-2.157l197.02-281.37c12.101 18.777 17.253 48.938 20.82 65.387 6.835 19.691 7.163-63.8 32.02-74.312 13.155-8.14 49.061-3.629 59.077-3.18 3.863-.257 23.031 4.7 17.06-1.882-76.54-41.184-76.36-42.855-43.146-119.72z"/></svg>
|
||||
<p class="point-lead">Easy</p>
|
||||
<p class="point-body">Simply tell us which transaction you need accelerated, set the maximum rate you're willing to pay, and we'll take care of the rest.</p>
|
||||
</div>
|
||||
<div class="point">
|
||||
<svg width="72" height="72" viewBox="0 0 466.09 445.916" fill="#7647c7" xmlns="http://www.w3.org/2000/svg"><path d="m434.87 159.649-40.781-84.11c7.023 1.204 13.37 2.461 18.77 3.774l4.262-11.844c-31.81-15.418-94.176-26.777-166.73-28.305 3.769-4.164 6.128-9.633 6.128-15.691C256.52 10.508 246.011 0 233.046 0s-23.473 10.508-23.473 23.473c0 6.059 2.36 11.527 6.13 15.691-72.56 1.527-134.93 12.887-166.73 28.305l4.261 11.844c5.398-1.313 11.746-2.57 18.77-3.773l-40.781 84.109H0c15.688 28.992 46.363 48.684 81.641 48.684 35.278 0 65.953-19.691 81.641-48.684h-31.219L89.965 72.825c34.863-4.653 82.227-7.86 134.67-8.118v52.02h-10.68v261.15H195.6c-38.832 0-70.941 30.051-74.328 68.04h223.55c-3.387-37.989-35.5-68.04-74.328-68.04h-18.359l-.004-261.15h-10.68v-52.02c52.445.258 99.809 3.465 134.67 8.118l-42.098 86.824h-31.215c15.688 28.992 46.363 48.684 81.641 48.684 35.278 0 65.953-19.691 81.641-48.684zm-311.61 0H40.01l41.52-85.637c.066-.012.129-.02.191-.028zm219.56 0 41.535-85.664c.062.008.125.02.191.027l41.52 85.637z"/></svg>
|
||||
<p class="point-lead">Fair</p>
|
||||
<p class="point-body">Our service is based on feerates and block templates you can verify by running The Mempool Open Source Project™ yourself.</p>
|
||||
</div>
|
||||
<div class="point">
|
||||
<svg width="72" height="72" viewBox="0 0 700.261 296.22" fill="#7647c7" xmlns="http://www.w3.org/2000/svg"><path d="M512.791 44.48C519.815 2.339 585.365 2.339 618.141 0c0 0 63.211 53.848 81.941 166.22 4.684 30.434-84.28 39.801-84.28 39.801-2.34-58.527-32.778-117.06-63.212-170.9-16.387 2.34-25.754 4.684-39.8 9.363zm-351.17 4.684C147.574 9.363 82.023 14.047 49.241 18.73c0 0-51.504 63.21-49.164 177.93 0 30.434 86.621 16.387 86.621 16.387-9.363-63.211 11.707-128.76 32.777-173.25 16.387 2.34 28.094 7.023 42.141 9.363zm32.777 189.63c-2.34-7.024 2.34-14.047 4.684-21.07-7.023-2.34-9.363-7.024-21.07-14.047-25.754 16.387-7.023 53.848 16.387 35.117zm58.527-7.024c-11.707-39.8-60.87-9.363-51.504 7.024 4.684 9.363 18.73 14.047 28.094 14.047 14.047-2.34 14.047-16.387 23.41-21.07zm42.141 25.754c2.34-18.73-28.094-25.754-39.8-18.73-14.048 11.707-16.388 23.41-9.364 30.434 11.707 9.364 44.48-4.683 49.164-11.707zm-16.387 30.434c11.707 11.707 44.48-4.683 39.801-18.73-7.023-11.707-30.434-2.34-37.457 2.34-2.34 2.34-7.023 9.363-2.34 16.387zM93.73 191.97c49.164 0 63.211-9.363 110.04 21.07 21.07-11.707 46.824-9.363 56.188 16.387 18.73 0 42.141 4.684 42.141 28.094 28.094 0 30.434 18.73 14.047 32.777 14.047 4.684 56.188 9.364 70.234 2.34 4.684-2.34 14.047-7.023 4.684-9.363s-28.094-2.34-32.777-4.684c-9.363 0-7.023-4.683 0-4.683 14.047 2.34 49.164 4.683 63.211 2.34 11.707-2.34 25.754-9.364 9.363-18.73-21.07-7.024-53.848-16.388-58.527-23.41 2.34-9.364 46.824 14.046 65.551 23.41 9.364 2.34 23.41 4.683 32.777 0 14.047-4.684 4.684-11.708-2.34-16.388-16.387-14.047-51.504-44.48-65.55-58.527-9.364-11.707-2.34-11.707 7.023-4.683 11.707 11.707 37.457 37.457 74.918 67.895 14.047 7.023 35.117-7.024 25.754-25.754 0-2.34-4.684-9.364-11.707-18.73-35.117-58.527-114.71-107.69-170.9-86.621-18.73 7.023-23.41 30.434-44.48 51.504-21.07 18.73-37.457 16.387-58.527 4.683 23.41-28.094 28.094-44.48 37.457-77.258 9.363-21.07 16.387-21.07 32.777-32.777 42.14-28.094 30.434-21.07 11.707-21.07-79.598-2.34-79.598 18.73-100.67 21.07-28.094 0-65.551-11.707-81.941-14.047-21.07 42.141-32.777 98.328-30.434 145.15zm358.2-124.08c-44.48 9.364-86.62-35.117-107.69-30.434-7.023 4.684-63.21 39.801-70.234 49.164-16.387 30.434 0 25.754-37.457 84.281 44.48 21.07 58.527-53.848 86.621-63.21 74.918-25.755 152.18 44.48 182.61 91.304 39.801-18.73 67.895-16.387 98.328-23.41-9.363-46.824-37.457-100.67-56.188-133.45-28.094 4.684-74.918 21.07-95.988 25.754z" fill-rule="evenodd"/></svg>
|
||||
<p class="point-lead">Reliable</p>
|
||||
<p class="point-body">Mempool's best-in-class fee estimation—combined with its mining pool relationships—means you can expect your transaction to be confirmed fast.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel faq">
|
||||
<div class="endpoint-container">
|
||||
<a id="faq-1" class="section-header"><table><tr><td>What makes Mempool Accelerator™ different from other accelerators?</td></tr></table></a>
|
||||
<p>Mempool Accelerator™ strives to be fair, reliable, and transparent. The fee you pay to accelerate, for example, is based on the fair market price for blockspace—which you can verify from your own node running The Mempool Open Source Project™.</p>
|
||||
</div>
|
||||
<div class="endpoint-container">
|
||||
<a id="faq-1" class="section-header"><table><tr><td>My transaction has been stuck for days. How quickly can Mempool Accelerator™ get it confirmed?</td></tr></table></a>
|
||||
<p>Mempool Accelerator™ prioritizes your transaction directly with mining pools based on the feerate ceiling you determine.</p><p>While it is not possible to provide a precise timeframe, your transaction will go from being "stuck" in the mempool to being in the top portion of pending transactions likely to be confirmed soon.</p>
|
||||
</div>
|
||||
<div class="endpoint-container">
|
||||
<a id="faq-1" class="section-header"><table><tr><td>When will Mempool Accelerator™ be available?</td></tr></table></a>
|
||||
<p>Soon™. Join the waitlist to be notified when it's ready.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cta panel bottom" style="margin-bottom: 30px;">
|
||||
<a href="/signup" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)">Join the Waitlist</a>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
p {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
tr {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#hero {
|
||||
text-align: center;
|
||||
margin: 60px auto;
|
||||
width: 100%;
|
||||
max-width: 2000px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
#hero p {
|
||||
color: rgba(255, 255, 255, 0.568627451);
|
||||
}
|
||||
|
||||
#hero video {
|
||||
height: auto;
|
||||
width: 50%;
|
||||
max-width: 675px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 30px auto;
|
||||
font-size: 2.35rem;
|
||||
}
|
||||
|
||||
.panel {
|
||||
padding: 30px 40px 30px 40px;
|
||||
}
|
||||
|
||||
.panel-lead {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.partner.panel {
|
||||
background-color: #1d1f31; //#1d1f31
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.partner.panel svg {
|
||||
width: 150px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.cta.panel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta.bottom.panel {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.explainer.panel {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 2000px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 75px;
|
||||
}
|
||||
|
||||
.explainer.panel .point {
|
||||
text-align: center;
|
||||
width: 460px;
|
||||
height: 300px;
|
||||
padding: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.explainer.panel .point .point-lead {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 26px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.explainer.panel .point svg {
|
||||
height: auto;
|
||||
width: 115px;
|
||||
border-radius: 50%;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.panel.faq {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.endpoint-container {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.endpoint-container .section-header {
|
||||
display: block;
|
||||
background-color: #2d3348;
|
||||
color: #1bd8f4;
|
||||
padding: 1rem 1.3rem;
|
||||
font-weight: 700;
|
||||
border-radius: .25rem;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.endpoint-container .section-header:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.bg-effect {
|
||||
position: absolute;
|
||||
z-index: -100 !important;
|
||||
height: 90vh;
|
||||
opacity: 0.3;
|
||||
background-repeat: no-repeat;
|
||||
max-width: calc(100% - 50px);
|
||||
animation: 2s ease forwards .5s bg-scale;
|
||||
}
|
||||
|
||||
.bg-effect.top {
|
||||
background-position: top left;
|
||||
top: 20px;
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
|
||||
#hero img {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.explainer.panel .point {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerator-landing',
|
||||
templateUrl: './accelerator-landing.component.html',
|
||||
styleUrls: ['./accelerator-landing.component.scss'],
|
||||
})
|
||||
export class AcceleratorLandingComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -82,6 +82,10 @@
|
||||
|
||||
<br />
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<app-global-footer *ngIf="footerVisible"></app-global-footer>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -17,6 +17,12 @@ li.nav-item {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
footer > .container-fluid {
|
||||
padding-bottom: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.navbar {
|
||||
padding: 0rem 2rem;
|
||||
|
||||
@@ -17,6 +17,7 @@ export class BisqMasterPageComponent implements OnInit {
|
||||
isMobile = window.innerWidth <= 767.98;
|
||||
urlLanguage: string;
|
||||
networkPaths: { [network: string]: string };
|
||||
footerVisible = true;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
@@ -31,6 +32,11 @@ export class BisqMasterPageComponent implements OnInit {
|
||||
this.urlLanguage = this.languageService.getLanguageForUrl();
|
||||
this.navigationService.subnetPaths.subscribe((paths) => {
|
||||
this.networkPaths = paths;
|
||||
if (paths.mainnet.indexOf('docs') > -1) {
|
||||
this.footerVisible = false;
|
||||
} else {
|
||||
this.footerVisible = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="full-container">
|
||||
<div class="card-header mb-0 mb-md-4">
|
||||
<div class="d-flex d-md-block align-items-baseline">
|
||||
<span i18n="mining.block-prediction-accuracy">Block Prediction Accuracy</span>
|
||||
<span i18n="mining.blocks-health">Block Health</span>
|
||||
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
||||
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
|
||||
</button>
|
||||
@@ -12,34 +12,34 @@
|
||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 432" [class.active]="radioGroupForm.get('dateSpan').value === '3d'">
|
||||
<input type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 3D
|
||||
<input type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 3D
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 1008" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
|
||||
<input type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 1W
|
||||
<input type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 1W
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
||||
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 3M
|
||||
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 3M
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
|
||||
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 6M
|
||||
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 6M
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 52560" [class.active]="radioGroupForm.get('dateSpan').value === '1y'">
|
||||
<input type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 1Y
|
||||
<input type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 1Y
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 105120" [class.active]="radioGroupForm.get('dateSpan').value === '2y'">
|
||||
<input type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 2Y
|
||||
<input type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 2Y
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 157680" [class.active]="radioGroupForm.get('dateSpan').value === '3y'">
|
||||
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 3Y
|
||||
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 3Y
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount > 157680" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
|
||||
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> ALL
|
||||
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> ALL
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -13,9 +13,9 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-prediction-graph',
|
||||
templateUrl: './block-prediction-graph.component.html',
|
||||
styleUrls: ['./block-prediction-graph.component.scss'],
|
||||
selector: 'app-block-health-graph',
|
||||
templateUrl: './block-health-graph.component.html',
|
||||
styleUrls: ['./block-health-graph.component.scss'],
|
||||
styles: [`
|
||||
.loadingGraphs {
|
||||
position: absolute;
|
||||
@@ -26,7 +26,7 @@ import { StateService } from '../../services/state.service';
|
||||
`],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlockPredictionGraphComponent implements OnInit {
|
||||
export class BlockHealthGraphComponent implements OnInit {
|
||||
@Input() right: number | string = 45;
|
||||
@Input() left: number | string = 75;
|
||||
|
||||
@@ -60,7 +60,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Prediction Accuracy`);
|
||||
this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Health`);
|
||||
this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
@@ -80,7 +80,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
this.storageService.setValue('miningWindowPreference', timespan);
|
||||
this.timespan = timespan;
|
||||
this.isLoading = true;
|
||||
return this.apiService.getHistoricalBlockPrediction$(timespan)
|
||||
return this.apiService.getHistoricalBlocksHealth$(timespan)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.prepareChartOptions(response.body);
|
||||
@@ -163,7 +163,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
hideOverlap: true,
|
||||
padding: [0, 5],
|
||||
},
|
||||
data: data.map(prediction => prediction[0])
|
||||
data: data.map(health => health[0])
|
||||
},
|
||||
yAxis: data.length === 0 ? undefined : [
|
||||
{
|
||||
@@ -186,12 +186,12 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
series: data.length === 0 ? undefined : [
|
||||
{
|
||||
zlevel: 0,
|
||||
name: $localize`Match rate`,
|
||||
data: data.map(prediction => ({
|
||||
value: prediction[2],
|
||||
block: prediction[1],
|
||||
name: $localize`Health`,
|
||||
data: data.map(health => ({
|
||||
value: health[2],
|
||||
block: health[1],
|
||||
itemStyle: {
|
||||
color: this.getPredictionColor(prediction[2])
|
||||
color: this.getHealthColor(health[2])
|
||||
}
|
||||
})),
|
||||
type: 'bar',
|
||||
@@ -257,7 +257,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';
|
||||
}
|
||||
|
||||
getPredictionColor(matchRate) {
|
||||
getHealthColor(matchRate) {
|
||||
return this.colorGradient(
|
||||
Math.pow((100 - matchRate) / 100, 0.5),
|
||||
{red: 67, green: 171, blue: 71},
|
||||
@@ -294,7 +294,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
excludeComponents: ['dataZoom'],
|
||||
}), `block-fees-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
||||
}), `block-health-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = prevBottom;
|
||||
this.chartOptions.backgroundColor = 'none';
|
||||
@@ -37,7 +37,7 @@ export default class TxView implements TransactionStripped {
|
||||
value: number;
|
||||
feerate: number;
|
||||
rate?: number;
|
||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
context?: 'projected' | 'actual';
|
||||
scene?: BlockScene;
|
||||
|
||||
@@ -171,6 +171,7 @@ export default class TxView implements TransactionStripped {
|
||||
case 'censored':
|
||||
return auditColors.censored;
|
||||
case 'missing':
|
||||
case 'sigop':
|
||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||
case 'fresh':
|
||||
return auditColors.missing;
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<td *ngSwitchCase="'found'"><span class="badge badge-success" i18n="transaction.audit.match">Match</span></td>
|
||||
<td *ngSwitchCase="'censored'"><span class="badge badge-danger" i18n="transaction.audit.removed">Removed</span></td>
|
||||
<td *ngSwitchCase="'missing'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||
<td *ngSwitchCase="'sigop'"><span class="badge badge-warning" i18n="transaction.audit.sigop">High sigop count</span></td>
|
||||
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
||||
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
||||
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||
|
||||
@@ -335,6 +335,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
const isMissing = {};
|
||||
const isSelected = {};
|
||||
const isFresh = {};
|
||||
const isSigop = {};
|
||||
this.numMissing = 0;
|
||||
this.numUnexpected = 0;
|
||||
|
||||
@@ -354,6 +355,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
for (const txid of blockAudit.freshTxs || []) {
|
||||
isFresh[txid] = true;
|
||||
}
|
||||
for (const txid of blockAudit.sigopTxs || []) {
|
||||
isSigop[txid] = true;
|
||||
}
|
||||
// set transaction statuses
|
||||
for (const tx of blockAudit.template) {
|
||||
tx.context = 'projected';
|
||||
@@ -362,7 +366,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
} else if (inBlock[tx.txid]) {
|
||||
tx.status = 'found';
|
||||
} else {
|
||||
tx.status = isFresh[tx.txid] ? 'fresh' : 'missing';
|
||||
tx.status = isFresh[tx.txid] ? 'fresh' : (isSigop[tx.txid] ? 'sigop' : 'missing');
|
||||
isMissing[tx.txid] = true;
|
||||
this.numMissing++;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private websocketService: WebsocketService,
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
this.updateTime();
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="stats top right">
|
||||
<p class="label" i18n="clock.priority-rate|priority fee rate">priority rate</p>
|
||||
<p *ngIf="recommendedFees$ | async as recommendedFees;" i18n="shared.sat-vbyte|sat/vB">{{ recommendedFees.fastestFee + 300 }} sat/vB</p>
|
||||
<p *ngIf="recommendedFees$ | async as recommendedFees;" i18n="shared.sat-vbyte|sat/vB">{{ recommendedFees.fastestFee }} sat/vB</p>
|
||||
</div>
|
||||
<div *ngIf="mode !== 'mempool' && block" class="stats bottom left">
|
||||
<p [innerHTML]="block.size | bytes: 2"></p>
|
||||
|
||||
@@ -52,7 +52,7 @@ export class ClockComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.resizeCanvas();
|
||||
this.websocketService.want(['blocks']);
|
||||
this.websocketService.want(['blocks', 'stats', 'mempool-blocks']);
|
||||
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.subscribe(([block]) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="fee-estimation-wrapper" *ngIf="(isLoadingWebSocket$ | async) === false && (recommendedFees$ | async) as recommendedFees; else loadingFees">
|
||||
<div class="fee-estimation-wrapper" *ngIf="(isLoading$ | async) === false && (recommendedFees$ | async) as recommendedFees; else loadingFees">
|
||||
<div class="d-flex">
|
||||
<div class="fee-progress-bar" [style.background]="noPriority">
|
||||
<span class="fee-label" i18n="fees-box.no-priority" i18n-ngbTooltip="Transaction feerate tooltip (economy)" ngbTooltip="Either 2x the minimum, or the Low Priority rate (whichever is lower)" placement="top">No Priority</span>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { map, startWith, tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-fees-box',
|
||||
@@ -12,7 +12,7 @@ import { tap } from 'rxjs/operators';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FeesBoxComponent implements OnInit {
|
||||
isLoadingWebSocket$: Observable<boolean>;
|
||||
isLoading$: Observable<boolean>;
|
||||
recommendedFees$: Observable<Recommendedfees>;
|
||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
||||
noPriority = '#2e324e';
|
||||
@@ -22,27 +22,24 @@ export class FeesBoxComponent implements OnInit {
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
||||
this.isLoading$ = combineLatest(
|
||||
this.stateService.isLoadingWebSocket$.pipe(startWith(false)),
|
||||
this.stateService.loadingIndicators$.pipe(startWith({ mempool: 0 })),
|
||||
).pipe(map(([socket, indicators]) => {
|
||||
return socket || (indicators.mempool != null && indicators.mempool !== 100);
|
||||
}));
|
||||
this.recommendedFees$ = this.stateService.recommendedFees$
|
||||
.pipe(
|
||||
tap((fees) => {
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.economyFee >= feeLvl);
|
||||
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const startColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
|
||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.hourFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const lowColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
|
||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.halfHourFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const medColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
|
||||
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.fastestFee >= feeLvl);
|
||||
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
|
||||
const highColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
const endColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
|
||||
|
||||
this.gradient = `linear-gradient(to right, ${lowColor} 0%, ${medColor} 50%, ${highColor} 100%)`;
|
||||
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
|
||||
this.noPriority = startColor;
|
||||
}
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<a class="dropdown-item" routerLinkActive="active"
|
||||
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
|
||||
<a *ngIf="stateService.env.AUDIT" class="dropdown-item" routerLinkActive="active"
|
||||
[routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" i18n="mining.block-prediction-accuracy">Block Prediction Accuracy</a>
|
||||
[routerLink]="['/graphs/mining/block-health' | relativeUrl]" i18n="mining.block-health">Block Health</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</ng-container>
|
||||
</a>
|
||||
|
||||
<div ngbDropdown (window:resize)="onResize($event)" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
||||
<div ngbDropdown (window:resize)="onResize()" class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
||||
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
|
||||
<app-svg-images [name]="network.val === '' ? 'liquid' : network.val" width="22" height="22" viewBox="0 0 125 125" style="width: 30px; height: 30px; margin-right: 5px;"></app-svg-images>
|
||||
</button>
|
||||
@@ -92,10 +92,10 @@
|
||||
|
||||
<app-testnet-alert *ngIf="network.val === 'liquidtestnet'"></app-testnet-alert>
|
||||
|
||||
<br />
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<app-global-footer></app-global-footer>
|
||||
<app-global-footer *ngIf="footerVisible"></app-global-footer>
|
||||
|
||||
</ng-container>
|
||||
@@ -19,6 +19,7 @@ export class LiquidMasterPageComponent implements OnInit {
|
||||
network$: Observable<string>;
|
||||
urlLanguage: string;
|
||||
networkPaths: { [network: string]: string };
|
||||
footerVisible = true;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
@@ -27,13 +28,18 @@ export class LiquidMasterPageComponent implements OnInit {
|
||||
private navigationService: NavigationService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.env = this.stateService.env;
|
||||
this.connectionState$ = this.stateService.connectionState$;
|
||||
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
||||
this.urlLanguage = this.languageService.getLanguageForUrl();
|
||||
this.navigationService.subnetPaths.subscribe((paths) => {
|
||||
this.networkPaths = paths;
|
||||
if (paths.liquid.indexOf('docs') > -1) {
|
||||
this.footerVisible = false;
|
||||
} else {
|
||||
this.footerVisible = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,7 +47,7 @@ export class LiquidMasterPageComponent implements OnInit {
|
||||
this.navCollapsed = !this.navCollapsed;
|
||||
}
|
||||
|
||||
onResize(event: any) {
|
||||
onResize(): void {
|
||||
this.isMobile = window.innerWidth <= 767.98;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</ng-container>
|
||||
</a>
|
||||
|
||||
<div (window:resize)="onResize($event)" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
||||
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
||||
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split" aria-haspopup="true">
|
||||
<app-svg-images [name]="network.val === '' ? 'bitcoin' : network.val" width="20" height="20" viewBox="0 0 65 65" style="width: 30px; height: 30px; margin-right: 5px;"></app-svg-images>
|
||||
</button>
|
||||
@@ -64,9 +64,9 @@
|
||||
|
||||
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
|
||||
|
||||
<main style="margin-top: 24px;">
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<app-global-footer></app-global-footer>
|
||||
<app-global-footer *ngIf="footerVisible"></app-global-footer>
|
||||
</ng-container>
|
||||
|
||||
@@ -20,6 +20,8 @@ export class MasterPageComponent implements OnInit {
|
||||
urlLanguage: string;
|
||||
subdomain = '';
|
||||
networkPaths: { [network: string]: string };
|
||||
networkPaths$: Observable<Record<string, string>>;
|
||||
footerVisible = true;
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
@@ -28,7 +30,7 @@ export class MasterPageComponent implements OnInit {
|
||||
private navigationService: NavigationService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.env = this.stateService.env;
|
||||
this.connectionState$ = this.stateService.connectionState$;
|
||||
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
||||
@@ -36,6 +38,11 @@ export class MasterPageComponent implements OnInit {
|
||||
this.subdomain = this.enterpriseService.getSubdomain();
|
||||
this.navigationService.subnetPaths.subscribe((paths) => {
|
||||
this.networkPaths = paths;
|
||||
if (paths.mainnet.indexOf('docs') > -1) {
|
||||
this.footerVisible = false;
|
||||
} else {
|
||||
this.footerVisible = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,7 +50,7 @@ export class MasterPageComponent implements OnInit {
|
||||
this.navCollapsed = !this.navCollapsed;
|
||||
}
|
||||
|
||||
onResize(event: any) {
|
||||
onResize(): void {
|
||||
this.isMobile = window.innerWidth <= 767.98;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.mempoolEmptyBlocks.forEach((b) => {
|
||||
this.mempoolEmptyBlockStyles.push(this.getStyleForMempoolEmptyBlock(b.index));
|
||||
});
|
||||
this.reduceMempoolBlocksToFitScreen(this.mempoolEmptyBlocks);
|
||||
this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks);
|
||||
|
||||
this.mempoolBlocks.map(() => {
|
||||
this.updateMempoolBlockStyles();
|
||||
@@ -244,12 +244,33 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
this.animateEntry = false;
|
||||
this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks);
|
||||
}
|
||||
|
||||
trackByFn(index: number, block: MempoolBlock) {
|
||||
return (block.isStack) ? `stack-${block.index}` : block.index;
|
||||
}
|
||||
|
||||
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
||||
const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
|
||||
while (blocks.length < blocksAmount) {
|
||||
blocks.push({
|
||||
blockSize: 0,
|
||||
blockVSize: 0,
|
||||
feeRange: [],
|
||||
index: blocks.length,
|
||||
medianFee: 0,
|
||||
nTx: 0,
|
||||
totalFees: 0
|
||||
});
|
||||
}
|
||||
while (blocks.length > blocksAmount) {
|
||||
blocks.pop();
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
||||
let blocksAmount;
|
||||
|
||||
@@ -24,6 +24,7 @@ import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/grap
|
||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
@Input() data: any[];
|
||||
@Input() filterSize = 100000;
|
||||
@Input() limitFilterFee = 1;
|
||||
@Input() height: number | string = 200;
|
||||
@Input() top: number | string = 20;
|
||||
@Input() right: number | string = 10;
|
||||
@@ -40,7 +41,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
};
|
||||
windowPreference: string;
|
||||
hoverIndexSerie = 0;
|
||||
maxFee: number;
|
||||
feeLimitIndex: number;
|
||||
maxFeeIndex: number;
|
||||
feeLevelsOrdered = [];
|
||||
chartColorsOrdered = chartColors;
|
||||
inverted: boolean;
|
||||
@@ -98,8 +101,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
||||
let finalArray: number[][][] = [];
|
||||
const finalArray: number[][][] = [];
|
||||
let feesArray: number[][] = [];
|
||||
|
||||
let maxTier = 0;
|
||||
for (let index = 37; index > -1; index--) {
|
||||
feesArray = [];
|
||||
@@ -111,7 +115,8 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
});
|
||||
finalArray.push(feesArray);
|
||||
}
|
||||
this.feeLimitIndex = maxTier;
|
||||
this.maxFeeIndex = maxTier;
|
||||
|
||||
finalArray.reverse();
|
||||
return finalArray;
|
||||
}
|
||||
@@ -124,7 +129,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
const newColors = [];
|
||||
for (let index = 0; index < series.length; index++) {
|
||||
const value = series[index];
|
||||
if (index < this.feeLimitIndex) {
|
||||
if (index >= this.feeLimitIndex && index <= this.maxFeeIndex) {
|
||||
newColors.push(this.chartColorsOrdered[index]);
|
||||
seriesGraph.push({
|
||||
zlevel: 0,
|
||||
@@ -181,7 +186,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
alwaysShowContent: false,
|
||||
position: (pos, params, el, elRect, size) => {
|
||||
const positions = { top: (this.template === 'advanced') ? 0 : -30 };
|
||||
positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 60;
|
||||
positions[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 100;
|
||||
return positions;
|
||||
},
|
||||
extraCssText: `width: ${(this.template === 'advanced') ? '275px' : '200px'};
|
||||
@@ -189,10 +194,19 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
border: none;
|
||||
box-shadow: none;`,
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
type: 'cross',
|
||||
label: {
|
||||
formatter: (params: any) => {
|
||||
if (params.axisDimension === 'y') {
|
||||
return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true)
|
||||
} else {
|
||||
return formatterXAxis(this.locale, this.windowPreference, params.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
|
||||
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
|
||||
const { totalValue, totalValueArray } = this.getTotalValues(params);
|
||||
const itemFormatted = [];
|
||||
let totalParcial = 0;
|
||||
@@ -374,21 +388,26 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||
|
||||
orderLevels() {
|
||||
this.feeLevelsOrdered = [];
|
||||
let maxIndex = Math.min(feeLevels.length, this.feeLimitIndex);
|
||||
for (let i = 0; i < maxIndex; i++) {
|
||||
const maxIndex = Math.min(feeLevels.length, this.maxFeeIndex);
|
||||
for (let i = 0; i < feeLevels.length; i++) {
|
||||
if (feeLevels[i] === this.limitFilterFee) {
|
||||
this.feeLimitIndex = i;
|
||||
}
|
||||
if (feeLevels[i] <= feeLevels[this.maxFeeIndex]) {
|
||||
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
||||
if (i === maxIndex - 1) {
|
||||
if (i === maxIndex || feeLevels[i] == null) {
|
||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)}+`);
|
||||
} else {
|
||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
||||
}
|
||||
} else {
|
||||
if (i === maxIndex - 1) {
|
||||
if (i === maxIndex || feeLevels[i] == null) {
|
||||
this.feeLevelsOrdered.push(`${feeLevels[i]}+`);
|
||||
} else {
|
||||
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export class SearchFormComponent implements OnInit {
|
||||
const matchesBlockHeight = this.regexBlockheight.test(searchText);
|
||||
const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText);
|
||||
const matchesBlockHash = this.regexBlockhash.test(searchText);
|
||||
const matchesAddress = this.regexAddress.test(searchText);
|
||||
const matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
|
||||
|
||||
if (matchesAddress && this.network === 'bisq') {
|
||||
searchText = 'B' + searchText;
|
||||
@@ -198,7 +198,7 @@ export class SearchFormComponent implements OnInit {
|
||||
const searchText = result || this.searchForm.value.searchText.trim();
|
||||
if (searchText) {
|
||||
this.isSearching = true;
|
||||
if (this.regexAddress.test(searchText)) {
|
||||
if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) {
|
||||
this.navigate('/address/', searchText);
|
||||
} else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) {
|
||||
this.navigate('/block/', searchText);
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
|
||||
<ul>
|
||||
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
|
||||
<ng-template [ngIf]="feeData.fee <= 400">
|
||||
<ng-template [ngIf]="feeData.fee <= (feeLevels[maxFeeIndex])">
|
||||
<li (click)="filterFeeIndex = feeData.fee"
|
||||
[class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
|
||||
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
|
||||
@@ -84,7 +84,8 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="incoming-transactions-graph">
|
||||
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [height]="500" [left]="65" [right]="10"
|
||||
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'"
|
||||
[limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="10"
|
||||
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,9 @@ export class StatisticsComponent implements OnInit {
|
||||
spinnerLoading = false;
|
||||
feeLevels = feeLevels;
|
||||
chartColors = chartColors;
|
||||
filterSize = 100000;
|
||||
filterFeeIndex = 1;
|
||||
maxFeeIndex: number;
|
||||
dropDownOpen = false;
|
||||
|
||||
mempoolStats: OptimizedMempoolStats[] = [];
|
||||
@@ -134,6 +136,16 @@ export class StatisticsComponent implements OnInit {
|
||||
mempoolStats.reverse();
|
||||
const labels = mempoolStats.map(stats => stats.added);
|
||||
|
||||
let maxTier = 0;
|
||||
for (let index = 37; index > -1; index--) {
|
||||
mempoolStats.forEach((stats) => {
|
||||
if (stats.vsizes[index] >= this.filterSize) {
|
||||
maxTier = Math.max(maxTier, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.maxFeeIndex = maxTier;
|
||||
|
||||
this.capExtremeVbytesValues();
|
||||
|
||||
this.mempoolTransactionsWeightPerSecondData = {
|
||||
@@ -152,27 +164,42 @@ export class StatisticsComponent implements OnInit {
|
||||
}
|
||||
|
||||
setFeeLevelDropdownData() {
|
||||
let _feeLevels = feeLevels
|
||||
let _feeLevels = feeLevels;
|
||||
let _chartColors = chartColors;
|
||||
if (!this.inverted) {
|
||||
_feeLevels = [...feeLevels].reverse();
|
||||
_chartColors = [...chartColors].reverse();
|
||||
}
|
||||
_feeLevels.forEach((fee, i) => {
|
||||
let range;
|
||||
const nextIndex = this.inverted ? i + 1 : i - 1;
|
||||
if (this.stateService.isLiquid()) {
|
||||
if (_feeLevels[nextIndex] == null) {
|
||||
range = `${(_feeLevels[i] / 10).toFixed(1)}+`;
|
||||
} else {
|
||||
range = `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[nextIndex] / 10).toFixed(1)}`;
|
||||
}
|
||||
} else {
|
||||
if (_feeLevels[nextIndex] == null) {
|
||||
range = `${_feeLevels[i]}+`;
|
||||
} else {
|
||||
range = `${_feeLevels[i]} - ${_feeLevels[nextIndex]}`;
|
||||
}
|
||||
}
|
||||
if (this.inverted) {
|
||||
this.feeLevelDropdownData.push({
|
||||
fee: fee,
|
||||
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i + 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i + 1]}`,
|
||||
range,
|
||||
color: _chartColors[i],
|
||||
});
|
||||
} else {
|
||||
this.feeLevelDropdownData.push({
|
||||
fee: fee,
|
||||
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i - 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i - 1]}`,
|
||||
range,
|
||||
color: _chartColors[i - 1],
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,15 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
interval: number;
|
||||
text: string;
|
||||
units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||
precisionThresholds = {
|
||||
year: 100,
|
||||
month: 18,
|
||||
week: 12,
|
||||
day: 31,
|
||||
hour: 48,
|
||||
minute: 90,
|
||||
second: 90
|
||||
};
|
||||
intervals = {};
|
||||
|
||||
@Input() time: number;
|
||||
@@ -85,8 +94,12 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
let counter: number;
|
||||
for (const [index, unit] of this.units.entries()) {
|
||||
const precisionUnit = this.units[Math.min(this.units.length - 1), index + this.precision];
|
||||
let precisionUnit = this.units[Math.min(this.units.length - 1, index + this.precision)];
|
||||
counter = Math.floor(seconds / this.intervals[unit]);
|
||||
const precisionCounter = Math.floor(seconds / this.intervals[precisionUnit]);
|
||||
if (precisionCounter > this.precisionThresholds[precisionUnit]) {
|
||||
precisionUnit = unit;
|
||||
}
|
||||
if (counter > 0) {
|
||||
let rounded = Math.round(seconds / this.intervals[precisionUnit]);
|
||||
if (this.fractionDigits) {
|
||||
|
||||
@@ -114,7 +114,12 @@
|
||||
<ng-container *ngFor="let witness of vin.witness; index as i">
|
||||
<input type="checkbox" [id]="'tx' + vindex + 'witness' + i" style="display: none;">
|
||||
<p class="witness-item" [class.accordioned]="witness.length > 1000">
|
||||
{{ witness }}
|
||||
<ng-template [ngIf]="witness" [ngIfElse]="emptyWitnessItem">
|
||||
{{ witness }}
|
||||
</ng-template>
|
||||
<ng-template #emptyWitnessItem>
|
||||
<empty>
|
||||
</ng-template>
|
||||
</p>
|
||||
<div class="witness-toggle" *ngIf="witness.length > 1000">
|
||||
<span class="ellipsis">...</span>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<div class="mempool-graph">
|
||||
<app-mempool-graph
|
||||
[template]="'widget'"
|
||||
[filterSize]="1000000"
|
||||
[data]="mempoolStats.value?.mempool"
|
||||
[windowPreferenceOverride]="'2h'"
|
||||
></app-mempool-graph>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { DashboardComponent } from '../dashboard/dashboard.component';
|
||||
import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component';
|
||||
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
|
||||
import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component';
|
||||
import { BlockPredictionGraphComponent } from '../components/block-prediction-graph/block-prediction-graph.component';
|
||||
import { BlockHealthGraphComponent } from '../components/block-health-graph/block-health-graph.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
@@ -46,7 +46,7 @@ import { CommonModule } from '@angular/common';
|
||||
LbtcPegsGraphComponent,
|
||||
HashrateChartComponent,
|
||||
HashrateChartPoolsComponent,
|
||||
BlockPredictionGraphComponent,
|
||||
BlockHealthGraphComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { BlockPredictionGraphComponent } from '../components/block-prediction-graph/block-prediction-graph.component';
|
||||
import { BlockHealthGraphComponent } from '../components/block-health-graph/block-health-graph.component';
|
||||
import { BlockFeeRatesGraphComponent } from '../components/block-fee-rates-graph/block-fee-rates-graph.component';
|
||||
import { BlockFeesGraphComponent } from '../components/block-fees-graph/block-fees-graph.component';
|
||||
import { BlockRewardsGraphComponent } from '../components/block-rewards-graph/block-rewards-graph.component';
|
||||
@@ -143,9 +143,9 @@ const routes: Routes = [
|
||||
redirectTo: 'mempool',
|
||||
},
|
||||
{
|
||||
path: 'mining/block-prediction',
|
||||
path: 'mining/block-health',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: BlockPredictionGraphComponent,
|
||||
component: BlockHealthGraphComponent,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -155,7 +155,7 @@ export interface TransactionStripped {
|
||||
fee: number;
|
||||
vsize: number;
|
||||
value: number;
|
||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
}
|
||||
|
||||
interface RbfTransaction extends TransactionStripped {
|
||||
|
||||
@@ -76,7 +76,7 @@ export interface TransactionStripped {
|
||||
vsize: number;
|
||||
value: number;
|
||||
rate?: number; // effective fee rate
|
||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||
context?: 'projected' | 'actual';
|
||||
}
|
||||
|
||||
|
||||
@@ -253,20 +253,4 @@ export class NodeFeeChartComponent implements OnInit {
|
||||
isMobile() {
|
||||
return (window.innerWidth <= 767.98);
|
||||
}
|
||||
|
||||
onSaveChart() {
|
||||
// @ts-ignore
|
||||
const prevBottom = this.chartOptions.grid.bottom;
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
}), `node-fee-chart.svg`);
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = prevBottom;
|
||||
this.chartOptions.backgroundColor = 'none';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,21 +252,4 @@ export class NodeStatisticsChartComponent implements OnInit {
|
||||
isMobile() {
|
||||
return (window.innerWidth <= 767.98);
|
||||
}
|
||||
|
||||
onSaveChart() {
|
||||
// @ts-ignore
|
||||
const prevBottom = this.chartOptions.grid.bottom;
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 40;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
}), `block-sizes-weights-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = prevBottom;
|
||||
this.chartOptions.backgroundColor = 'none';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
excludeComponents: ['dataZoom'],
|
||||
}), `block-sizes-weights-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
||||
}), `lightning-nodes-per-network-${Math.round(now.getTime() / 1000)}.svg`);
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = prevBottom;
|
||||
this.chartOptions.backgroundColor = 'none';
|
||||
|
||||
@@ -238,7 +238,7 @@ export class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
getHistoricalBlockPrediction$(interval: string | undefined) : Observable<any> {
|
||||
getHistoricalBlocksHealth$(interval: string | undefined) : Observable<any> {
|
||||
return this.httpClient.get<any[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/predictions` +
|
||||
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||
|
||||
@@ -1,22 +1,85 @@
|
||||
<footer *ngIf="networkPaths$ | async as networkPaths">
|
||||
<div class="pref-selectors">
|
||||
<div class="selector">
|
||||
<app-language-selector></app-language-selector>
|
||||
<footer>
|
||||
<div class="container-fluid">
|
||||
<div class="row main">
|
||||
<div class="offset-lg-1 col-lg-4 col align-self-center branding">
|
||||
<h5><ng-container i18n="about.about-the-project">The Mempool Open Source Project</ng-container><ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ™</ng-template></h5>
|
||||
<p><ng-container i18n="@@7deec1c1520f06170e1f8e8ddfbe4532312f638f">Explore the full Bitcoin ecosystem</ng-container><ng-template [ngIf]="locale.substr(0, 2) === 'en'"> ™</ng-template></p>
|
||||
<div class="selector">
|
||||
<app-language-selector></app-language-selector>
|
||||
</div>
|
||||
<div class="selector">
|
||||
<app-fiat-selector></app-fiat-selector>
|
||||
</div>
|
||||
<ng-template #temporaryHidden>
|
||||
<a *ngIf="officialMempoolSpace" class="cta btn btn-purple sponsor" [routerLink]="['/signup' | relativeUrl]">Support the Project</a>
|
||||
<p *ngIf="officialMempoolSpace && env.BASE_MODULE === 'mempool'" class="cta-secondary"><a [routerLink]="['/signin' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Sign In</a></p>
|
||||
</ng-template>
|
||||
<p class="cta-secondary"><a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a></p>
|
||||
<p *ngIf="officialMempoolSpace && env.LIGHTNING" class="cta-secondary"><a [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]" i18n="footer.connect-to-our-nodes">Connect to our Nodes</a></p>
|
||||
<p><a [routerLink]="['/about' | relativeUrl]">About The Mempool Open Source Project™</a></p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-10 offset-md-1 links outer">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<p class="category">Explore</p>
|
||||
<p><a [routerLink]="['/mining' | relativeUrl]">Mining Dashboard</a></p>
|
||||
<p><a [routerLink]="['/lightning' | relativeUrl]">Lightning Dashboard</a></p>
|
||||
<p><a [routerLink]="['/blocks' | relativeUrl]">Recent Blocks</a></p>
|
||||
<p><a [routerLink]="['/docs/api' | relativeUrl]">API Documentation</a></p>
|
||||
</div>
|
||||
<div class="col-lg-6 links">
|
||||
<p class="category">Learn</p>
|
||||
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool">What is a mempool?</a></p>
|
||||
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-block-explorer">What is a block explorer?</a></p>
|
||||
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool-explorer">What is a mempool explorer?</a></p>
|
||||
<p><a [routerLink]="['/docs/faq']" fragment="why-is-transaction-stuck-in-mempool">Why isn't my transaction confirming?</a></p>
|
||||
<p><a [routerLink]="['/docs/faq' | relativeUrl]">More FAQs ›</a></p>
|
||||
</div>
|
||||
<!--<div class="col-lg-4 links">
|
||||
<p class="category">Connect</p>
|
||||
<p><a href="https://github.com/mempool" target="_blank">GitHub</a></p>
|
||||
<p><a href="https://twitter.com/mempool" target="_blank">Twitter</a></p>
|
||||
<p><a href="nostr:npub18d4r6wanxkyrdfjdrjqzj2ukua5cas669ew2g5w7lf4a8te7awzqey6lt3" target="_blank">Nostr</a></p>
|
||||
<p><a href="https://youtube.com/@mempool" target="_blank">YouTube</a></p>
|
||||
<p><a href="https://bitcointv.com/c/mempool/videos" target="_blank">BitcoinTV</a></p>
|
||||
<p><a href="https://mempool.chat" target="_blank">Matrix</a></p>
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 links">
|
||||
<p class="category">More Networks</p>
|
||||
<p *ngIf="currentNetwork !== '' && currentNetwork !== 'mainnet'"><a [href]="networkLink('mainnet')">Mainnet Explorer</a></p>
|
||||
<p *ngIf="currentNetwork !== 'testnet'"><a [href]="networkLink('testnet')">Testnet Explorer</a></p>
|
||||
<p *ngIf="currentNetwork !== 'signet'"><a [href]="networkLink('signet')">Signet Explorer</a></p>
|
||||
<p *ngIf="currentNetwork !== 'liquid' && currentNetwork !== 'liquidtestnet'"><a [href]="networkLink('liquid')">Liquid Explorer</a></p>
|
||||
<p *ngIf="currentNetwork !== 'bisq'"><a [href]="networkLink('bisq')">Bisq Explorer</a></p>
|
||||
</div>
|
||||
<div class="col-lg-6 links">
|
||||
<p class="category">Legal</p>
|
||||
<p><a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a></p>
|
||||
<p><a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a></p>
|
||||
<p><a [routerLink]="['/trademark-policy']">Trademark Policy</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="selector">
|
||||
<app-fiat-selector></app-fiat-selector>
|
||||
<div class="row social-links">
|
||||
<div class="col-sm-12">
|
||||
<a href="https://github.com/mempool" target="_blank"><svg fill="#fff" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a>
|
||||
<a href="https://twitter.com/mempool" target="_blank"><svg fill="#fff" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg></a>
|
||||
<a href="nostr:npub18d4r6wanxkyrdfjdrjqzj2ukua5cas669ew2g5w7lf4a8te7awzqey6lt3" target="_blank"><svg fill="#fff" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 875 875"><path d="M684.72 485.57c.22 12.59-11.93 51.47-38.67 81.3-26.74 29.83-56.02 20.85-58.42 20.16s-3.09-4.46-7.89-3.77-9.6 6.17-18.86 7.2-17.49 1.71-26.06-1.37c-4.46.69-5.14.71-7.2 2.24s-17.83 10.79-21.6 11.47c0 7.2-1.37 44.57 0 55.89s3.77 25.71 7.54 36 2.74 10.63 7.54 9.94 13.37.34 15.77 4.11c2.4 3.77 1.37 6.51 5.49 8.23s60.69 17.14 99.43 19.2c26.74.69 42.86 2.74 52.12 19.54 1.37 7.89 7.54 13.03 11.31 14.06s8.23 2.06 12 5.83 1.03 8.23 5.49 11.66c4.46 3.43 14.74 8.57 25.37 13.71 10.63 5.14 15.09 13.37 15.77 16.11s1.71 10.97 1.71 10.97-8.91 0-10.97-2.06-2.74-5.83-2.74-5.83-6.17 1.03-7.54 3.43.69 2.74-7.89.69-11.66-3.77-18.17-8.57c-6.51-4.8-16.46-17.14-25.03-16.8 4.11 8.23 5.83 8.23 10.63 10.97s8.23 5.83 8.23 5.83l-7.2 4.46s-4.46 2.06-14.74-.69-11.66-4.46-12.69-10.63 0-9.26-2.74-14.4-4.11-15.77-22.29-21.26c-18.17-5.49-66.52-21.26-100.12-24.69s-22.63-2.74-28.11-1.37-15.77 4.46-26.4-1.37c-10.63-5.83-16.8-13.71-17.49-20.23s-1.71-10.97 0-19.2 3.43-19.89 1.71-26.74-14.06-55.89-19.89-64.12c-13.03 1.03-50.74-.69-50.74-.69s-2.4-.69-17.49 5.83-36.48 13.76-46.77 19.93-14.4 9.7-16.12 13.13c.12 3-1.23 7.72-2.79 9.06s-12.48 2.42-12.48 2.42-5.85 5.86-8.25 9.97c-6.86 9.6-55.2 125.14-66.52 149.83-13.54 32.57-9.77 27.43-37.71 27.43s-8.06.3-8.06.3-12.34 5.88-16.8 5.88-18.86-2.4-26.4 0-16.46 9.26-23.31 10.29-4.95-1.34-8.38-3.74c-4-.21-14.27-.12-14.27-.12s1.74-6.51 7.91-10.88c8.23-5.83 25.37-16.11 34.63-21.26s17.49-7.89 23.31-9.26 18.51-6.17 30.51-9.94 19.54-8.23 29.83-31.54 50.4-111.43 51.43-116.23c.63-2.96 3.73-6.48 4.8-15.09.66-5.35-2.49-13.04 1.71-22.63 10.97-25.03 21.6-20.23 26.4-20.23s17.14.34 26.4-1.37 15.43-2.74 24.69-7.89 11.31-8.91 11.31-8.91l-19.89-3.43s-18.51.69-25.03-4.46-15.43-15.77-15.43-15.77l-7.54-7.2 1.03 8.57s-5.14-8.91-6.51-10.29-8.57-6.51-11.31-11.31-7.54-25.03-7.54-25.03l-6.17 13.03-1.71-18.86-5.14 7.2-2.74-16.11-4.8 8.23-3.43-14.4-5.83 4.46-2.4-10.29-5.83-3.43s-14.06-9.26-16.46-9.6-4.46 3.43-4.46 3.43l1.37 12-12.2-6.27-7-11.9s2.36 4.01-9.62 7.53c-20.55 0-21.89-2.28-24.93-3.94-1.31-6.56-5.57-10.11-5.57-10.11h-20.57l-.34-6.86-7.89 3.09.69-10.29h-14.06l1.03-11.31h-8.91s3.09-9.26 25.71-22.97 25.03-16.46 46.29-17.14c21.26-.69 32.91 2.74 46.29 8.23s38.74 13.71 43.89 17.49c11.31-9.94 28.46-19.89 34.29-19.89 1.03-2.4 6.19-12.33 17.96-17.6 35.31-15.81 108.13-34 131.53-35.54 31.2-2.06 7.89-1.37 39.09 2.06 31.2 3.43 54.17 7.54 69.6 12.69 12.58 4.19 25.03 9.6 34.29 2.06 4.33-1.81 11.81-1.34 17.83-5.14 30.69-25.09 34.72-32.35 43.63-41.95s20.14-24.91 22.54-45.14 4.46-58.29-10.63-88.12-28.8-45.26-34.63-69.26c-5.83-24-8.23-61.03-6.17-73.03 2.06-12 5.14-22.29 6.86-30.51s9.94-14.74 19.89-16.46c9.94-1.71 17.83 1.37 22.29 4.8 4.46 3.43 11.65 6.28 13.37 10.29.34 1.71-1.37 6.51 8.23 8.23 9.6 1.71 16.05 4.16 16.05 4.16s15.64 4.29 3.11 7.73c-12.69 2.06-20.52-.71-24.29 1.69s-7.21 10.08-9.61 11.1-7.2.34-12 4.11-9.6 6.86-12.69 14.4-5.49 15.77-3.43 26.74 8.57 31.54 14.4 43.2c5.83 11.66 20.23 40.8 24.34 47.66s15.77 29.49 16.8 53.83 1.03 44.23 0 54.86-10.84 51.65-35.53 85.94c-8.16 14.14-23.21 31.9-24.67 35.03-1.45 3.13-3.02 4.88-1.61 7.65 4.62 9.05 12.87 22.13 14.71 29.22 2.29 6.64 6.99 16.13 7.22 28.72Z" style="stroke:#000;stroke-miterlimit:10;stroke-width:6px"/></svg></a>
|
||||
<a href="https://youtube.com/@mempool" target="_blank"><svg fill="#fff" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>YouTube</title><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg></a>
|
||||
<a href="https://bitcointv.com/c/mempool/videos" target="_blank"><svg _ngcontent-serverApp-c90="" xmlns="http://www.w3.org/2000/svg" focusable="false" viewBox="0 0 440 440"><path _ngcontent-serverApp-c90="" d="M225.57,2.08l-.69-.45a4.22,4.22,0,0,0-5.72,1.23L182.33,46.09a4,4,0,0,0,.88,5.81l9.38,6.38L173.48,97.49a4.22,4.22,0,0,0,2.45,4.19s3.55.7,4.53-1l41.92-40.56a3.62,3.62,0,0,0-1.51-5.1l-10.55-6.12L227.44,6.79A4.26,4.26,0,0,0,225.57,2.08Z" fill="#fff"></path><path _ngcontent-serverApp-c90="" d="M118.52,401.83c-62.51,0-113.37-51-113.37-113.67V214.68C5.15,152,56,101,118.52,101H342.08a24.82,24.82,0,0,1,24.76,24.83V377a24.81,24.81,0,0,1-24.76,24.82Z"></path><path _ngcontent-serverApp-c90="" d="M342.08,105.18a20.65,20.65,0,0,1,20.61,20.66V377a20.66,20.66,0,0,1-20.61,20.66H118.52C58.3,397.67,9.31,348.55,9.31,288.16V214.68c0-60.38,49-109.5,109.21-109.5H342.08m0-8.34H118.52C53.62,96.84,1,149.6,1,214.68v73.48C1,353.24,53.62,406,118.52,406H342.08A29,29,0,0,0,371,377V125.84a29,29,0,0,0-28.92-29Z" fill="#fff"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M344.69,346.23A25.84,25.84,0,1,0,335,369.87l-10.22-10.2a11.69,11.69,0,1,1,4.77-5.12l10.31,10.28A25.84,25.84,0,0,0,344.69,346.23Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M315.82,257.61a25.67,25.67,0,0,0-12.53,5.22L315,274.49a9.58,9.58,0,0,1,2.11-.73A9.72,9.72,0,1,1,309.4,283a9.4,9.4,0,0,1,.75-3.41L298.4,267.84a25.77,25.77,0,1,0,17.42-10.23Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M313,214a7.76,7.76,0,1,1,1.41,10.91,7.62,7.62,0,0,1-2.19-2.69l-18.67-.14a25.94,25.94,0,1,0,.05-7l18.64.14A7.4,7.4,0,0,1,313,214Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M341.2,144.08h-6.32c-1.67,0-3.61,1.87-3.61,4.29s1.94,4.29,3.61,4.29h6.32c1.67,0,3.61-1.87,3.61-4.29S342.87,144.08,341.2,144.08Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M301.75,144.08h-6.44c-1.67,0-3.61,1.87-3.61,4.29s1.94,4.29,3.61,4.29h6.44c1.67,0,3.61-1.87,3.61-4.29S303.42,144.08,301.75,144.08Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M321.77,144.08h-7c-1.67,0-3.62,1.87-3.62,4.29s1.95,4.29,3.62,4.29h7c1.67,0,3.62-1.87,3.62-4.29S323.44,144.08,321.77,144.08Z"></path><ellipse _ngcontent-serverApp-c90="" fill="#fff" cx="295.97" cy="127.61" rx="4.27" ry="4.29"></ellipse><path _ngcontent-serverApp-c90="" fill="#fff" d="M340.54,131.9a4.29,4.29,0,1,0-4.27-4.29A4.28,4.28,0,0,0,340.54,131.9Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M318.26,131.9a4.29,4.29,0,1,0-4.27-4.29A4.29,4.29,0,0,0,318.26,131.9Z"></path><ellipse _ngcontent-serverApp-c90="" fill="#fff" cx="295.97" cy="169.13" rx="4.27" ry="4.29"></ellipse><path _ngcontent-serverApp-c90="" fill="#fff" d="M340.54,164.84a4.3,4.3,0,1,0,4.27,4.29A4.29,4.29,0,0,0,340.54,164.84Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M318.26,164.84a4.3,4.3,0,1,0,4.28,4.29A4.29,4.29,0,0,0,318.26,164.84Z"></path><path _ngcontent-serverApp-c90="" d="M108.62,256.87c8.36-1,7.68-7.76,3.14-17-3.64-7.4-9.74-16.39-15.75-25.36-14.23-21.23-27.69-42.23-5.35-41.07,19.55,1,42.9,18.63,68.22,36.74,31.1,22.24,65.16,45.21,98.81,39.11a151.19,151.19,0,0,1,20-2.37V221a92,92,0,0,0-91.91-92.16H124.33A92,92,0,0,0,32.42,221v17.59c17.71,3.81,31,9.94,43.8,14.15C86.6,256.16,96.69,258.31,108.62,256.87Z"></path><path _ngcontent-serverApp-c90="" d="M273.37,310.79c-35-15.26-76.67-32.1-104-23.59-3.15,1-5,2.3-6,3.85-3.35,5.31,4.67,13.57,14.89,22.17,7.17,6,15.36,12.21,21.44,17.64,11.47,10.26,15.35,17.84-9.89,16.62-29.75-1.44-49.18-13.75-71.18-24l-.29-.14a165.84,165.84,0,0,0-22.93-8.91c-15.74-4.67-34.22-6.79-58.51-3.28A91.93,91.93,0,0,0,124.33,375h61.45A92,92,0,0,0,273.37,310.79Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M257.69,249.31C224,255.41,190,232.44,158.88,210.2c-25.32-18.11-48.67-35.72-68.22-36.74C68.32,172.3,81.78,193.3,96,214.53c6,9,12.11,18,15.75,25.36,4.54,9.22,5.22,16-3.14,17-11.93,1.44-22-.71-32.4-4.13-12.8-4.21-26.09-10.34-43.8-14.15v44.26c0,1.26.14,2.48.19,3.72a91.8,91.8,0,0,0,2.9,19.62c.43,1.67.84,3.34,1.37,5,24.29-3.51,42.77-1.39,58.51,3.28a165.84,165.84,0,0,1,22.93,8.91c.39-.12.76-.26,1.14-.39l-.85.53c22,10.25,41.43,22.56,71.18,24,25.24,1.22,21.36-6.36,9.89-16.62-6.08-5.43-14.27-11.61-21.44-17.64-10.22-8.6-18.24-16.86-14.89-22.17,1-1.55,2.87-2.87,6-3.85,27.33-8.51,69,8.33,104,23.59.32-1,.56-2.05.84-3.07a92.33,92.33,0,0,0,3.48-24.87V246.94A151.19,151.19,0,0,0,257.69,249.31Z"></path><path _ngcontent-serverApp-c90="" fill="#fff" d="M192,137a78,78,0,0,1,77.78,78v73.91a78,78,0,0,1-77.78,78H118.51a78,78,0,0,1-77.78-78V215a78,78,0,0,1,77.78-78H192m0-8.33H118.51A86.21,86.21,0,0,0,32.42,215v73.91a86.21,86.21,0,0,0,86.09,86.33H192a86.21,86.21,0,0,0,86.09-86.33V215A86.21,86.21,0,0,0,192,128.64Z"></path></svg></a>
|
||||
<a href="https://mempool.chat" target="_blank"><svg fill="#fff" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Matrix</title><path d="M.632.55v22.9H2.28V24H0V0h2.28v.55zm7.043 7.26v1.157h.033c.309-.443.683-.784 1.117-1.024.433-.245.936-.365 1.5-.365.54 0 1.033.107 1.481.314.448.208.785.582 1.02 1.108.254-.374.6-.706 1.034-.992.434-.287.95-.43 1.546-.43.453 0 .872.056 1.26.167.388.11.716.286.993.53.276.245.489.559.646.951.152.392.23.863.23 1.417v5.728h-2.349V11.52c0-.286-.01-.559-.032-.812a1.755 1.755 0 0 0-.18-.66 1.106 1.106 0 0 0-.438-.448c-.194-.11-.457-.166-.785-.166-.332 0-.6.064-.803.189a1.38 1.38 0 0 0-.48.499 1.946 1.946 0 0 0-.231.696 5.56 5.56 0 0 0-.06.785v4.768h-2.35v-4.8c0-.254-.004-.503-.018-.752a2.074 2.074 0 0 0-.143-.688 1.052 1.052 0 0 0-.415-.503c-.194-.125-.476-.19-.854-.19-.111 0-.259.024-.439.074-.18.051-.36.143-.53.282-.171.138-.319.337-.439.595-.12.259-.18.6-.18 1.02v4.966H5.46V7.81zm15.693 15.64V.55H21.72V0H24v24h-2.28v-.55z"/></svg></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row version">
|
||||
<div class="col-sm-12">
|
||||
<p *ngIf="officialMempoolSpace">{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [<a target="_blank" href="https://github.com/mempool/mempool/commit/{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}">{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}</a>]</p>
|
||||
|
||||
<div class="terms-of-service">
|
||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||
|
|
||||
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
|
||||
|
|
||||
<a *ngIf="officialMempoolSpace && networkPaths['mainnet'] === '/lightning' else broadcastTransaction" [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]">Connect to our nodes</a>
|
||||
<ng-template #broadcastTransaction>
|
||||
<a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a>
|
||||
</ng-template>
|
||||
<p *ngIf="!officialMempoolSpace">v{{ packetJsonVersion }} [<a target="_blank" href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</footer>
|
||||
</footer>
|
||||
|
||||
@@ -1,22 +1,106 @@
|
||||
footer {
|
||||
background-color: #1d1f31;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
footer .row.main {
|
||||
padding: 40px 0;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
footer .row.main .branding {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terms-of-service {
|
||||
footer .row.main .branding .btn {
|
||||
display: inline-block;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
footer .row.main .branding button.account {
|
||||
background-color: #2d3348;
|
||||
}
|
||||
|
||||
footer .row.main .branding .cta {
|
||||
margin: 20px auto 25px auto;
|
||||
}
|
||||
|
||||
footer .row.main .branding .cta-secondary {
|
||||
|
||||
}
|
||||
|
||||
footer .row.main .links.outer {
|
||||
padding-left: 24px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
footer .row.main .links > div:first-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
footer .row.main .links .category {
|
||||
color: #4a68b9;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
footer .row.main .links .category:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.pref-selectors {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
.selector {
|
||||
margin-left: .5em;
|
||||
margin-bottom: .5em;
|
||||
&:first {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
footer .selector {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
footer .row.social-links {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
footer .row.social-links a {
|
||||
margin: 0 14px;
|
||||
}
|
||||
|
||||
footer .row.social-links svg {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
footer .row.version {
|
||||
padding: 20px !important;
|
||||
background-color: #11131f;
|
||||
}
|
||||
|
||||
footer .row.version p {
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
footer .row.version p a {
|
||||
color: #09a3ba;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
|
||||
footer .row.main .links.outer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer .row.main .links.outer > .row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
footer .row.main .links.outer > .row > div:first-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { NavigationService } from '../../../services/navigation.service';
|
||||
import { ChangeDetectionStrategy, Component, OnInit, Inject, LOCALE_ID } from '@angular/core';
|
||||
import { Observable, merge, of, Subject } from 'rxjs';
|
||||
import { tap, takeUntil } from 'rxjs/operators';
|
||||
import { Env, StateService } from '../../../services/state.service';
|
||||
import { IBackendInfo } from '../../../interfaces/websocket.interface';
|
||||
import { LanguageService } from '../../../services/language.service';
|
||||
import { NavigationService } from '../../../services/navigation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-global-footer',
|
||||
@@ -10,19 +13,57 @@ import { Env, StateService } from '../../../services/state.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class GlobalFooterComponent implements OnInit {
|
||||
private destroy$: Subject<any> = new Subject<any>();
|
||||
env: Env;
|
||||
networkPaths: { [network: string]: string };
|
||||
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||
networkPaths$: Observable<Record<string, string>>;
|
||||
backendInfo$: Observable<IBackendInfo>;
|
||||
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
|
||||
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
|
||||
urlLanguage: string;
|
||||
network$: Observable<string>;
|
||||
networkPaths: { [network: string]: string };
|
||||
currentNetwork = '';
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private languageService: LanguageService,
|
||||
private navigationService: NavigationService,
|
||||
@Inject(LOCALE_ID) public locale: string,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.env = this.stateService.env;
|
||||
this.networkPaths$ = this.navigationService.subnetPaths;
|
||||
this.backendInfo$ = this.stateService.backendInfo$;
|
||||
this.urlLanguage = this.languageService.getLanguageForUrl();
|
||||
this.navigationService.subnetPaths.subscribe((paths) => {
|
||||
this.networkPaths = paths;
|
||||
});
|
||||
this.network$ = merge(of(''), this.stateService.networkChanged$).pipe(
|
||||
tap((network: string) => {
|
||||
return network;
|
||||
})
|
||||
);
|
||||
this.network$.pipe(takeUntil(this.destroy$)).subscribe((network) => {
|
||||
this.currentNetwork = network;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(true);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
networkLink(network) {
|
||||
const thisNetwork = network || 'mainnet';
|
||||
if( network === '' || network === 'mainnet' || network === 'testnet' || network === 'signet' ) {
|
||||
return (this.env.BASE_MODULE === 'mempool' ? '' : this.env.MEMPOOL_WEBSITE_URL + this.urlLanguage) + this.networkPaths[thisNetwork] || '/';
|
||||
}
|
||||
if( network === 'liquid' || network === 'liquidtestnet' ) {
|
||||
return (this.env.BASE_MODULE === 'liquid' ? '' : this.env.LIQUID_WEBSITE_URL + this.urlLanguage) + this.networkPaths[thisNetwork] || '/';
|
||||
}
|
||||
if( network === 'bisq' ) {
|
||||
return (this.env.BASE_MODULE === 'bisq' ? '' : this.env.BISQ_WEBSITE_URL + this.urlLanguage) + this.networkPaths[thisNetwork] || '/';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
@media (max-width: 767.98px) {
|
||||
min-height: 100vh;
|
||||
@@ -1143,7 +1147,7 @@ th {
|
||||
display: none;
|
||||
}
|
||||
|
||||
app-master-page, app-liquid-master-page {
|
||||
app-master-page, app-liquid-master-page, app-bisq-master-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
@@ -1155,4 +1159,4 @@ app-master-page, app-liquid-master-page {
|
||||
|
||||
app-global-footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1312,8 +1312,13 @@ case $OS in
|
||||
osSudo "${ROOT_USER}" pw usermod ${MEMPOOL_USER} -G "${CLN_GROUP}"
|
||||
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
||||
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
||||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning" "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
||||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/bitcoin"
|
||||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/signet"
|
||||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/testnet"
|
||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning"
|
||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/bitcoin"
|
||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/signet"
|
||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/testnet"
|
||||
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
||||
echo "[*] Creating symlink to .bitcoin folder"
|
||||
osSudo "${CLN_USER}" ln -s "${BITCOIN_HOME}/.bitcoin" "${CLN_HOME}/.bitcoin"
|
||||
@@ -1321,16 +1326,18 @@ case $OS in
|
||||
echo "[*] Installing Core Lightning package"
|
||||
osPackageInstall ${CLN_PKG}
|
||||
|
||||
echo "[*] Installing Core Lightning mainnet Cronjob"
|
||||
public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
|
||||
public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
|
||||
|
||||
crontab_cln+="@reboot sleep 10 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||
crontab_cln+="@reboot sleep 10 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||
crontab_cln+="@reboot sleep 10 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n"
|
||||
crontab_cln+="@reboot sleep 20 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||
crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||
echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
||||
######## FIXME: this code doesn't work properly, needs fixing
|
||||
#
|
||||
# echo "[*] Installing Core Lightning mainnet Cronjob"
|
||||
# public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
|
||||
# public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
|
||||
#
|
||||
# crontab_cln+="@reboot sleep 10 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||
# crontab_cln+="@reboot sleep 10 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||
# crontab_cln+="@reboot sleep 10 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n"
|
||||
# crontab_cln+="@reboot sleep 20 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||
# crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||
# echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
||||
;;
|
||||
Debian)
|
||||
;;
|
||||
@@ -1813,7 +1820,7 @@ echo "[*] Adding MySQL configuration"
|
||||
case $OS in
|
||||
|
||||
FreeBSD)
|
||||
osSudo "${ROOT_USER}" service mysql-server start
|
||||
osSudo "${ROOT_USER}" service mysql-server onestart
|
||||
;;
|
||||
Debian)
|
||||
osSudo "${ROOT_USER}" service mysql start
|
||||
|
||||
Reference in New Issue
Block a user