Merge branch 'master' into natsoni/fix-unnecessary-load-more
This commit is contained in:
commit
d02625eb0d
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
||||||
# Latest version available on this commit is 1.71.1
|
# Latest version available on this commit is 1.71.1
|
||||||
# Commit date is Aug 3, 2023
|
# Commit date is Aug 3, 2023
|
||||||
uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07
|
uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
|
|
||||||
export interface AbstractBitcoinApi {
|
export interface AbstractBitcoinApi {
|
||||||
@ -22,6 +22,7 @@ export interface AbstractBitcoinApi {
|
|||||||
$getScriptHash(scripthash: string): Promise<IEsploraApi.ScriptHash>;
|
$getScriptHash(scripthash: string): Promise<IEsploraApi.ScriptHash>;
|
||||||
$getScriptHashTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
|
$getScriptHashTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
|
||||||
$sendRawTransaction(rawTransaction: string): Promise<string>;
|
$sendRawTransaction(rawTransaction: string): Promise<string>;
|
||||||
|
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]>;
|
||||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
|
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend>;
|
||||||
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
|
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]>;
|
||||||
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||||
|
@ -205,3 +205,16 @@ export namespace IBitcoinApi {
|
|||||||
"utxo_size_inc": number;
|
"utxo_size_inc": number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TestMempoolAcceptResult {
|
||||||
|
txid: string,
|
||||||
|
wtxid: string,
|
||||||
|
allowed?: boolean,
|
||||||
|
vsize?: number,
|
||||||
|
fees?: {
|
||||||
|
base: number,
|
||||||
|
"effective-feerate": number,
|
||||||
|
"effective-includes": string[],
|
||||||
|
},
|
||||||
|
['reject-reason']?: string,
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
import { IBitcoinApi, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import blocks from '../blocks';
|
import blocks from '../blocks';
|
||||||
import mempool from '../mempool';
|
import mempool from '../mempool';
|
||||||
@ -174,6 +174,14 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
return this.bitcoindClient.sendRawTransaction(rawTransaction);
|
return this.bitcoindClient.sendRawTransaction(rawTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async $testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]> {
|
||||||
|
if (rawTransactions.length) {
|
||||||
|
return this.bitcoindClient.testMempoolAccept(rawTransactions, maxfeerate ?? undefined);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
async $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||||
const txOut = await this.bitcoindClient.getTxOut(txId, vout, false);
|
const txOut = await this.bitcoindClient.getTxOut(txId, vout, false);
|
||||||
return {
|
return {
|
||||||
|
@ -55,6 +55,7 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', this.getRecentMempoolTransactions)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', this.getRecentMempoolTransactions)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', this.getTransaction)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', this.getTransaction)
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx', this.$postTransaction)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'tx', this.$postTransaction)
|
||||||
|
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/test', this.$testTransactions)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends)
|
||||||
@ -749,6 +750,19 @@ class BitcoinRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $testTransactions(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const rawTxs = Common.getTransactionsFromRequest(req);
|
||||||
|
const maxfeerate = parseFloat(req.query.maxfeerate as string);
|
||||||
|
const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate);
|
||||||
|
res.send(result);
|
||||||
|
} catch (e: any) {
|
||||||
|
res.setHeader('content-type', 'text/plain');
|
||||||
|
res.status(400).send(e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||||
|
: (e.message || 'Error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new BitcoinRoutes();
|
export default new BitcoinRoutes();
|
||||||
|
@ -5,6 +5,7 @@ import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-fact
|
|||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import { Common } from '../common';
|
import { Common } from '../common';
|
||||||
|
import { TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||||
|
|
||||||
interface FailoverHost {
|
interface FailoverHost {
|
||||||
host: string,
|
host: string,
|
||||||
@ -327,6 +328,10 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$testMempoolAccept(rawTransactions: string[], maxfeerate?: number): Promise<TestMempoolAcceptResult[]> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||||
return this.failoverRouter.$get<IEsploraApi.Outspend>('/tx/' + txId + '/outspend/' + vout);
|
return this.failoverRouter.$get<IEsploraApi.Outspend>('/tx/' + txId + '/outspend/' + vout);
|
||||||
}
|
}
|
||||||
|
@ -839,8 +839,11 @@ class Blocks {
|
|||||||
} else {
|
} else {
|
||||||
this.currentBlockHeight++;
|
this.currentBlockHeight++;
|
||||||
logger.debug(`New block found (#${this.currentBlockHeight})!`);
|
logger.debug(`New block found (#${this.currentBlockHeight})!`);
|
||||||
this.updateTimerProgress(timer, `getting orphaned blocks for ${this.currentBlockHeight}`);
|
// skip updating the orphan block cache if we've fallen behind the chain tip
|
||||||
await chainTips.updateOrphanedBlocks();
|
if (this.currentBlockHeight >= blockHeightTip - 2) {
|
||||||
|
this.updateTimerProgress(timer, `getting orphaned blocks for ${this.currentBlockHeight}`);
|
||||||
|
await chainTips.updateOrphanedBlocks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTimerProgress(timer, `getting block data for ${this.currentBlockHeight}`);
|
this.updateTimerProgress(timer, `getting block data for ${this.currentBlockHeight}`);
|
||||||
|
@ -12,32 +12,68 @@ export interface OrphanedBlock {
|
|||||||
height: number;
|
height: number;
|
||||||
hash: string;
|
hash: string;
|
||||||
status: 'valid-fork' | 'valid-headers' | 'headers-only';
|
status: 'valid-fork' | 'valid-headers' | 'headers-only';
|
||||||
|
prevhash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChainTips {
|
class ChainTips {
|
||||||
private chainTips: ChainTip[] = [];
|
private chainTips: ChainTip[] = [];
|
||||||
private orphanedBlocks: OrphanedBlock[] = [];
|
private orphanedBlocks: { [hash: string]: OrphanedBlock } = {};
|
||||||
|
private blockCache: { [hash: string]: OrphanedBlock } = {};
|
||||||
|
private orphansByHeight: { [height: number]: OrphanedBlock[] } = {};
|
||||||
|
|
||||||
public async updateOrphanedBlocks(): Promise<void> {
|
public async updateOrphanedBlocks(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.chainTips = await bitcoinClient.getChainTips();
|
this.chainTips = await bitcoinClient.getChainTips();
|
||||||
this.orphanedBlocks = [];
|
|
||||||
|
const start = Date.now();
|
||||||
|
const breakAt = start + 10000;
|
||||||
|
let newOrphans = 0;
|
||||||
|
this.orphanedBlocks = {};
|
||||||
|
|
||||||
for (const chain of this.chainTips) {
|
for (const chain of this.chainTips) {
|
||||||
if (chain.status === 'valid-fork' || chain.status === 'valid-headers') {
|
if (chain.status === 'valid-fork' || chain.status === 'valid-headers') {
|
||||||
let block = await bitcoinClient.getBlock(chain.hash);
|
const orphans: OrphanedBlock[] = [];
|
||||||
while (block && block.confirmations === -1) {
|
let hash = chain.hash;
|
||||||
this.orphanedBlocks.push({
|
do {
|
||||||
height: block.height,
|
let orphan = this.blockCache[hash];
|
||||||
hash: block.hash,
|
if (!orphan) {
|
||||||
status: chain.status
|
const block = await bitcoinClient.getBlock(hash);
|
||||||
});
|
if (block && block.confirmations === -1) {
|
||||||
block = await bitcoinClient.getBlock(block.previousblockhash);
|
newOrphans++;
|
||||||
|
orphan = {
|
||||||
|
height: block.height,
|
||||||
|
hash: block.hash,
|
||||||
|
status: chain.status,
|
||||||
|
prevhash: block.previousblockhash,
|
||||||
|
};
|
||||||
|
this.blockCache[hash] = orphan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (orphan) {
|
||||||
|
orphans.push(orphan);
|
||||||
|
}
|
||||||
|
hash = orphan?.prevhash;
|
||||||
|
} while (hash && (Date.now() < breakAt));
|
||||||
|
for (const orphan of orphans) {
|
||||||
|
this.orphanedBlocks[orphan.hash] = orphan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Date.now() >= breakAt) {
|
||||||
|
logger.debug(`Breaking orphaned blocks updater after 10s, will continue next block`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`Updated orphaned blocks cache. Found ${this.orphanedBlocks.length} orphaned blocks`);
|
this.orphansByHeight = {};
|
||||||
|
const allOrphans = Object.values(this.orphanedBlocks);
|
||||||
|
for (const orphan of allOrphans) {
|
||||||
|
if (!this.orphansByHeight[orphan.height]) {
|
||||||
|
this.orphansByHeight[orphan.height] = [];
|
||||||
|
}
|
||||||
|
this.orphansByHeight[orphan.height].push(orphan);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Updated orphaned blocks cache. Fetched ${newOrphans} new orphaned blocks. Total ${allOrphans.length}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot get fetch orphaned blocks. Reason: ${e instanceof Error ? e.message : e}`);
|
logger.err(`Cannot get fetch orphaned blocks. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
}
|
}
|
||||||
@ -48,13 +84,7 @@ class ChainTips {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const orphans: OrphanedBlock[] = [];
|
return this.orphansByHeight[height] || [];
|
||||||
for (const block of this.orphanedBlocks) {
|
|
||||||
if (block.height === height) {
|
|
||||||
orphans.push(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orphans;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,6 +946,33 @@ export class Common {
|
|||||||
return this.validateTransactionHex(matches[1].toLowerCase());
|
return this.validateTransactionHex(matches[1].toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTransactionsFromRequest(req: Request, limit: number = 25): string[] {
|
||||||
|
if (!Array.isArray(req.body) || req.body.some(hex => typeof hex !== 'string')) {
|
||||||
|
throw Object.assign(new Error('Invalid request body (should be an array of hexadecimal strings)'), { code: -1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit && req.body.length > limit) {
|
||||||
|
throw Object.assign(new Error('Exceeded maximum of 25 transactions'), { code: -1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const txs = req.body;
|
||||||
|
|
||||||
|
return txs.map(rawTx => {
|
||||||
|
// Support both upper and lower case hex
|
||||||
|
// Support both txHash= Form and direct API POST
|
||||||
|
const reg = /^((?:[a-fA-F0-9]{2})+)$/;
|
||||||
|
const matches = reg.exec(rawTx);
|
||||||
|
if (!matches || !matches[1]) {
|
||||||
|
throw Object.assign(new Error('Invalid hex string'), { code: -2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guaranteed to be a hex string of multiple of 2
|
||||||
|
// Guaranteed to be lower case
|
||||||
|
// Guaranteed to pass validation (see function below)
|
||||||
|
return this.validateTransactionHex(matches[1].toLowerCase());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static validateTransactionHex(txhex: string): string {
|
private static validateTransactionHex(txhex: string): string {
|
||||||
// Do not mutate txhex
|
// Do not mutate txhex
|
||||||
|
|
||||||
|
@ -666,7 +666,9 @@ class NodesApi {
|
|||||||
node.last_update = null;
|
node.last_update = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sockets = (node.addresses?.map(a => a.addr).join(',')) ?? '';
|
const uniqueAddr = [...new Set(node.addresses?.map(a => a.addr))];
|
||||||
|
const formattedSockets = (uniqueAddr.join(',')) ?? '';
|
||||||
|
|
||||||
const query = `INSERT INTO nodes(
|
const query = `INSERT INTO nodes(
|
||||||
public_key,
|
public_key,
|
||||||
first_seen,
|
first_seen,
|
||||||
@ -695,13 +697,13 @@ class NodesApi {
|
|||||||
node.alias,
|
node.alias,
|
||||||
this.aliasToSearchText(node.alias),
|
this.aliasToSearchText(node.alias),
|
||||||
node.color,
|
node.color,
|
||||||
sockets,
|
formattedSockets,
|
||||||
JSON.stringify(node.features),
|
JSON.stringify(node.features),
|
||||||
node.last_update,
|
node.last_update,
|
||||||
node.alias,
|
node.alias,
|
||||||
this.aliasToSearchText(node.alias),
|
this.aliasToSearchText(node.alias),
|
||||||
node.color,
|
node.color,
|
||||||
sockets,
|
formattedSockets,
|
||||||
JSON.stringify(node.features),
|
JSON.stringify(node.features),
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -404,6 +404,10 @@ class Mempool {
|
|||||||
|
|
||||||
const newAccelerationMap: { [txid: string]: Acceleration } = {};
|
const newAccelerationMap: { [txid: string]: Acceleration } = {};
|
||||||
for (const acceleration of newAccelerations) {
|
for (const acceleration of newAccelerations) {
|
||||||
|
// skip transactions we don't know about
|
||||||
|
if (!this.mempoolCache[acceleration.txid]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
newAccelerationMap[acceleration.txid] = acceleration;
|
newAccelerationMap[acceleration.txid] = acceleration;
|
||||||
if (this.accelerations[acceleration.txid] == null) {
|
if (this.accelerations[acceleration.txid] == null) {
|
||||||
// new acceleration
|
// new acceleration
|
||||||
|
@ -3,6 +3,7 @@ import * as WebSocket from 'ws';
|
|||||||
import {
|
import {
|
||||||
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
|
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
|
||||||
OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo,
|
OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo,
|
||||||
|
MempoolBlockDelta, MempoolDelta, MempoolDeltaTxids
|
||||||
} from '../mempool.interfaces';
|
} from '../mempool.interfaces';
|
||||||
import blocks from './blocks';
|
import blocks from './blocks';
|
||||||
import memPool from './mempool';
|
import memPool from './mempool';
|
||||||
@ -346,6 +347,17 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parsedMessage && parsedMessage['track-accelerations'] != null) {
|
||||||
|
if (parsedMessage['track-accelerations']) {
|
||||||
|
client['track-accelerations'] = true;
|
||||||
|
response['accelerations'] = JSON.stringify({
|
||||||
|
accelerations: Object.values(memPool.getAccelerations()),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
client['track-accelerations'] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedMessage.action === 'init') {
|
if (parsedMessage.action === 'init') {
|
||||||
if (!this.socketData['blocks']?.length || !this.socketData['da'] || !this.socketData['backendInfo'] || !this.socketData['conversions']) {
|
if (!this.socketData['blocks']?.length || !this.socketData['da'] || !this.socketData['backendInfo'] || !this.socketData['conversions']) {
|
||||||
this.updateSocketData();
|
this.updateSocketData();
|
||||||
@ -364,6 +376,18 @@ class WebsocketHandler {
|
|||||||
client['track-donation'] = parsedMessage['track-donation'];
|
client['track-donation'] = parsedMessage['track-donation'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parsedMessage['track-mempool-txids'] === true) {
|
||||||
|
client['track-mempool-txids'] = true;
|
||||||
|
} else if (parsedMessage['track-mempool-txids'] === false) {
|
||||||
|
delete client['track-mempool-txids'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedMessage['track-mempool'] === true) {
|
||||||
|
client['track-mempool'] = true;
|
||||||
|
} else if (parsedMessage['track-mempool'] === false) {
|
||||||
|
delete client['track-mempool'];
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
@ -524,6 +548,7 @@ class WebsocketHandler {
|
|||||||
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
||||||
const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
|
const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
|
||||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||||
|
const accelerations = memPool.getAccelerations();
|
||||||
memPool.handleRbfTransactions(rbfTransactions);
|
memPool.handleRbfTransactions(rbfTransactions);
|
||||||
const rbfChanges = rbfCache.getRbfChanges();
|
const rbfChanges = rbfCache.getRbfChanges();
|
||||||
let rbfReplacements;
|
let rbfReplacements;
|
||||||
@ -545,6 +570,33 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
const latestTransactions = memPool.getLatestTransactions();
|
const latestTransactions = memPool.getLatestTransactions();
|
||||||
|
|
||||||
|
if (memPool.isInSync()) {
|
||||||
|
this.mempoolSequence++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
|
||||||
|
for (const tx of newTransactions) {
|
||||||
|
if (rbfTransactions[tx.txid]) {
|
||||||
|
for (const replaced of rbfTransactions[tx.txid]) {
|
||||||
|
replacedTransactions.push({ replaced: replaced.txid, by: tx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mempoolDeltaTxids: MempoolDeltaTxids = {
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
added: newTransactions.map(tx => tx.txid),
|
||||||
|
removed: deletedTransactions.map(tx => tx.txid),
|
||||||
|
mined: [],
|
||||||
|
replaced: replacedTransactions.map(replacement => ({ replaced: replacement.replaced, by: replacement.by.txid })),
|
||||||
|
};
|
||||||
|
const mempoolDelta: MempoolDelta = {
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
added: newTransactions,
|
||||||
|
removed: deletedTransactions.map(tx => tx.txid),
|
||||||
|
mined: [],
|
||||||
|
replaced: replacedTransactions,
|
||||||
|
};
|
||||||
|
|
||||||
// update init data
|
// update init data
|
||||||
const socketDataFields = {
|
const socketDataFields = {
|
||||||
'mempoolInfo': mempoolInfo,
|
'mempoolInfo': mempoolInfo,
|
||||||
@ -604,9 +656,11 @@ class WebsocketHandler {
|
|||||||
const addressCache = this.makeAddressCache(newTransactions);
|
const addressCache = this.makeAddressCache(newTransactions);
|
||||||
const removedAddressCache = this.makeAddressCache(deletedTransactions);
|
const removedAddressCache = this.makeAddressCache(deletedTransactions);
|
||||||
|
|
||||||
if (memPool.isInSync()) {
|
// pre-compute acceleration delta
|
||||||
this.mempoolSequence++;
|
const accelerationUpdate = {
|
||||||
}
|
added: accelerationDelta.map(txid => accelerations[txid]).filter(acc => acc != null),
|
||||||
|
removed: accelerationDelta.filter(txid => !accelerations[txid]),
|
||||||
|
};
|
||||||
|
|
||||||
// TODO - Fix indentation after PR is merged
|
// TODO - Fix indentation after PR is merged
|
||||||
for (const server of this.webSocketServers) {
|
for (const server of this.webSocketServers) {
|
||||||
@ -847,6 +901,18 @@ class WebsocketHandler {
|
|||||||
response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary);
|
response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client['track-mempool-txids']) {
|
||||||
|
response['mempool-txids'] = getCachedResponse('mempool-txids', mempoolDeltaTxids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client['track-mempool']) {
|
||||||
|
response['mempool-transactions'] = getCachedResponse('mempool-transactions', mempoolDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client['track-accelerations'] && (accelerationUpdate.added.length || accelerationUpdate.removed.length)) {
|
||||||
|
response['accelerations'] = getCachedResponse('accelerations', accelerationUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
@ -992,6 +1058,31 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||||
|
|
||||||
|
if (memPool.isInSync()) {
|
||||||
|
this.mempoolSequence++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const replacedTransactions: { replaced: string, by: TransactionExtended }[] = [];
|
||||||
|
for (const txid of Object.keys(rbfTransactions)) {
|
||||||
|
for (const replaced of rbfTransactions[txid].replaced) {
|
||||||
|
replacedTransactions.push({ replaced: replaced.txid, by: rbfTransactions[txid].replacedBy });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mempoolDeltaTxids: MempoolDeltaTxids = {
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
mined: transactions.map(tx => tx.txid),
|
||||||
|
replaced: replacedTransactions.map(replacement => ({ replaced: replacement.replaced, by: replacement.by.txid })),
|
||||||
|
};
|
||||||
|
const mempoolDelta: MempoolDelta = {
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
mined: transactions.map(tx => tx.txid),
|
||||||
|
replaced: replacedTransactions,
|
||||||
|
};
|
||||||
|
|
||||||
const responseCache = { ...this.socketData };
|
const responseCache = { ...this.socketData };
|
||||||
function getCachedResponse(key, data): string {
|
function getCachedResponse(key, data): string {
|
||||||
if (!responseCache[key]) {
|
if (!responseCache[key]) {
|
||||||
@ -1000,10 +1091,6 @@ class WebsocketHandler {
|
|||||||
return responseCache[key];
|
return responseCache[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memPool.isInSync()) {
|
|
||||||
this.mempoolSequence++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - Fix indentation after PR is merged
|
// TODO - Fix indentation after PR is merged
|
||||||
for (const server of this.webSocketServers) {
|
for (const server of this.webSocketServers) {
|
||||||
server.clients.forEach((client) => {
|
server.clients.forEach((client) => {
|
||||||
@ -1185,6 +1272,14 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client['track-mempool-txids']) {
|
||||||
|
response['mempool-txids'] = getCachedResponse('mempool-txids', mempoolDeltaTxids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client['track-mempool']) {
|
||||||
|
response['mempool-transactions'] = getCachedResponse('mempool-transactions', mempoolDelta);
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(this.serializeResponse(response));
|
client.send(this.serializeResponse(response));
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,7 @@ class Server {
|
|||||||
})
|
})
|
||||||
.use(express.urlencoded({ extended: true }))
|
.use(express.urlencoded({ extended: true }))
|
||||||
.use(express.text({ type: ['text/plain', 'application/base64'] }))
|
.use(express.text({ type: ['text/plain', 'application/base64'] }))
|
||||||
|
.use(express.json())
|
||||||
;
|
;
|
||||||
|
|
||||||
if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) {
|
if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) {
|
||||||
|
@ -71,6 +71,22 @@ export interface MempoolBlockDelta {
|
|||||||
changed: MempoolDeltaChange[];
|
changed: MempoolDeltaChange[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MempoolDeltaTxids {
|
||||||
|
sequence: number,
|
||||||
|
added: string[];
|
||||||
|
removed: string[];
|
||||||
|
mined: string[];
|
||||||
|
replaced: { replaced: string, by: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MempoolDelta {
|
||||||
|
sequence: number,
|
||||||
|
added: MempoolTransactionExtended[];
|
||||||
|
removed: string[];
|
||||||
|
mined: string[];
|
||||||
|
replaced: { replaced: string, by: TransactionExtended }[];
|
||||||
|
}
|
||||||
|
|
||||||
interface VinStrippedToScriptsig {
|
interface VinStrippedToScriptsig {
|
||||||
scriptsig: string;
|
scriptsig: string;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ RUN npm install --omit=dev --omit=optional
|
|||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.25.4-alpine
|
FROM nginx:1.26.0-alpine
|
||||||
|
|
||||||
WORKDIR /patch
|
WORKDIR /patch
|
||||||
|
|
||||||
|
@ -181,6 +181,11 @@
|
|||||||
"bundleName": "wiz",
|
"bundleName": "wiz",
|
||||||
"inject": false
|
"inject": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"input": "src/theme-bukele.scss",
|
||||||
|
"bundleName": "bukele",
|
||||||
|
"inject": false
|
||||||
|
},
|
||||||
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
"node_modules/@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
],
|
],
|
||||||
"vendorChunk": true,
|
"vendorChunk": true,
|
||||||
|
@ -1,30 +1,37 @@
|
|||||||
{
|
{
|
||||||
"theme": "contrast",
|
"theme": "bukele",
|
||||||
"enterprise": "onbtc",
|
"enterprise": "onbtc",
|
||||||
"branding": {
|
"branding": {
|
||||||
"name": "onbtc",
|
"name": "onbtc",
|
||||||
"title": "Oficina Nacional del Bitcoin",
|
"title": "Bitcoin Office",
|
||||||
"site_id": 19,
|
"site_id": 19,
|
||||||
"header_img": "/resources/onbtc.svg",
|
"header_img": "/resources/onbtclogo.svg",
|
||||||
"img": "/resources/elsalvador.svg",
|
"footer_img": "/resources/onbtclogo.svg",
|
||||||
"rounded_corner": true
|
"rounded_corner": true
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"widgets": [
|
"widgets": [
|
||||||
{
|
{
|
||||||
"component": "fees"
|
"component": "fees",
|
||||||
|
"mobileOrder": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "balance",
|
"component": "balance",
|
||||||
|
"mobileOrder": 1,
|
||||||
"props": {
|
"props": {
|
||||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "goggles"
|
"component": "twitter",
|
||||||
|
"mobileOrder": 5,
|
||||||
|
"props": {
|
||||||
|
"handle": "nayibbukele"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "address",
|
"component": "address",
|
||||||
|
"mobileOrder": 2,
|
||||||
"props": {
|
"props": {
|
||||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
|
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo",
|
||||||
"period": "1m"
|
"period": "1m"
|
||||||
@ -35,6 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": "addressTransactions",
|
"component": "addressTransactions",
|
||||||
|
"mobileOrder": 3,
|
||||||
"props": {
|
"props": {
|
||||||
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
"address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo"
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ let settings = [];
|
|||||||
let configContent = {};
|
let configContent = {};
|
||||||
let gitCommitHash = '';
|
let gitCommitHash = '';
|
||||||
let packetJsonVersion = '';
|
let packetJsonVersion = '';
|
||||||
|
let customConfig;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
const rawConfig = fs.readFileSync(CONFIG_FILE_NAME);
|
||||||
@ -23,7 +24,13 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexFilePath = configContent.BASE_MODULE ? 'src/index.' + configContent.BASE_MODULE + '.html' : 'src/index.mempool.html';
|
if (configContent && configContent.CUSTOMIZATION) {
|
||||||
|
customConfig = readConfig(configContent.CUSTOMIZATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseModuleName = configContent.BASE_MODULE || 'mempool';
|
||||||
|
const customBuildName = (customConfig && configContent.enterprise) ? ('.' + configContent.enterprise) : '';
|
||||||
|
const indexFilePath = 'src/index.' + baseModuleName + customBuildName + '.html';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.copyFileSync(indexFilePath, 'src/index.html');
|
fs.copyFileSync(indexFilePath, 'src/index.html');
|
||||||
@ -111,20 +118,14 @@ writeConfigTemplate(GENERATED_TEMPLATE_CONFIG_FILE_NAME, newConfigTemplate);
|
|||||||
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
|
const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME);
|
||||||
|
|
||||||
let customConfigJs = '';
|
let customConfigJs = '';
|
||||||
if (configContent && configContent.CUSTOMIZATION) {
|
if (customConfig) {
|
||||||
const customConfig = readConfig(configContent.CUSTOMIZATION);
|
console.log(`Customizing frontend using ${configContent.CUSTOMIZATION}`);
|
||||||
if (customConfig) {
|
customConfigJs = `(function (window) {
|
||||||
console.log(`Customizing frontend using ${configContent.CUSTOMIZATION}`);
|
window.__env = window.__env || {};
|
||||||
customConfigJs = `(function (window) {
|
window.__env.customize = ${customConfig};
|
||||||
window.__env = window.__env || {};
|
}((typeof global !== 'undefined') ? global : this));
|
||||||
window.__env.customize = ${customConfig};
|
`;
|
||||||
}((typeof global !== 'undefined') ? global : this));
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to load customization file');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeConfig(GENERATED_CUSTOMIZATION_FILE_NAME, customConfigJs);
|
writeConfig(GENERATED_CUSTOMIZATION_FILE_NAME, customConfigJs);
|
||||||
|
|
||||||
if (currentConfig && currentConfig === newConfig) {
|
if (currentConfig && currentConfig === newConfig) {
|
||||||
|
398
frontend/package-lock.json
generated
398
frontend/package-lock.json
generated
@ -32,10 +32,10 @@
|
|||||||
"bootstrap": "~4.6.2",
|
"bootstrap": "~4.6.2",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"cypress": "^13.8.0",
|
"cypress": "^13.9.0",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.5.0",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.1",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-echarts": "~17.1.0",
|
"ngx-echarts": "~17.1.0",
|
||||||
"ngx-infinite-scroll": "^17.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
"@types/cypress": "^1.1.3",
|
"@types/cypress": "^1.1.3",
|
||||||
"cypress": "^13.8.0",
|
"cypress": "^13.9.0",
|
||||||
"cypress-fail-on-console-error": "~5.1.0",
|
"cypress-fail-on-console-error": "~5.1.0",
|
||||||
"cypress-wait-until": "^2.0.1",
|
"cypress-wait-until": "^2.0.1",
|
||||||
"mock-socket": "~9.3.1",
|
"mock-socket": "~9.3.1",
|
||||||
@ -3197,9 +3197,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
"integrity": "sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -3212,9 +3212,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
"integrity": "sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -3227,9 +3227,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
"integrity": "sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3242,9 +3242,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
"integrity": "sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3257,9 +3257,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
"integrity": "sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3272,9 +3272,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
"integrity": "sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3287,9 +3287,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
"integrity": "sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3302,9 +3302,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
"integrity": "sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3317,9 +3317,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
"integrity": "sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -3332,9 +3332,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
"integrity": "sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3347,9 +3347,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
"integrity": "sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -3362,9 +3362,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz",
|
||||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
"integrity": "sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@ -3377,9 +3377,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz",
|
||||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
"integrity": "sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@ -3392,9 +3392,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
"integrity": "sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@ -3407,9 +3407,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz",
|
||||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
"integrity": "sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -3422,9 +3422,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz",
|
||||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
"integrity": "sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@ -3437,9 +3437,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
"integrity": "sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3452,9 +3452,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
"integrity": "sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3467,9 +3467,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
"integrity": "sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3482,9 +3482,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
"integrity": "sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3497,9 +3497,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
"integrity": "sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3512,9 +3512,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
"integrity": "sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -3527,9 +3527,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
"integrity": "sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -8029,9 +8029,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "13.8.0",
|
"version": "13.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz",
|
||||||
"integrity": "sha512-Qau//mtrwEGOU9cn2YjavECKyDUwBh8J2tit+y9s1wsv6C3BX+rlv6I9afmQnL8PmEEzJ6be7nppMHacFzZkTw==",
|
"integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -9197,9 +9197,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz",
|
||||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
"integrity": "sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
@ -9208,29 +9208,29 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.20.2",
|
"@esbuild/aix-ppc64": "0.21.1",
|
||||||
"@esbuild/android-arm": "0.20.2",
|
"@esbuild/android-arm": "0.21.1",
|
||||||
"@esbuild/android-arm64": "0.20.2",
|
"@esbuild/android-arm64": "0.21.1",
|
||||||
"@esbuild/android-x64": "0.20.2",
|
"@esbuild/android-x64": "0.21.1",
|
||||||
"@esbuild/darwin-arm64": "0.20.2",
|
"@esbuild/darwin-arm64": "0.21.1",
|
||||||
"@esbuild/darwin-x64": "0.20.2",
|
"@esbuild/darwin-x64": "0.21.1",
|
||||||
"@esbuild/freebsd-arm64": "0.20.2",
|
"@esbuild/freebsd-arm64": "0.21.1",
|
||||||
"@esbuild/freebsd-x64": "0.20.2",
|
"@esbuild/freebsd-x64": "0.21.1",
|
||||||
"@esbuild/linux-arm": "0.20.2",
|
"@esbuild/linux-arm": "0.21.1",
|
||||||
"@esbuild/linux-arm64": "0.20.2",
|
"@esbuild/linux-arm64": "0.21.1",
|
||||||
"@esbuild/linux-ia32": "0.20.2",
|
"@esbuild/linux-ia32": "0.21.1",
|
||||||
"@esbuild/linux-loong64": "0.20.2",
|
"@esbuild/linux-loong64": "0.21.1",
|
||||||
"@esbuild/linux-mips64el": "0.20.2",
|
"@esbuild/linux-mips64el": "0.21.1",
|
||||||
"@esbuild/linux-ppc64": "0.20.2",
|
"@esbuild/linux-ppc64": "0.21.1",
|
||||||
"@esbuild/linux-riscv64": "0.20.2",
|
"@esbuild/linux-riscv64": "0.21.1",
|
||||||
"@esbuild/linux-s390x": "0.20.2",
|
"@esbuild/linux-s390x": "0.21.1",
|
||||||
"@esbuild/linux-x64": "0.20.2",
|
"@esbuild/linux-x64": "0.21.1",
|
||||||
"@esbuild/netbsd-x64": "0.20.2",
|
"@esbuild/netbsd-x64": "0.21.1",
|
||||||
"@esbuild/openbsd-x64": "0.20.2",
|
"@esbuild/openbsd-x64": "0.21.1",
|
||||||
"@esbuild/sunos-x64": "0.20.2",
|
"@esbuild/sunos-x64": "0.21.1",
|
||||||
"@esbuild/win32-arm64": "0.20.2",
|
"@esbuild/win32-arm64": "0.21.1",
|
||||||
"@esbuild/win32-ia32": "0.20.2",
|
"@esbuild/win32-ia32": "0.21.1",
|
||||||
"@esbuild/win32-x64": "0.20.2"
|
"@esbuild/win32-x64": "0.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild-wasm": {
|
"node_modules/esbuild-wasm": {
|
||||||
@ -20563,141 +20563,141 @@
|
|||||||
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
|
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="
|
||||||
},
|
},
|
||||||
"@esbuild/aix-ppc64": {
|
"@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
"integrity": "sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/android-arm": {
|
"@esbuild/android-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
"integrity": "sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/android-arm64": {
|
"@esbuild/android-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
"integrity": "sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/android-x64": {
|
"@esbuild/android-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
"integrity": "sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/darwin-arm64": {
|
"@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
"integrity": "sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/darwin-x64": {
|
"@esbuild/darwin-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
"integrity": "sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/freebsd-arm64": {
|
"@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
"integrity": "sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/freebsd-x64": {
|
"@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
"integrity": "sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-arm": {
|
"@esbuild/linux-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz",
|
||||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
"integrity": "sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-arm64": {
|
"@esbuild/linux-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
"integrity": "sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-ia32": {
|
"@esbuild/linux-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
"integrity": "sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-loong64": {
|
"@esbuild/linux-loong64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz",
|
||||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
"integrity": "sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-mips64el": {
|
"@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz",
|
||||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
"integrity": "sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-ppc64": {
|
"@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz",
|
||||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
"integrity": "sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-riscv64": {
|
"@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz",
|
||||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
"integrity": "sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-s390x": {
|
"@esbuild/linux-s390x": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz",
|
||||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
"integrity": "sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/linux-x64": {
|
"@esbuild/linux-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
"integrity": "sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/netbsd-x64": {
|
"@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
"integrity": "sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/openbsd-x64": {
|
"@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
"integrity": "sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/sunos-x64": {
|
"@esbuild/sunos-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
"integrity": "sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/win32-arm64": {
|
"@esbuild/win32-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz",
|
||||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
"integrity": "sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/win32-ia32": {
|
"@esbuild/win32-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz",
|
||||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
"integrity": "sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@esbuild/win32-x64": {
|
"@esbuild/win32-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz",
|
||||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
"integrity": "sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@eslint-community/eslint-utils": {
|
"@eslint-community/eslint-utils": {
|
||||||
@ -24112,9 +24112,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "13.8.0",
|
"version": "13.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.9.0.tgz",
|
||||||
"integrity": "sha512-Qau//mtrwEGOU9cn2YjavECKyDUwBh8J2tit+y9s1wsv6C3BX+rlv6I9afmQnL8PmEEzJ6be7nppMHacFzZkTw==",
|
"integrity": "sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^3.0.0",
|
"@cypress/request": "^3.0.0",
|
||||||
@ -25032,33 +25032,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"esbuild": {
|
"esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz",
|
||||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
"integrity": "sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@esbuild/aix-ppc64": "0.20.2",
|
"@esbuild/aix-ppc64": "0.21.1",
|
||||||
"@esbuild/android-arm": "0.20.2",
|
"@esbuild/android-arm": "0.21.1",
|
||||||
"@esbuild/android-arm64": "0.20.2",
|
"@esbuild/android-arm64": "0.21.1",
|
||||||
"@esbuild/android-x64": "0.20.2",
|
"@esbuild/android-x64": "0.21.1",
|
||||||
"@esbuild/darwin-arm64": "0.20.2",
|
"@esbuild/darwin-arm64": "0.21.1",
|
||||||
"@esbuild/darwin-x64": "0.20.2",
|
"@esbuild/darwin-x64": "0.21.1",
|
||||||
"@esbuild/freebsd-arm64": "0.20.2",
|
"@esbuild/freebsd-arm64": "0.21.1",
|
||||||
"@esbuild/freebsd-x64": "0.20.2",
|
"@esbuild/freebsd-x64": "0.21.1",
|
||||||
"@esbuild/linux-arm": "0.20.2",
|
"@esbuild/linux-arm": "0.21.1",
|
||||||
"@esbuild/linux-arm64": "0.20.2",
|
"@esbuild/linux-arm64": "0.21.1",
|
||||||
"@esbuild/linux-ia32": "0.20.2",
|
"@esbuild/linux-ia32": "0.21.1",
|
||||||
"@esbuild/linux-loong64": "0.20.2",
|
"@esbuild/linux-loong64": "0.21.1",
|
||||||
"@esbuild/linux-mips64el": "0.20.2",
|
"@esbuild/linux-mips64el": "0.21.1",
|
||||||
"@esbuild/linux-ppc64": "0.20.2",
|
"@esbuild/linux-ppc64": "0.21.1",
|
||||||
"@esbuild/linux-riscv64": "0.20.2",
|
"@esbuild/linux-riscv64": "0.21.1",
|
||||||
"@esbuild/linux-s390x": "0.20.2",
|
"@esbuild/linux-s390x": "0.21.1",
|
||||||
"@esbuild/linux-x64": "0.20.2",
|
"@esbuild/linux-x64": "0.21.1",
|
||||||
"@esbuild/netbsd-x64": "0.20.2",
|
"@esbuild/netbsd-x64": "0.21.1",
|
||||||
"@esbuild/openbsd-x64": "0.20.2",
|
"@esbuild/openbsd-x64": "0.21.1",
|
||||||
"@esbuild/sunos-x64": "0.20.2",
|
"@esbuild/sunos-x64": "0.21.1",
|
||||||
"@esbuild/win32-arm64": "0.20.2",
|
"@esbuild/win32-arm64": "0.21.1",
|
||||||
"@esbuild/win32-ia32": "0.20.2",
|
"@esbuild/win32-ia32": "0.21.1",
|
||||||
"@esbuild/win32-x64": "0.20.2"
|
"@esbuild/win32-x64": "0.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"esbuild-wasm": {
|
"esbuild-wasm": {
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
"ngx-infinite-scroll": "^17.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
"qrcode": "1.5.1",
|
"qrcode": "1.5.1",
|
||||||
"rxjs": "~7.8.1",
|
"rxjs": "~7.8.1",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.1",
|
||||||
"tinyify": "^4.0.0",
|
"tinyify": "^4.0.0",
|
||||||
"tlite": "^0.1.9",
|
"tlite": "^0.1.9",
|
||||||
"tslib": "~2.6.0",
|
"tslib": "~2.6.0",
|
||||||
@ -115,7 +115,7 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
"@types/cypress": "^1.1.3",
|
"@types/cypress": "^1.1.3",
|
||||||
"cypress": "^13.8.0",
|
"cypress": "^13.9.0",
|
||||||
"cypress-fail-on-console-error": "~5.1.0",
|
"cypress-fail-on-console-error": "~5.1.0",
|
||||||
"cypress-wait-until": "^2.0.1",
|
"cypress-wait-until": "^2.0.1",
|
||||||
"mock-socket": "~9.3.1",
|
"mock-socket": "~9.3.1",
|
||||||
|
@ -53,6 +53,44 @@ let routes: Routes = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'testnet4',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
|
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||||
|
data: { preload: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule),
|
||||||
|
data: { preload: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'wallet',
|
||||||
|
children: [],
|
||||||
|
component: AddressGroupComponent,
|
||||||
|
data: {
|
||||||
|
networkSpecific: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'status',
|
||||||
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
component: StatusViewComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule),
|
||||||
|
data: { preload: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
redirectTo: '/testnet4'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'signet',
|
path: 'signet',
|
||||||
children: [
|
children: [
|
||||||
@ -130,6 +168,10 @@ let routes: Routes = [
|
|||||||
path: 'testnet',
|
path: 'testnet',
|
||||||
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'testnet4',
|
||||||
|
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'signet',
|
path: 'signet',
|
||||||
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
|
||||||
|
@ -189,22 +189,22 @@ export const specialBlocks = {
|
|||||||
'0': {
|
'0': {
|
||||||
labelEvent: 'Genesis',
|
labelEvent: 'Genesis',
|
||||||
labelEventCompleted: 'The Genesis of Bitcoin',
|
labelEventCompleted: 'The Genesis of Bitcoin',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'210000': {
|
'210000': {
|
||||||
labelEvent: 'Bitcoin\'s 1st Halving',
|
labelEvent: 'Bitcoin\'s 1st Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 25 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 25 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'420000': {
|
'420000': {
|
||||||
labelEvent: 'Bitcoin\'s 2nd Halving',
|
labelEvent: 'Bitcoin\'s 2nd Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 12.5 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 12.5 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'630000': {
|
'630000': {
|
||||||
labelEvent: 'Bitcoin\'s 3rd Halving',
|
labelEvent: 'Bitcoin\'s 3rd Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 6.25 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 6.25 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'709632': {
|
'709632': {
|
||||||
labelEvent: 'Taproot 🌱 activation',
|
labelEvent: 'Taproot 🌱 activation',
|
||||||
@ -214,62 +214,62 @@ export const specialBlocks = {
|
|||||||
'840000': {
|
'840000': {
|
||||||
labelEvent: 'Bitcoin\'s 4th Halving',
|
labelEvent: 'Bitcoin\'s 4th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 3.125 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 3.125 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'1050000': {
|
'1050000': {
|
||||||
labelEvent: 'Bitcoin\'s 5th Halving',
|
labelEvent: 'Bitcoin\'s 5th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 1.5625 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 1.5625 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'1260000': {
|
'1260000': {
|
||||||
labelEvent: 'Bitcoin\'s 6th Halving',
|
labelEvent: 'Bitcoin\'s 6th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.78125 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.78125 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'1470000': {
|
'1470000': {
|
||||||
labelEvent: 'Bitcoin\'s 7th Halving',
|
labelEvent: 'Bitcoin\'s 7th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.390625 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.390625 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'1680000': {
|
'1680000': {
|
||||||
labelEvent: 'Bitcoin\'s 8th Halving',
|
labelEvent: 'Bitcoin\'s 8th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.1953125 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.1953125 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'1890000': {
|
'1890000': {
|
||||||
labelEvent: 'Bitcoin\'s 9th Halving',
|
labelEvent: 'Bitcoin\'s 9th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.09765625 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.09765625 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'2100000': {
|
'2100000': {
|
||||||
labelEvent: 'Bitcoin\'s 10th Halving',
|
labelEvent: 'Bitcoin\'s 10th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.04882812 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.04882812 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'2310000': {
|
'2310000': {
|
||||||
labelEvent: 'Bitcoin\'s 11th Halving',
|
labelEvent: 'Bitcoin\'s 11th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.02441406 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.02441406 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'2520000': {
|
'2520000': {
|
||||||
labelEvent: 'Bitcoin\'s 12th Halving',
|
labelEvent: 'Bitcoin\'s 12th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.01220703 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.01220703 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'2730000': {
|
'2730000': {
|
||||||
labelEvent: 'Bitcoin\'s 13th Halving',
|
labelEvent: 'Bitcoin\'s 13th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.00610351 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.00610351 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'2940000': {
|
'2940000': {
|
||||||
labelEvent: 'Bitcoin\'s 14th Halving',
|
labelEvent: 'Bitcoin\'s 14th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.00305175 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.00305175 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
},
|
},
|
||||||
'3150000': {
|
'3150000': {
|
||||||
labelEvent: 'Bitcoin\'s 15th Halving',
|
labelEvent: 'Bitcoin\'s 15th Halving',
|
||||||
labelEventCompleted: 'Block Subsidy has halved to 0.00152587 BTC per block',
|
labelEventCompleted: 'Block Subsidy has halved to 0.00152587 BTC per block',
|
||||||
networks: ['mainnet', 'testnet'],
|
networks: ['mainnet', 'testnet', 'testnet4'],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -266,6 +266,11 @@ const featureActivation = {
|
|||||||
segwit: 872730,
|
segwit: 872730,
|
||||||
taproot: 2032291,
|
taproot: 2032291,
|
||||||
},
|
},
|
||||||
|
testnet4: {
|
||||||
|
rbf: 0,
|
||||||
|
segwit: 0,
|
||||||
|
taproot: 0,
|
||||||
|
},
|
||||||
signet: {
|
signet: {
|
||||||
rbf: 0,
|
rbf: 0,
|
||||||
segwit: 0,
|
segwit: 0,
|
||||||
|
@ -343,8 +343,8 @@
|
|||||||
<a href="https://opencrypto.org/" title="Coppa - Crypto Open Patent Alliance">
|
<a href="https://opencrypto.org/" title="Coppa - Crypto Open Patent Alliance">
|
||||||
<img class="copa" src="/resources/profile/copa.png" />
|
<img class="copa" src="/resources/profile/copa.png" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://bisq.network/" title="Bisq Network">
|
<a href="https://bitcoin.gob.sv" title="Oficina Nacional del Bitcoin">
|
||||||
<img class="bisq" src="/resources/profile/bisq.svg" />
|
<img class="sv" src="/resources/profile/onbtc-full.svg" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,8 +129,9 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
.bisq {
|
.sv {
|
||||||
top: 3px;
|
height: 85px;
|
||||||
|
width: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||||
import { combineLatest, BehaviorSubject, Observable, catchError, of, switchMap, tap } from 'rxjs';
|
import { BehaviorSubject, Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||||
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
||||||
import { StateService } from '../../../services/state.service';
|
import { StateService } from '../../../services/state.service';
|
||||||
import { WebsocketService } from '../../../services/websocket.service';
|
import { WebsocketService } from '../../../services/websocket.service';
|
||||||
@ -11,7 +11,7 @@ import { ServicesApiServices } from '../../../services/services-api.service';
|
|||||||
styleUrls: ['./accelerations-list.component.scss'],
|
styleUrls: ['./accelerations-list.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AccelerationsListComponent implements OnInit {
|
export class AccelerationsListComponent implements OnInit, OnDestroy {
|
||||||
@Input() widget: boolean = false;
|
@Input() widget: boolean = false;
|
||||||
@Input() pending: boolean = false;
|
@Input() pending: boolean = false;
|
||||||
@Input() accelerations$: Observable<Acceleration[]>;
|
@Input() accelerations$: Observable<Acceleration[]>;
|
||||||
@ -44,7 +44,10 @@ export class AccelerationsListComponent implements OnInit {
|
|||||||
|
|
||||||
this.accelerationList$ = this.pageSubject.pipe(
|
this.accelerationList$ = this.pageSubject.pipe(
|
||||||
switchMap((page) => {
|
switchMap((page) => {
|
||||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistoryObserveResponse$({ page: page }));
|
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.stateService.liveAccelerations$ : this.servicesApiService.getAccelerationHistoryObserveResponse$({ page: page }));
|
||||||
|
if (!this.accelerations$ && this.pending) {
|
||||||
|
this.websocketService.ensureTrackAccelerations();
|
||||||
|
}
|
||||||
return accelerationObservable$.pipe(
|
return accelerationObservable$.pipe(
|
||||||
switchMap(response => {
|
switchMap(response => {
|
||||||
let accelerations = response;
|
let accelerations = response;
|
||||||
@ -85,4 +88,8 @@ export class AccelerationsListComponent implements OnInit {
|
|||||||
trackByBlock(index: number, block: BlockExtended): number {
|
trackByBlock(index: number, block: BlockExtended): number {
|
||||||
return block.height;
|
return block.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.websocketService.stopTrackAccelerations();
|
||||||
|
}
|
||||||
}
|
}
|
@ -60,7 +60,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, PLATFORM_ID } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { SeoService } from '../../../services/seo.service';
|
import { SeoService } from '../../../services/seo.service';
|
||||||
import { OpenGraphService } from '../../../services/opengraph.service';
|
import { OpenGraphService } from '../../../services/opengraph.service';
|
||||||
import { WebsocketService } from '../../../services/websocket.service';
|
import { WebsocketService } from '../../../services/websocket.service';
|
||||||
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
||||||
import { StateService } from '../../../services/state.service';
|
import { StateService } from '../../../services/state.service';
|
||||||
import { Observable, catchError, combineLatest, distinctUntilChanged, interval, map, of, share, startWith, switchMap, tap } from 'rxjs';
|
import { Observable, Subscription, catchError, combineLatest, distinctUntilChanged, map, of, share, switchMap, tap } from 'rxjs';
|
||||||
import { Color } from '../../block-overview-graph/sprite-types';
|
import { Color } from '../../block-overview-graph/sprite-types';
|
||||||
import { hexToColor } from '../../block-overview-graph/utils';
|
import { hexToColor } from '../../block-overview-graph/utils';
|
||||||
import TxView from '../../block-overview-graph/tx-view';
|
import TxView from '../../block-overview-graph/tx-view';
|
||||||
@ -28,7 +28,7 @@ interface AccelerationBlock extends BlockExtended {
|
|||||||
styleUrls: ['./accelerator-dashboard.component.scss'],
|
styleUrls: ['./accelerator-dashboard.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AcceleratorDashboardComponent implements OnInit {
|
export class AcceleratorDashboardComponent implements OnInit, OnDestroy {
|
||||||
blocks$: Observable<AccelerationBlock[]>;
|
blocks$: Observable<AccelerationBlock[]>;
|
||||||
accelerations$: Observable<Acceleration[]>;
|
accelerations$: Observable<Acceleration[]>;
|
||||||
pendingAccelerations$: Observable<Acceleration[]>;
|
pendingAccelerations$: Observable<Acceleration[]>;
|
||||||
@ -39,6 +39,8 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
firstLoad = true;
|
firstLoad = true;
|
||||||
timespan: '3d' | '1w' | '1m' = '1w';
|
timespan: '3d' | '1w' | '1m' = '1w';
|
||||||
|
|
||||||
|
accelerationDeltaSubscription: Subscription;
|
||||||
|
|
||||||
graphHeight: number = 300;
|
graphHeight: number = 300;
|
||||||
theme: ThemeService;
|
theme: ThemeService;
|
||||||
|
|
||||||
@ -59,27 +61,28 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.onResize();
|
this.onResize();
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks', 'stats']);
|
this.websocketService.want(['blocks', 'mempool-blocks', 'stats']);
|
||||||
|
this.websocketService.startTrackAccelerations();
|
||||||
|
|
||||||
this.pendingAccelerations$ = (this.stateService.isBrowser ? interval(30000) : of(null)).pipe(
|
this.pendingAccelerations$ = this.stateService.liveAccelerations$.pipe(
|
||||||
startWith(true),
|
|
||||||
switchMap(() => {
|
|
||||||
return this.serviceApiServices.getAccelerations$().pipe(
|
|
||||||
catchError(() => {
|
|
||||||
return of([]);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
tap(accelerations => {
|
|
||||||
if (!this.firstLoad && accelerations.some(acc => !this.seen.has(acc.txid))) {
|
|
||||||
this.audioService.playSound('bright-harmony');
|
|
||||||
}
|
|
||||||
for(const acc of accelerations) {
|
|
||||||
this.seen.add(acc.txid);
|
|
||||||
}
|
|
||||||
this.firstLoad = false;
|
|
||||||
}),
|
|
||||||
share(),
|
share(),
|
||||||
);
|
);
|
||||||
|
this.accelerationDeltaSubscription = this.stateService.accelerations$.subscribe((delta) => {
|
||||||
|
if (!delta.reset) {
|
||||||
|
let hasNewAcceleration = false;
|
||||||
|
for (const acc of delta.added) {
|
||||||
|
if (!this.seen.has(acc.txid)) {
|
||||||
|
hasNewAcceleration = true;
|
||||||
|
}
|
||||||
|
this.seen.add(acc.txid);
|
||||||
|
}
|
||||||
|
for (const txid of delta.removed) {
|
||||||
|
this.seen.delete(txid);
|
||||||
|
}
|
||||||
|
if (hasNewAcceleration) {
|
||||||
|
this.audioService.playSound('bright-harmony');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.accelerations$ = this.stateService.chainTip$.pipe(
|
this.accelerations$ = this.stateService.chainTip$.pipe(
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
@ -145,7 +148,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
||||||
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1;
|
||||||
return this.theme.theme === 'contrast' ? contrastColors[feeLevelIndex] || contrastColors[contrastColors.length - 1] : normalColors[feeLevelIndex] || normalColors[normalColors.length - 1];
|
return this.theme.theme === 'contrast' || this.theme.theme === 'bukele' ? contrastColors[feeLevelIndex] || contrastColors[contrastColors.length - 1] : normalColors[feeLevelIndex] || normalColors[normalColors.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +157,11 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.accelerationDeltaSubscription.unsubscribe();
|
||||||
|
this.websocketService.stopTrackAccelerations();
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(): void {
|
onResize(): void {
|
||||||
if (window.innerWidth >= 992) {
|
if (window.innerWidth >= 992) {
|
||||||
|
@ -2,7 +2,8 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core
|
|||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
import { StateService } from '../../../services/state.service';
|
||||||
|
import { WebsocketService } from '../../../services/websocket.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pending-stats',
|
selector: 'app-pending-stats',
|
||||||
@ -15,11 +16,12 @@ export class PendingStatsComponent implements OnInit {
|
|||||||
public accelerationStats$: Observable<any>;
|
public accelerationStats$: Observable<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private servicesApiService: ServicesApiServices,
|
private stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.accelerationStats$ = (this.accelerations$ || this.servicesApiService.getAccelerations$()).pipe(
|
this.accelerationStats$ = (this.accelerations$ || this.stateService.liveAccelerations$).pipe(
|
||||||
switchMap(accelerations => {
|
switchMap(accelerations => {
|
||||||
let totalAccelerations = 0;
|
let totalAccelerations = 0;
|
||||||
let totalFeeDelta = 0;
|
let totalFeeDelta = 0;
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
<app-indexing-progress *ngIf="!widget"></app-indexing-progress>
|
<app-indexing-progress *ngIf="!widget"></app-indexing-progress>
|
||||||
|
|
||||||
<div [class.full-container]="!widget">
|
<div [class.full-container]="!widget">
|
||||||
<div *ngIf="!widget" class="card-header mb-0 mb-md-2">
|
|
||||||
<div class="d-flex d-md-block align-items-baseline">
|
|
||||||
<span i18n="address.balance-history">Balance History</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!error">
|
<ng-container *ngIf="!error">
|
||||||
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? ((height + 20) + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||||
(chartInit)="onChartInit($event)">
|
(chartInit)="onChartInit($event)">
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -45,23 +46,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
@media (max-width: 992px) {
|
|
||||||
padding-bottom: 25px;
|
|
||||||
}
|
|
||||||
@media (max-width: 829px) {
|
|
||||||
padding-bottom: 50px;
|
|
||||||
}
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
padding-bottom: 25px;
|
|
||||||
}
|
|
||||||
@media (max-width: 629px) {
|
|
||||||
padding-bottom: 55px;
|
|
||||||
}
|
|
||||||
@media (max-width: 567px) {
|
|
||||||
padding-bottom: 55px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.chart-widget {
|
.chart-widget {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
|
||||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||||
import { Observable, of } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription, combineLatest, of } from 'rxjs';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
import { AddressTxSummary, ChainStats } from '../../interfaces/electrs.interface';
|
import { AddressTxSummary, ChainStats } from '../../interfaces/electrs.interface';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
@ -32,7 +32,7 @@ const periodSeconds = {
|
|||||||
`],
|
`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AddressGraphComponent implements OnChanges {
|
export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||||
@Input() address: string;
|
@Input() address: string;
|
||||||
@Input() isPubkey: boolean = false;
|
@Input() isPubkey: boolean = false;
|
||||||
@Input() stats: ChainStats;
|
@Input() stats: ChainStats;
|
||||||
@ -46,6 +46,9 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
data: any[] = [];
|
data: any[] = [];
|
||||||
hoverData: any[] = [];
|
hoverData: any[] = [];
|
||||||
|
|
||||||
|
subscription: Subscription;
|
||||||
|
redraw$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
chartOptions: EChartsOption = {};
|
chartOptions: EChartsOption = {};
|
||||||
chartInitOptions = {
|
chartInitOptions = {
|
||||||
renderer: 'svg',
|
renderer: 'svg',
|
||||||
@ -70,24 +73,38 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
if (!this.address || !this.stats) {
|
if (!this.address || !this.stats) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(this.addressSummary$ || (this.isPubkey
|
if (changes.address || changes.isPubkey || changes.addressSummary$ || changes.stats) {
|
||||||
? this.electrsApiService.getScriptHashSummary$((this.address.length === 66 ? '21' : '41') + this.address + 'ac')
|
if (this.subscription) {
|
||||||
: this.electrsApiService.getAddressSummary$(this.address)).pipe(
|
this.subscription.unsubscribe();
|
||||||
catchError(e => {
|
|
||||||
this.error = `Failed to fetch address balance history: ${e?.status || ''} ${e?.statusText || 'unknown error'}`;
|
|
||||||
return of(null);
|
|
||||||
}),
|
|
||||||
)).subscribe(addressSummary => {
|
|
||||||
if (addressSummary) {
|
|
||||||
this.error = null;
|
|
||||||
this.prepareChartOptions(addressSummary);
|
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.subscription = combineLatest([
|
||||||
this.cd.markForCheck();
|
this.redraw$,
|
||||||
});
|
(this.addressSummary$ || (this.isPubkey
|
||||||
|
? this.electrsApiService.getScriptHashSummary$((this.address.length === 66 ? '21' : '41') + this.address + 'ac')
|
||||||
|
: this.electrsApiService.getAddressSummary$(this.address)).pipe(
|
||||||
|
catchError(e => {
|
||||||
|
this.error = `Failed to fetch address balance history: ${e?.status || ''} ${e?.statusText || 'unknown error'}`;
|
||||||
|
return of(null);
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
]).subscribe(([redraw, addressSummary]) => {
|
||||||
|
if (addressSummary) {
|
||||||
|
this.error = null;
|
||||||
|
this.prepareChartOptions(addressSummary);
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// re-trigger subscription
|
||||||
|
this.redraw$.next(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(summary): void {
|
prepareChartOptions(summary): void {
|
||||||
|
if (!summary || !this.stats) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let total = (this.stats.funded_txo_sum - this.stats.spent_txo_sum);
|
let total = (this.stats.funded_txo_sum - this.stats.spent_txo_sum);
|
||||||
this.data = summary.map(d => {
|
this.data = summary.map(d => {
|
||||||
const balance = total;
|
const balance = total;
|
||||||
@ -104,8 +121,8 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxValue = this.data.reduce((acc, d) => Math.max(acc, Math.abs(d[1] || d.value[1])), 0);
|
const maxValue = this.data.reduce((acc, d) => Math.max(acc, Math.abs(d[1] ?? d.value[1])), 0);
|
||||||
const minValue = this.data.reduce((acc, d) => Math.min(acc, Math.abs(d[1] || d.value[1])), maxValue);
|
const minValue = this.data.reduce((acc, d) => Math.min(acc, Math.abs(d[1] ?? d.value[1])), maxValue);
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
color: [
|
color: [
|
||||||
@ -230,6 +247,12 @@ export class AddressGraphComponent implements OnChanges {
|
|||||||
this.chartInstance.on('click', 'series', this.onChartClick.bind(this));
|
this.chartInstance.on('click', 'series', this.onChartClick.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return (window.innerWidth <= 767.98);
|
return (window.innerWidth <= 767.98);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: var(--fg);
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -53,10 +53,20 @@
|
|||||||
|
|
||||||
<ng-container *ngIf="(stateService.backend$ | async) === 'esplora' && address && transactions && transactions.length > 2">
|
<ng-container *ngIf="(stateService.backend$ | async) === 'esplora' && address && transactions && transactions.length > 2">
|
||||||
<br>
|
<br>
|
||||||
|
<div class="title-tx">
|
||||||
|
<h2 class="text-left" i18n="address.balance-history">Balance History</h2>
|
||||||
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
<div class="widget-toggler" *ngIf="showBalancePeriod()">
|
||||||
|
<a href="" (click)="setBalancePeriod('all')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': balancePeriod === 'all'}"><small i18n="all">all</small></a>
|
||||||
|
<span style="color: var(--transparent-fg); font-size: 8px"> | </span>
|
||||||
|
<a href="" (click)="setBalancePeriod('1m')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': balancePeriod === '1m'}"><small i18n="recent">recent</small></a>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<app-address-graph [address]="addressString" [isPubkey]="address?.is_pubkey" [stats]="address.chain_stats" />
|
<app-address-graph [address]="addressString" [isPubkey]="address?.is_pubkey" [stats]="address.chain_stats" [period]="balancePeriod" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: var(--fg);
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -109,3 +109,19 @@ h1 {
|
|||||||
flex-grow: 0.5;
|
flex-grow: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.widget-toggler {
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 3px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggler-option {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
color: var(--transparent-fg);
|
||||||
|
}
|
@ -38,6 +38,8 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
txCount = 0;
|
txCount = 0;
|
||||||
received = 0;
|
received = 0;
|
||||||
sent = 0;
|
sent = 0;
|
||||||
|
now = Date.now() / 1000;
|
||||||
|
balancePeriod: 'all' | '1m' = 'all';
|
||||||
|
|
||||||
private tempTransactions: Transaction[];
|
private tempTransactions: Transaction[];
|
||||||
private timeTxIndexes: number[];
|
private timeTxIndexes: number[];
|
||||||
@ -175,6 +177,10 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
this.transactions = this.tempTransactions;
|
this.transactions = this.tempTransactions;
|
||||||
if (this.transactions.length === this.txCount) this.fullyLoaded = true;
|
if (this.transactions.length === this.txCount) this.fullyLoaded = true;
|
||||||
this.isLoadingTransactions = false;
|
this.isLoadingTransactions = false;
|
||||||
|
|
||||||
|
if (!this.showBalancePeriod()) {
|
||||||
|
this.setBalancePeriod('all');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@ -297,6 +303,18 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
|
this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBalancePeriod(period: 'all' | '1m'): boolean {
|
||||||
|
this.balancePeriod = period;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
showBalancePeriod(): boolean {
|
||||||
|
return this.transactions?.length && (
|
||||||
|
!this.transactions[0].status?.confirmed
|
||||||
|
|| this.transactions[0].status.block_time > (this.now - (60 * 60 * 24 * 30))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.mainSubscription.unsubscribe();
|
this.mainSubscription.unsubscribe();
|
||||||
this.mempoolTxSubscription.unsubscribe();
|
this.mempoolTxSubscription.unsubscribe();
|
||||||
|
@ -43,5 +43,6 @@
|
|||||||
<ng-template [ngIf]="network === 'liquid' && !forceBtc">L-</ng-template>
|
<ng-template [ngIf]="network === 'liquid' && !forceBtc">L-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
||||||
|
<ng-template [ngIf]="network === 'testnet4'">t</ng-template>
|
||||||
<ng-template [ngIf]="network === 'signet'">s</ng-template>
|
<ng-template [ngIf]="network === 'signet'">s</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: var(--fg);
|
background-color: #fff;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -57,8 +57,9 @@ export class BalanceWidgetComponent implements OnInit, OnChanges {
|
|||||||
calculateStats(summary: AddressTxSummary[]): void {
|
calculateStats(summary: AddressTxSummary[]): void {
|
||||||
let weekTotal = 0;
|
let weekTotal = 0;
|
||||||
let monthTotal = 0;
|
let monthTotal = 0;
|
||||||
const weekAgo = (Date.now() / 1000) - (60 * 60 * 24 * 7);
|
|
||||||
const monthAgo = (Date.now() / 1000) - (60 * 60 * 24 * 30);
|
const weekAgo = (new Date(new Date().setHours(0, 0, 0, 0) - (7 * 24 * 60 * 60 * 1000)).getTime()) / 1000;
|
||||||
|
const monthAgo = (new Date(new Date().setHours(0, 0, 0, 0) - (30 * 24 * 60 * 60 * 1000)).getTime()) / 1000;
|
||||||
for (let i = 0; i < summary.length && summary[i].time >= monthAgo; i++) {
|
for (let i = 0; i < summary.length && summary[i].time >= monthAgo; i++) {
|
||||||
monthTotal += summary[i].value;
|
monthTotal += summary[i].value;
|
||||||
if (summary[i].time >= weekAgo) {
|
if (summary[i].time >= weekAgo) {
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -81,6 +81,20 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
tooltipPosition: Position;
|
tooltipPosition: Position;
|
||||||
|
|
||||||
readyNextFrame = false;
|
readyNextFrame = false;
|
||||||
|
lastUpdate: number = 0;
|
||||||
|
pendingUpdate: {
|
||||||
|
count: number,
|
||||||
|
add: { [txid: string]: TransactionStripped },
|
||||||
|
remove: { [txid: string]: string },
|
||||||
|
change: { [txid: string]: { txid: string, rate: number | undefined, acc: boolean | undefined } },
|
||||||
|
direction?: string,
|
||||||
|
} = {
|
||||||
|
count: 0,
|
||||||
|
add: {},
|
||||||
|
remove: {},
|
||||||
|
change: {},
|
||||||
|
direction: 'left',
|
||||||
|
};
|
||||||
|
|
||||||
searchText: string;
|
searchText: string;
|
||||||
searchSubscription: Subscription;
|
searchSubscription: Subscription;
|
||||||
@ -176,6 +190,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
destroy(): void {
|
destroy(): void {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
this.scene.destroy();
|
this.scene.destroy();
|
||||||
|
this.clearUpdateQueue();
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,6 +203,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
this.filtersAvailable = filtersAvailable;
|
this.filtersAvailable = filtersAvailable;
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
|
this.clearUpdateQueue();
|
||||||
this.scene.setup(transactions);
|
this.scene.setup(transactions);
|
||||||
this.readyNextFrame = true;
|
this.readyNextFrame = true;
|
||||||
this.start();
|
this.start();
|
||||||
@ -197,6 +213,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
enter(transactions: TransactionStripped[], direction: string): void {
|
enter(transactions: TransactionStripped[], direction: string): void {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
|
this.clearUpdateQueue();
|
||||||
this.scene.enter(transactions, direction);
|
this.scene.enter(transactions, direction);
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
@ -205,6 +222,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
exit(direction: string): void {
|
exit(direction: string): void {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
|
this.clearUpdateQueue();
|
||||||
this.scene.exit(direction);
|
this.scene.exit(direction);
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
@ -213,13 +231,67 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
|
|
||||||
replace(transactions: TransactionStripped[], direction: string, sort: boolean = true, startTime?: number): void {
|
replace(transactions: TransactionStripped[], direction: string, sort: boolean = true, startTime?: number): void {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
|
this.clearUpdateQueue();
|
||||||
this.scene.replace(transactions || [], direction, sort, startTime);
|
this.scene.replace(transactions || [], direction, sort, startTime);
|
||||||
this.start();
|
this.start();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collates deferred updates into a set of consistent pending changes
|
||||||
|
queueUpdate(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left'): void {
|
||||||
|
for (const tx of add) {
|
||||||
|
this.pendingUpdate.add[tx.txid] = tx;
|
||||||
|
delete this.pendingUpdate.remove[tx.txid];
|
||||||
|
delete this.pendingUpdate.change[tx.txid];
|
||||||
|
}
|
||||||
|
for (const txid of remove) {
|
||||||
|
delete this.pendingUpdate.add[txid];
|
||||||
|
this.pendingUpdate.remove[txid] = txid;
|
||||||
|
delete this.pendingUpdate.change[txid];
|
||||||
|
}
|
||||||
|
for (const tx of change) {
|
||||||
|
if (this.pendingUpdate.add[tx.txid]) {
|
||||||
|
this.pendingUpdate.add[tx.txid].rate = tx.rate;
|
||||||
|
this.pendingUpdate.add[tx.txid].acc = tx.acc;
|
||||||
|
} else {
|
||||||
|
this.pendingUpdate.change[tx.txid] = tx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.pendingUpdate.direction = direction;
|
||||||
|
this.pendingUpdate.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
deferredUpdate(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left'): void {
|
||||||
|
this.queueUpdate(add, remove, change, direction);
|
||||||
|
this.applyQueuedUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyQueuedUpdates(): void {
|
||||||
|
if (this.pendingUpdate.count && performance.now() > (this.lastUpdate + this.animationDuration)) {
|
||||||
|
this.applyUpdate(Object.values(this.pendingUpdate.add), Object.values(this.pendingUpdate.remove), Object.values(this.pendingUpdate.change), this.pendingUpdate.direction);
|
||||||
|
this.clearUpdateQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearUpdateQueue(): void {
|
||||||
|
this.pendingUpdate = {
|
||||||
|
count: 0,
|
||||||
|
add: {},
|
||||||
|
remove: {},
|
||||||
|
change: {},
|
||||||
|
};
|
||||||
|
this.lastUpdate = performance.now();
|
||||||
|
}
|
||||||
|
|
||||||
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
||||||
|
// merge any pending changes into this update
|
||||||
|
this.queueUpdate(add, remove, change);
|
||||||
|
this.applyUpdate(Object.values(this.pendingUpdate.add), Object.values(this.pendingUpdate.remove), Object.values(this.pendingUpdate.change), direction, resetLayout);
|
||||||
|
this.clearUpdateQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyUpdate(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
add = add.filter(tx => !this.scene.txs[tx.txid]);
|
add = add.filter(tx => !this.scene.txs[tx.txid]);
|
||||||
remove = remove.filter(txid => this.scene.txs[txid]);
|
remove = remove.filter(txid => this.scene.txs[txid]);
|
||||||
@ -230,6 +302,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
}
|
}
|
||||||
this.scene.update(add, remove, change, direction, resetLayout);
|
this.scene.update(add, remove, change, direction, resetLayout);
|
||||||
this.start();
|
this.start();
|
||||||
|
this.lastUpdate = performance.now();
|
||||||
this.updateSearchHighlight();
|
this.updateSearchHighlight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,6 +443,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
if (!now) {
|
if (!now) {
|
||||||
now = performance.now();
|
now = performance.now();
|
||||||
}
|
}
|
||||||
|
this.applyQueuedUpdates();
|
||||||
// skip re-render if there's no change to the scene
|
// skip re-render if there's no change to the scene
|
||||||
if (this.scene && this.gl) {
|
if (this.scene && this.gl) {
|
||||||
/* SET UP SHADER UNIFORMS */
|
/* SET UP SHADER UNIFORMS */
|
||||||
@ -577,13 +651,13 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
getFilterColorFunction(flags: bigint, gradient: 'fee' | 'age'): ((tx: TxView) => Color) {
|
||||||
return (tx: TxView) => {
|
return (tx: TxView) => {
|
||||||
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
if ((this.filterMode === 'and' && (tx.bigintFlags & flags) === flags) || (this.filterMode === 'or' && (flags === 0n || (tx.bigintFlags & flags) > 0n))) {
|
||||||
if (this.themeService.theme !== 'contrast') {
|
if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') {
|
||||||
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
return (gradient === 'age') ? ageColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000)) : defaultColorFunction(tx, defaultColors.fee, defaultAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
} else {
|
} else {
|
||||||
return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000));
|
return (gradient === 'age') ? ageColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000)) : contrastColorFunction(tx, contrastColors.fee, contrastAuditColors, this.relativeTime || (Date.now() / 1000));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.themeService.theme !== 'contrast') {
|
if (this.themeService.theme !== 'contrast' && this.themeService.theme !== 'bukele') {
|
||||||
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
return (gradient === 'age') ? { r: 1, g: 1, b: 1, a: 0.05 } : defaultColorFunction(
|
||||||
tx,
|
tx,
|
||||||
defaultColors.unmatchedfee,
|
defaultColors.unmatchedfee,
|
||||||
|
@ -13,7 +13,7 @@ export default class BlockScene {
|
|||||||
theme: ThemeService;
|
theme: ThemeService;
|
||||||
orientation: string;
|
orientation: string;
|
||||||
flip: boolean;
|
flip: boolean;
|
||||||
animationDuration: number = 900;
|
animationDuration: number = 1000;
|
||||||
configAnimationOffset: number | null;
|
configAnimationOffset: number | null;
|
||||||
animationOffset: number;
|
animationOffset: number;
|
||||||
highlightingEnabled: boolean;
|
highlightingEnabled: boolean;
|
||||||
@ -69,7 +69,7 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void {
|
||||||
this.theme.theme === 'contrast' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
this.theme.theme === 'contrast' || this.theme.theme === 'bukele' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
||||||
this.updateAllColors();
|
this.updateAllColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ export default class BlockScene {
|
|||||||
removed.forEach(tx => {
|
removed.forEach(tx => {
|
||||||
tx.destroy();
|
tx.destroy();
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, (startTime - performance.now()) + this.animationDuration + 1000);
|
||||||
|
|
||||||
if (resetLayout) {
|
if (resetLayout) {
|
||||||
add.forEach(tx => {
|
add.forEach(tx => {
|
||||||
@ -239,14 +239,14 @@ export default class BlockScene {
|
|||||||
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
{ width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number,
|
||||||
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null }
|
||||||
): void {
|
): void {
|
||||||
this.animationDuration = animationDuration || 1000;
|
this.animationDuration = animationDuration || this.animationDuration || 1000;
|
||||||
this.configAnimationOffset = animationOffset;
|
this.configAnimationOffset = animationOffset;
|
||||||
this.animationOffset = this.configAnimationOffset == null ? (this.width * 1.4) : this.configAnimationOffset;
|
this.animationOffset = this.configAnimationOffset == null ? (this.width * 1.4) : this.configAnimationOffset;
|
||||||
this.orientation = orientation;
|
this.orientation = orientation;
|
||||||
this.flip = flip;
|
this.flip = flip;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
this.highlightingEnabled = highlighting;
|
this.highlightingEnabled = highlighting;
|
||||||
theme.theme === 'contrast' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
theme.theme === 'contrast' || theme.theme === 'bukele' ? this.getColor = colorFunction || contrastColorFunction : this.getColor = colorFunction || defaultColorFunction;
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
|
|
||||||
this.scene = {
|
this.scene = {
|
||||||
|
@ -177,7 +177,7 @@ export function ageColorFunction(
|
|||||||
return auditColors.accelerated;
|
return auditColors.accelerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = theme !== 'contrast' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime);
|
const color = theme !== 'contrast' && theme !== 'bukele' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime);
|
||||||
|
|
||||||
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.block-overview-tooltip {
|
.block-overview-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: var(--tooltip-grey);
|
color: var(--tooltip-grey);
|
||||||
@ -30,7 +30,7 @@ th, td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.badge.badge-accelerated {
|
.badge.badge-accelerated {
|
||||||
background-color: var(--tertiary);
|
background-color: #653b9c;
|
||||||
box-shadow: #ad7de57f 0px 0px 12px -2px;
|
box-shadow: #ad7de57f 0px 0px 12px -2px;
|
||||||
color: white;
|
color: white;
|
||||||
animation: acceleratePulse 1s infinite;
|
animation: acceleratePulse 1s infinite;
|
||||||
@ -71,7 +71,7 @@ th, td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes acceleratePulse {
|
@keyframes acceleratePulse {
|
||||||
0% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
||||||
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
|
50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;}
|
||||||
100% { background-color: var(--tertiary); box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
100% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; }
|
||||||
}
|
}
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -136,7 +136,12 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
|||||||
return of(transactions);
|
return of(transactions);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([])
|
this.stateService.env.ACCELERATOR === true && block.height > 819500
|
||||||
|
? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height })
|
||||||
|
.pipe(catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
}))
|
||||||
|
: of([])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -345,7 +345,12 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
return of(null);
|
return of(null);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) : of([])
|
this.stateService.env.ACCELERATOR === true && block.height > 819500
|
||||||
|
? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height })
|
||||||
|
.pipe(catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
}))
|
||||||
|
: of([])
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
.fee-span {
|
.fee-span {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
color: #fff000;
|
color: var(--yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-count {
|
.transaction-count {
|
||||||
@ -130,7 +130,7 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-bottom: calc(var(--block-size) * 0.3) solid #FFF;
|
border-bottom: calc(var(--block-size) * 0.3) solid var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flashing {
|
.flashing {
|
||||||
|
@ -70,6 +70,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
||||||
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
||||||
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
|
testnet4: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
signet: ['var(--signet)', 'var(--signet-alt)'],
|
signet: ['var(--signet)', 'var(--signet-alt)'],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -349,7 +350,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return {
|
return {
|
||||||
left: addLeft + this.blockOffset * index + 'px',
|
left: addLeft + this.blockOffset * index + 'px',
|
||||||
background: `repeating-linear-gradient(
|
background: `repeating-linear-gradient(
|
||||||
#2d3348,
|
var(--secondary),
|
||||||
var(--secondary) ${greenBackgroundHeight}%,
|
var(--secondary) ${greenBackgroundHeight}%,
|
||||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||||
${this.gradientColors[this.network][1]} 100%
|
${this.gradientColors[this.network][1]} 100%
|
||||||
@ -361,7 +362,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
convertStyleForLoadingBlock(style) {
|
convertStyleForLoadingBlock(style) {
|
||||||
return {
|
return {
|
||||||
...style,
|
...style,
|
||||||
background: "#2d3348",
|
background: "var(--secondary)",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +371,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
left: addLeft + (this.blockOffset * index) + 'px',
|
left: addLeft + (this.blockOffset * index) + 'px',
|
||||||
background: "#2d3348",
|
background: "var(--secondary)",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-toggle {
|
.time-toggle {
|
||||||
color: white;
|
color: var(--fg);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -1.8em;
|
bottom: -1.8em;
|
||||||
@ -68,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.block-display-toggle {
|
.block-display-toggle {
|
||||||
color: white;
|
color: var(--fg);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 15.8em;
|
bottom: 15.8em;
|
||||||
|
@ -55,7 +55,7 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges {
|
|||||||
firstValueFrom(this.stateService.chainTip$).then(() => {
|
firstValueFrom(this.stateService.chainTip$).then(() => {
|
||||||
this.loadingTip = false;
|
this.loadingTip = false;
|
||||||
});
|
});
|
||||||
this.blockDisplayMode = this.StorageService.getValue('block-display-mode-preference') as 'size' | 'fees' || 'size';
|
this.blockDisplayMode = this.StorageService.getValue('block-display-mode-preference') as 'size' | 'fees' || 'fees';
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@ -32,11 +32,12 @@ export class ClockComponent implements OnInit {
|
|||||||
limitHeight: number;
|
limitHeight: number;
|
||||||
|
|
||||||
gradientColors = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0'],
|
'': ['var(--mainnet-alt)', 'var(--primary)'],
|
||||||
liquid: ['#116761', '#183550'],
|
liquid: ['var(--liquid)', 'var(--testnet-alt)'],
|
||||||
'liquidtestnet': ['#494a4a', '#272e46'],
|
'liquidtestnet': ['var(--liquidtestnet)', 'var(--liquidtestnet-alt)'],
|
||||||
testnet: ['#1d486f', '#183550'],
|
testnet: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
signet: ['#6f1d5d', '#471850'],
|
testnet4: ['var(--testnet)', 'var(--testnet-alt)'],
|
||||||
|
signet: ['var(--signet)', 'var(--signet-alt)'],
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -99,8 +100,8 @@ export class ClockComponent implements OnInit {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
background: `repeating-linear-gradient(
|
background: `repeating-linear-gradient(
|
||||||
#2d3348,
|
var(--secondary),
|
||||||
#2d3348 ${greenBackgroundHeight}%,
|
var(--secondary) ${greenBackgroundHeight}%,
|
||||||
${this.gradientColors[''][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
${this.gradientColors[''][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||||
${this.gradientColors[''][1]} 100%
|
${this.gradientColors[''][1]} 100%
|
||||||
)`,
|
)`,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@for (widget of widgets; track widget.component) {
|
@for (widget of widgets; track widget.component) {
|
||||||
@switch (widget.component) {
|
@switch (widget.component) {
|
||||||
@case ('fees') {
|
@case ('fees') {
|
||||||
<div class="col card-wrapper">
|
<div class="col card-wrapper" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
|
<div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body less-padding">
|
<div class="card-body less-padding">
|
||||||
@ -14,12 +14,12 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('difficulty') {
|
@case ('difficulty') {
|
||||||
<div class="col">
|
<div class="col" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<app-difficulty></app-difficulty>
|
<app-difficulty></app-difficulty>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('goggles') {
|
@case ('goggles') {
|
||||||
<div class="col">
|
<div class="col" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card graph-card">
|
<div class="card graph-card">
|
||||||
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
||||||
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
<a class="title-link mb-0" style="margin-top: -2px" href="" [routerLink]="['/mempool-block/0' | relativeUrl]">
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('incoming') {
|
@case ('incoming') {
|
||||||
<div class="col">
|
<div class="col" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card graph-card">
|
<div class="card graph-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
|
||||||
@ -93,7 +93,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('replacements') {
|
@case ('replacements') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
|
||||||
@ -140,7 +140,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('blocks') {
|
@case ('blocks') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
||||||
@ -184,7 +184,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('transactions') {
|
@case ('transactions') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title" i18n="dashboard.recent-transactions">Recent Transactions</h5>
|
<h5 class="card-title" i18n="dashboard.recent-transactions">Recent Transactions</h5>
|
||||||
@ -224,13 +224,13 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
}
|
||||||
@case ('balance') {
|
@case ('balance') {
|
||||||
<div class="col card-wrapper">
|
<div class="col card-wrapper" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="main-title" i18n="dashboard.treasury">Treasury</div>
|
<div class="main-title" i18n="dashboard.treasury">Treasury</div>
|
||||||
<app-balance-widget [address]="widget.props.address" [addressSummary$]="addressSummary$" [addressInfo]="address"></app-balance-widget>
|
<app-balance-widget [address]="widget.props.address" [addressSummary$]="addressSummary$" [addressInfo]="address"></app-balance-widget>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('address') {
|
@case ('address') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card graph-card">
|
<div class="card graph-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
||||||
@ -238,13 +238,13 @@
|
|||||||
<span> </span>
|
<span> </span>
|
||||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<app-address-graph [address]="widget.props.address" [addressSummary$]="addressSummary$" [period]="widget.props.period || 'all'" [stats]="address?.chain_stats" [widget]="true" [height]="graphHeight"></app-address-graph>
|
<app-address-graph [address]="widget.props.address" [addressSummary$]="addressSummary$" [period]="widget.props.period || 'all'" [stats]="address ? address.chain_stats : null" [widget]="true" [height]="graphHeight"></app-address-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case ('addressTransactions') {
|
@case ('addressTransactions') {
|
||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="[('/address/' + widget.props.address) | relativeUrl]">
|
||||||
@ -257,6 +257,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@case ('twitter') {
|
||||||
|
<div class="col" style="min-height:410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||||
|
<div class="card graph-card">
|
||||||
|
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2 d-flex flex-column">
|
||||||
|
<a class="title-link" [href]="'https://x.com/' + widget.props?.handle" target="_blank">
|
||||||
|
<h5 class="card-title d-inline" i18n="dashboard.x-timeline">X Timeline</h5>
|
||||||
|
<span> </span>
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
|
</a>
|
||||||
|
@defer {
|
||||||
|
<app-twitter-widget [handle]="widget.props?.handle" style="flex-grow: 1"></app-twitter-widget>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { combineLatest, merge, Observable, of, Subject, Subscription } from 'rxjs';
|
import { combineLatest, merge, Observable, of, Subject, Subscription } from 'rxjs';
|
||||||
import { catchError, filter, map, scan, share, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
|
import { catchError, filter, map, scan, share, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { BlockExtended, OptimizedMempoolStats, TransactionStripped } from '../../interfaces/node-api.interface';
|
import { BlockExtended, OptimizedMempoolStats, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
@ -57,6 +57,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
incomingGraphHeight: number = 300;
|
incomingGraphHeight: number = 300;
|
||||||
graphHeight: number = 300;
|
graphHeight: number = 300;
|
||||||
webGlEnabled = true;
|
webGlEnabled = true;
|
||||||
|
isMobile: boolean = window.innerWidth <= 767.98;
|
||||||
|
|
||||||
widgets;
|
widgets;
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
@Inject(PLATFORM_ID) private platformId: Object,
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
@ -230,8 +232,10 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
this.stateService.live2Chart$
|
this.stateService.live2Chart$
|
||||||
.pipe(
|
.pipe(
|
||||||
scan((acc, stats) => {
|
scan((acc, stats) => {
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
const start = now - (2 * 60 * 60);
|
||||||
acc.unshift(stats);
|
acc.unshift(stats);
|
||||||
acc = acc.slice(0, 120);
|
acc = acc.filter(p => p.added >= start);
|
||||||
return acc;
|
return acc;
|
||||||
}, (mempoolStats || []))
|
}, (mempoolStats || []))
|
||||||
),
|
),
|
||||||
@ -283,8 +287,8 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
|
|
||||||
startAddressSubscription(): void {
|
startAddressSubscription(): void {
|
||||||
if (this.stateService.env.customize && this.stateService.env.customize.dashboard.widgets.some(w => w.props?.address)) {
|
if (this.stateService.env.customize && this.stateService.env.customize.dashboard.widgets.some(w => w.props?.address)) {
|
||||||
const address = this.stateService.env.customize.dashboard.widgets.find(w => w.props?.address).props.address;
|
let addressString = this.stateService.env.customize.dashboard.widgets.find(w => w.props?.address).props.address;
|
||||||
const addressString = (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(address)) ? address.toLowerCase() : address;
|
addressString = (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(addressString)) ? addressString.toLowerCase() : addressString;
|
||||||
|
|
||||||
this.addressSubscription = (
|
this.addressSubscription = (
|
||||||
addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
|
addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
|
||||||
@ -299,6 +303,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
).subscribe((address: Address) => {
|
).subscribe((address: Address) => {
|
||||||
this.websocketService.startTrackAddress(address.address);
|
this.websocketService.startTrackAddress(address.address);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addressSummary$ = (
|
this.addressSummary$ = (
|
||||||
@ -368,5 +373,6 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
|
|||||||
this.goggleResolution = 86;
|
this.goggleResolution = 86;
|
||||||
this.graphHeight = 310;
|
this.graphHeight = 310;
|
||||||
}
|
}
|
||||||
|
this.isMobile = window.innerWidth <= 767.98;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
.difficulty-tooltip {
|
.difficulty-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="diff-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
<linearGradient id="diff-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0%" stop-color="#105fb0" />
|
<stop offset="0%" stop-color="var(--primary)" />
|
||||||
<stop offset="100%" stop-color="#9339f4" />
|
<stop offset="100%" stop-color="var(--mainnet-alt)" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="diff-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
<linearGradient id="diff-hover-gradient" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0%" stop-color="#2486eb" />
|
<stop offset="0%" stop-color="#2486eb" />
|
||||||
|
@ -128,7 +128,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -223,7 +224,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.background {
|
.background {
|
||||||
background: linear-gradient(to right, var(--primary), #9339f4);
|
background: linear-gradient(to right, var(--primary), var(--mainnet-alt));
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
transition: background-color 1s;
|
transition: background-color 1s;
|
||||||
color: var(--color-fg);
|
color: #fff;
|
||||||
&.priority {
|
&.priority {
|
||||||
@media (767px < width < 992px), (width < 576px) {
|
@media (767px < width < 992px), (width < 576px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -16,8 +16,8 @@ export class FeesBoxComponent implements OnInit, OnDestroy {
|
|||||||
isLoading$: Observable<boolean>;
|
isLoading$: Observable<boolean>;
|
||||||
recommendedFees$: Observable<Recommendedfees>;
|
recommendedFees$: Observable<Recommendedfees>;
|
||||||
themeSubscription: Subscription;
|
themeSubscription: Subscription;
|
||||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
gradient = 'linear-gradient(to right, var(--skeleton-bg), var(--skeleton-bg))';
|
||||||
noPriority = '#2e324e';
|
noPriority = 'var(--skeleton-bg)';
|
||||||
fees: Recommendedfees;
|
fees: Recommendedfees;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -66,7 +66,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||||||
if (!this.data) {
|
if (!this.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
this.windowPreference = (this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference')) || '2h';
|
||||||
const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8));
|
const windowSize = Math.max(10, Math.floor(this.data.series[0].length / 8));
|
||||||
this.MA = this.calculateMA(this.data.series[0], windowSize);
|
this.MA = this.calculateMA(this.data.series[0], windowSize);
|
||||||
if (this.outlierCappingEnabled === true) {
|
if (this.outlierCappingEnabled === true) {
|
||||||
@ -216,22 +216,19 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||||||
type: 'line',
|
type: 'line',
|
||||||
},
|
},
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue);
|
const bestItem = params.reduce((best, item) => {
|
||||||
|
return (item.seriesName === 'data' && (!best || best.value[1] < item.value[1])) ? item : best;
|
||||||
|
}, null);
|
||||||
|
const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, bestItem.axisValue);
|
||||||
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
|
const colorSpan = (color: string) => `<span class="indicator" style="background-color: ` + color + `"></span>`;
|
||||||
let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
|
let itemFormatted = '<div class="title">' + axisValueLabel + '</div>';
|
||||||
params.map((item: any, index: number) => {
|
if (bestItem) {
|
||||||
|
itemFormatted += `<div class="item">
|
||||||
//Do no include MA in tooltip legend!
|
<div class="indicator-container">${colorSpan(bestItem.color)}</div>
|
||||||
if (item.seriesName !== 'MA') {
|
|
||||||
if (index < 26) {
|
|
||||||
itemFormatted += `<div class="item">
|
|
||||||
<div class="indicator-container">${colorSpan(item.color)}</div>
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
<div class="value">${formatNumber(item.value[1], this.locale, '1.0-0')}<span class="symbol">vB/s</span></div>
|
<div class="value">${formatNumber(bestItem.value[1], this.locale, '1.0-0')}<span class="symbol">vB/s</span></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}"
|
return `<div class="tx-wrapper-tooltip-chart ${(this.template === 'advanced') ? 'tx-wrapper-tooltip-chart-advanced' : ''}"
|
||||||
style="width: ${(this.windowPreference === '2h' || this.template === 'widget') ? '125px' : '215px'}">${itemFormatted}</div>`;
|
style="width: ${(this.windowPreference === '2h' || this.template === 'widget') ? '125px' : '215px'}">${itemFormatted}</div>`;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@
|
|||||||
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
|
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
|
||||||
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['mainnet'] || '')" ngbDropdownItem class="mainnet"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
|
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['mainnet'] || '')" ngbDropdownItem class="mainnet"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
|
||||||
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['signet'] || '/signet')" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
|
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['signet'] || '/signet')" ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
|
||||||
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['testnet'] || '/testnet')" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet</a>
|
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['testnet'] || '/testnet')" ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
|
||||||
|
<a [href]="env.MEMPOOL_WEBSITE_URL + urlLanguage + (networkPaths['testnet4'] || '/testnet4')" ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4 <span class="badge badge-pill badge-warning beta-network" i18n="beta">beta</span></a>
|
||||||
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
|
<h6 class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
|
||||||
<a ngbDropdownItem class="liquid mr-1" [class.active]="network.val === 'liquid'" [routerLink]="networkPaths['liquid'] || '/'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
|
<a ngbDropdownItem class="liquid mr-1" [class.active]="network.val === 'liquid'" [routerLink]="networkPaths['liquid'] || '/'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
|
||||||
<a ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquidtestnet'" [routerLink]="networkPaths['liquidtestnet'] || '/testnet'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
|
<a ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquidtestnet'" [routerLink]="networkPaths['liquidtestnet'] || '/testnet'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-item.active {
|
li.nav-item.active {
|
||||||
@ -17,7 +18,7 @@ fa-icon {
|
|||||||
.navbar {
|
.navbar {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
background-color: var(--bg);
|
background-color: var(--nav-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-item {
|
li.nav-item {
|
||||||
@ -48,7 +49,7 @@ li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
background: var(--navbar-bg);
|
background: var(--nav-bg);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0px 0px 15px 0px #000;
|
box-shadow: 0px 0px 15px 0px #000;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -169,4 +170,8 @@ nav {
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.beta-network {
|
||||||
|
font-size: 8px;
|
||||||
}
|
}
|
@ -6,7 +6,7 @@
|
|||||||
<img [src]="enterpriseInfo.img" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
<img [src]="enterpriseInfo.img" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
}
|
}
|
||||||
@if (enterpriseInfo?.header_img) {
|
@if (enterpriseInfo?.header_img) {
|
||||||
<img *ngIf="enterpriseInfo.header_img" [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="36px">
|
<img *ngIf="enterpriseInfo.header_img" [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="60px" class="mr-3">
|
||||||
} @else {
|
} @else {
|
||||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" width="500" height="126" class="mempool-logo" style="width: 200px; height: 50px"></app-svg-images>
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" width="500" height="126" class="mempool-logo" style="width: 200px; height: 50px"></app-svg-images>
|
||||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" style="width: 200px; height: 50px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" style="width: 200px; height: 50px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
||||||
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
<div [ngSwitch]="network.val">
|
<div [ngSwitch]="network.val">
|
||||||
<span *ngSwitchCase="'signet'" class="network signet"><app-svg-images name="signet" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Signet</span>
|
<span *ngSwitchCase="'signet'" class="network signet"><app-svg-images name="signet" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Signet</span>
|
||||||
<span *ngSwitchCase="'testnet'" class="network testnet"><app-svg-images name="testnet" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Testnet</span>
|
<span *ngSwitchCase="'testnet'" class="network testnet"><app-svg-images name="testnet" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Testnet3</span>
|
||||||
|
<span *ngSwitchCase="'testnet4'" class="network testnet"><app-svg-images name="testnet4" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Testnet4</span>
|
||||||
<span *ngSwitchCase="'liquid'" class="network liquid"><app-svg-images name="liquid" width="35" height="35" viewBox="0 0 125 125" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Mainnet</span>
|
<span *ngSwitchCase="'liquid'" class="network liquid"><app-svg-images name="liquid" width="35" height="35" viewBox="0 0 125 125" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Mainnet</span>
|
||||||
<span *ngSwitchCase="'liquidtestnet'" class="network liquidtestnet"><app-svg-images name="liquidtestnet" width="35" height="35" viewBox="0 0 125 125" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Testnet</span>
|
<span *ngSwitchCase="'liquidtestnet'" class="network liquidtestnet"><app-svg-images name="liquidtestnet" width="35" height="35" viewBox="0 0 125 125" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Testnet</span>
|
||||||
<span *ngSwitchDefault class="network mainnet"><app-svg-images name="bitcoin" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Mainnet</span>
|
<span *ngSwitchDefault class="network mainnet"><app-svg-images name="bitcoin" width="35" height="35" viewBox="0 0 65 65" style="width: 40px; height: 48px;" class="mainnet mr-1"></app-svg-images> Mainnet</span>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
padding-top: 80px;
|
padding-top: 80px;
|
||||||
|
background: var(--nav-bg);
|
||||||
|
|
||||||
header {
|
header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -18,7 +19,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--stat-box-bg);
|
background: var(--nav-bg);
|
||||||
text-align: start;
|
text-align: start;
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
<!-- Large screen -->
|
<!-- Large screen -->
|
||||||
<a class="navbar-brand d-none d-md-flex" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
<a class="navbar-brand d-none d-md-flex" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
|
||||||
<div class="subdomain_container">
|
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
|
||||||
</div>
|
|
||||||
<div class="vertical-line"></div>
|
|
||||||
</ng-template>
|
|
||||||
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
||||||
@if (enterpriseInfo?.header_img) {
|
@if (enterpriseInfo?.header_img) {
|
||||||
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="36px">
|
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="48px" class="mr-3">
|
||||||
} @else {
|
} @else {
|
||||||
|
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||||
|
<div class="subdomain_container">
|
||||||
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
|
</div>
|
||||||
|
<div class="vertical-line"></div>
|
||||||
|
</ng-template>
|
||||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
||||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
||||||
}
|
}
|
||||||
@ -38,34 +38,39 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Mobile -->
|
<!-- Mobile -->
|
||||||
<a class="navbar-brand d-flex d-md-none justify-content-center" [ngClass]="{'dual-logos': subdomain, 'mr-0': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
<a class="navbar-brand d-flex d-md-none justify-content-center" [ngClass]="{'dual-logos': subdomain, 'mr-0': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||||
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
@if (enterpriseInfo?.header_img) {
|
||||||
<div class="subdomain_container">
|
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="42px">
|
||||||
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
} @else {
|
||||||
</div>
|
<ng-template [ngIf]="subdomain && enterpriseInfo">
|
||||||
<div class="vertical-line"></div>
|
<div class="subdomain_container">
|
||||||
</ng-template>
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + subdomain + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
</div>
|
||||||
@if (enterpriseInfo?.header_img) {
|
<div class="vertical-line"></div>
|
||||||
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="36px">
|
</ng-template>
|
||||||
} @else {
|
<ng-container *ngIf="{ val: connectionState$ | async } as connectionState">
|
||||||
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
@if (enterpriseInfo?.header_img) {
|
||||||
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
<img [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="36px">
|
||||||
}
|
} @else {
|
||||||
<div class="connection-badge">
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" viewBox="0 0 500 126" class="mempool-logo" [ngStyle]="{'opacity': connectionState.val === 2 ? 1 : 0.5 }"></app-svg-images>
|
||||||
<div class="badge badge-warning" *ngIf="connectionState.val === 0" i18n="master-page.offline">Offline</div>
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" viewBox="0 0 500 126"></app-svg-images>
|
||||||
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
|
}
|
||||||
</div>
|
<div class="connection-badge">
|
||||||
</ng-container>
|
<div class="badge badge-warning" *ngIf="connectionState.val === 0" i18n="master-page.offline">Offline</div>
|
||||||
|
<div class="badge badge-warning" *ngIf="connectionState.val === 1" i18n="master-page.reconnecting">Reconnecting...</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.TESTNET4_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
|
||||||
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split d-flex justify-content-center align-items-center" aria-haspopup="true">
|
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split d-flex justify-content-center align-items-center" aria-haspopup="true">
|
||||||
<app-svg-images class="d-flex justify-content-center align-items-center current-network-svg" [name]="network.val === '' ? 'bitcoin' : network.val" width="20" height="20" viewBox="0 0 65 65"></app-svg-images>
|
<app-svg-images class="d-flex justify-content-center align-items-center current-network-svg" [name]="network.val === '' ? 'bitcoin' : network.val" width="20" height="20" viewBox="0 0 65 65"></app-svg-images>
|
||||||
</button>
|
</button>
|
||||||
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
|
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
|
||||||
<a ngbDropdownItem class="mainnet" [routerLink]="networkPaths['mainnet'] || '/'"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
|
<a ngbDropdownItem class="mainnet" [routerLink]="networkPaths['mainnet'] || '/'"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
|
||||||
<a ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" [routerLink]="networkPaths['signet'] || '/signet'"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
|
<a ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" [routerLink]="networkPaths['signet'] || '/signet'"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
|
||||||
<a ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" [routerLink]="networkPaths['testnet'] || '/testnet'"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet</a>
|
<a ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" [routerLink]="networkPaths['testnet'] || '/testnet'"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
|
||||||
|
<a ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet" [class.active]="network.val === 'testnet4'" [routerLink]="networkPaths['testnet4'] || '/testnet4'"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4 <span class="badge badge-pill badge-warning beta-network" i18n="beta">beta</span></a>
|
||||||
<h6 *ngIf="env.LIQUID_ENABLED" class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
|
<h6 *ngIf="env.LIQUID_ENABLED" class="dropdown-header" i18n="master-page.layer2-networks-header">Layer 2 Networks</h6>
|
||||||
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquid'] || '')" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
|
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquid'] || '')" ngbDropdownItem *ngIf="env.LIQUID_ENABLED" class="liquid" [class.active]="network.val === 'liquid'"><app-svg-images name="liquid" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid</a>
|
||||||
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquidtestnet'] || '/testnet')" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquid'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
|
<a [href]="env.LIQUID_WEBSITE_URL + urlLanguage + (networkPaths['liquidtestnet'] || '/testnet')" ngbDropdownItem *ngIf="env.LIQUID_TESTNET_ENABLED" class="liquidtestnet" [class.active]="network.val === 'liquid'"><app-svg-images name="liquidtestnet" width="22" height="22" viewBox="0 0 125 125" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Liquid Testnet</a>
|
||||||
@ -87,7 +92,7 @@
|
|||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">
|
||||||
<a class="nav-link" [routerLink]="['/mining' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="mining.mining-dashboard" title="Mining Dashboard"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/mining' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="mining.mining-dashboard" title="Mining Dashboard"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-lightning" *ngIf="stateService.env.LIGHTNING">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-lightning" *ngIf="stateService.env.LIGHTNING && lightningNetworks.includes(stateService.network)">
|
||||||
<a class="nav-link" [routerLink]="['/lightning' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'bolt']" [fixedWidth]="true" i18n-title="master-page.lightning" title="Lightning Explorer"></fa-icon>
|
<a class="nav-link" [routerLink]="['/lightning' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'bolt']" [fixedWidth]="true" i18n-title="master-page.lightning" title="Lightning Explorer"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -114,7 +119,7 @@
|
|||||||
<div class="empty-sidenav"><!-- empty sidenav needed to push footer down the screen --></div>
|
<div class="empty-sidenav"><!-- empty sidenav needed to push footer down the screen --></div>
|
||||||
|
|
||||||
<div class="flex-grow-1 d-flex flex-column">
|
<div class="flex-grow-1 d-flex flex-column">
|
||||||
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
|
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'testnet4' || network.val === 'signet'"></app-testnet-alert>
|
||||||
|
|
||||||
<main style="min-width: 375px; max-width: 100vw">
|
<main style="min-width: 375px; max-width: 100vw">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background-color: var(--bg);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ fa-icon {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--bg);
|
background-color: var(--nav-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.nav-item {
|
li.nav-item {
|
||||||
@ -59,7 +60,7 @@ li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav {
|
.navbar-nav {
|
||||||
background: var(--navbar-bg);
|
background: var(--nav-bg);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
box-shadow: 0px 0px 15px 0px #000;
|
box-shadow: 0px 0px 15px 0px #000;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -243,6 +244,10 @@ nav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.beta-network {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.current-network-svg {
|
.current-network-svg {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
@ -27,6 +27,7 @@ export class MasterPageComponent implements OnInit, OnDestroy {
|
|||||||
subdomain = '';
|
subdomain = '';
|
||||||
networkPaths: { [network: string]: string };
|
networkPaths: { [network: string]: string };
|
||||||
networkPaths$: Observable<Record<string, string>>;
|
networkPaths$: Observable<Record<string, string>>;
|
||||||
|
lightningNetworks = ['', 'mainnet', 'bitcoin', 'testnet', 'signet'];
|
||||||
footerVisible = true;
|
footerVisible = true;
|
||||||
user: any = undefined;
|
user: any = undefined;
|
||||||
servicesEnabled = false;
|
servicesEnabled = false;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventEmitter,
|
import { Component, ViewChild, Input, Output, EventEmitter,
|
||||||
OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { MempoolBlockDelta } from '../../interfaces/websocket.interface';
|
import { MempoolBlockDelta, isMempoolDelta } from '../../interfaces/websocket.interface';
|
||||||
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||||
import { Subscription, BehaviorSubject, merge, of, timer } from 'rxjs';
|
import { Subscription, BehaviorSubject } from 'rxjs';
|
||||||
import { switchMap, filter, concatMap, map } from 'rxjs/operators';
|
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@ -39,10 +38,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
poolDirection: string = 'left';
|
poolDirection: string = 'left';
|
||||||
|
|
||||||
blockSub: Subscription;
|
blockSub: Subscription;
|
||||||
rateLimit = 1000;
|
|
||||||
private lastEventTime = Date.now() - this.rateLimit;
|
|
||||||
private subId = 0;
|
|
||||||
|
|
||||||
firstLoad: boolean = true;
|
firstLoad: boolean = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -62,39 +57,13 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.blockSub = merge(
|
this.blockSub = this.stateService.mempoolBlockUpdate$.subscribe((update) => {
|
||||||
this.stateService.mempoolBlockTransactions$,
|
|
||||||
this.stateService.mempoolBlockDelta$,
|
|
||||||
).pipe(
|
|
||||||
concatMap(update => {
|
|
||||||
const now = Date.now();
|
|
||||||
const timeSinceLastEvent = now - this.lastEventTime;
|
|
||||||
this.lastEventTime = Math.max(now, this.lastEventTime + this.rateLimit);
|
|
||||||
|
|
||||||
const subId = this.subId;
|
|
||||||
|
|
||||||
// If time since last event is less than X seconds, delay this event
|
|
||||||
if (timeSinceLastEvent < this.rateLimit) {
|
|
||||||
return timer(this.rateLimit - timeSinceLastEvent).pipe(
|
|
||||||
// Emit the event after the timer
|
|
||||||
map(() => ({ update, subId }))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If enough time has passed, emit the event immediately
|
|
||||||
return of({ update, subId });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).subscribe(({ update, subId }) => {
|
|
||||||
// discard stale updates after a block transition
|
|
||||||
if (subId !== this.subId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// process update
|
// process update
|
||||||
if (update['added']) {
|
if (isMempoolDelta(update)) {
|
||||||
// delta
|
// delta
|
||||||
this.updateBlock(update as MempoolBlockDelta);
|
this.updateBlock(update);
|
||||||
} else {
|
} else {
|
||||||
const transactionsStripped = update as TransactionStripped[];
|
const transactionsStripped = update.transactions;
|
||||||
// new transactions
|
// new transactions
|
||||||
if (this.firstLoad) {
|
if (this.firstLoad) {
|
||||||
this.replaceBlock(transactionsStripped);
|
this.replaceBlock(transactionsStripped);
|
||||||
@ -137,7 +106,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
|
|
||||||
ngOnChanges(changes): void {
|
ngOnChanges(changes): void {
|
||||||
if (changes.index) {
|
if (changes.index) {
|
||||||
this.subId++;
|
|
||||||
this.firstLoad = true;
|
this.firstLoad = true;
|
||||||
if (this.blockGraph) {
|
if (this.blockGraph) {
|
||||||
this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
|
this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
|
||||||
@ -173,7 +141,11 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
|||||||
const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection;
|
const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection;
|
||||||
this.blockGraph.replace(delta.added, direction);
|
this.blockGraph.replace(delta.added, direction);
|
||||||
} else {
|
} else {
|
||||||
this.blockGraph.update(delta.added, delta.removed, delta.changed || [], blockMined ? this.chainDirection : this.poolDirection, blockMined);
|
if (blockMined) {
|
||||||
|
this.blockGraph.update(delta.added, delta.removed, delta.changed || [], blockMined ? this.chainDirection : this.poolDirection, blockMined);
|
||||||
|
} else {
|
||||||
|
this.blockGraph.deferredUpdate(delta.added, delta.removed, delta.changed || [], this.poolDirection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastBlockHeight = this.stateService.latestBlockHeight;
|
this.lastBlockHeight = this.stateService.latestBlockHeight;
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
.fee-span {
|
.fee-span {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
color: #fff000;
|
color: var(--yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-count {
|
.transaction-count {
|
||||||
@ -119,7 +119,7 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
border-left: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
border-right: calc(var(--block-size) * 0.3) solid transparent;
|
||||||
border-bottom: calc(var(--block-size) * 0.3) solid #FFF;
|
border-bottom: calc(var(--block-size) * 0.3) solid var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockLink {
|
.blockLink {
|
||||||
|
@ -77,7 +77,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
this.isWidget = this.template === 'widget';
|
this.isWidget = this.template === 'widget';
|
||||||
this.showCount = !this.isWidget && !this.hideCount;
|
this.showCount = !this.isWidget && !this.hideCount;
|
||||||
this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference');
|
this.windowPreference = (this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference')) || '2h';
|
||||||
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([]));
|
||||||
this.mountFeeChart();
|
this.mountFeeChart();
|
||||||
}
|
}
|
||||||
@ -256,11 +256,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
const itemFormatted = [];
|
const itemFormatted = [];
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
let progressPercentageText = '';
|
let progressPercentageText = '';
|
||||||
let countItem;
|
const unfilteredItems = this.inverted ? [...params].reverse() : params;
|
||||||
let items = this.inverted ? [...params].reverse() : params;
|
const countItem = unfilteredItems.find(p => p.seriesName === 'count');
|
||||||
if (items[items.length - 1].seriesName === 'count') {
|
const usedSeries = {};
|
||||||
countItem = items.pop();
|
const items = unfilteredItems.filter(p => {
|
||||||
}
|
if (usedSeries[p.seriesName] || p.seriesName === 'count') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
usedSeries[p.seriesName] = true;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
items.map((item: any, index: number) => {
|
items.map((item: any, index: number) => {
|
||||||
sum += item.value[1];
|
sum += item.value[1];
|
||||||
const progressPercentage = (item.value[1] / totalValue) * 100;
|
const progressPercentage = (item.value[1] / totalValue) * 100;
|
||||||
|
@ -63,7 +63,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
.rbf-tooltip {
|
.rbf-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: #b1b1b1;
|
color: var(--tooltip-grey);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -159,7 +159,7 @@
|
|||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
.shape-border {
|
.shape-border {
|
||||||
background: #9339f4;
|
background: var(--mainnet-alt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,7 @@
|
|||||||
width: calc(1em + 16px);
|
width: calc(1em + 16px);
|
||||||
|
|
||||||
.shape {
|
.shape {
|
||||||
border: solid 4px #9339f4;
|
border: solid 4px var(--mainnet-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -179,7 +179,7 @@ export class SearchFormComponent implements OnInit {
|
|||||||
const lightningResults = result[1];
|
const lightningResults = result[1];
|
||||||
|
|
||||||
// Do not show date and timestamp results for liquid
|
// Do not show date and timestamp results for liquid
|
||||||
const isNetworkBitcoin = this.network === '' || this.network === 'testnet' || this.network === 'signet';
|
const isNetworkBitcoin = this.network === '' || this.network === 'testnet' || this.network === 'testnet4' || this.network === 'signet';
|
||||||
|
|
||||||
const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight;
|
const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight;
|
||||||
const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date' && new Date(searchText).getTime() <= Date.now() && isNetworkBitcoin;
|
const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date' && new Date(searchText).getTime() <= Date.now() && isNetworkBitcoin;
|
||||||
|
@ -60,6 +60,9 @@
|
|||||||
<ng-container *ngSwitchCase="'testnet'">
|
<ng-container *ngSwitchCase="'testnet'">
|
||||||
<ng-component *ngTemplateOutlet="bitcoinLogo; context: {$implicit: '#5fd15c', width, height, viewBox}"></ng-component>
|
<ng-component *ngTemplateOutlet="bitcoinLogo; context: {$implicit: '#5fd15c', width, height, viewBox}"></ng-component>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'testnet4'">
|
||||||
|
<ng-component *ngTemplateOutlet="bitcoinLogo; context: {$implicit: '#5fd15c', width, height, viewBox}"></ng-component>
|
||||||
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="'liquid'">
|
<ng-container *ngSwitchCase="'liquid'">
|
||||||
<ng-component *ngTemplateOutlet="liquidLogo; context: {$implicit: '', width, height, viewBox, color1: '#2cccbf', color2: '#9ef2ed'}"></ng-component>
|
<ng-component *ngTemplateOutlet="liquidLogo; context: {$implicit: '', width, height, viewBox, color1: '#2cccbf', color2: '#9ef2ed'}"></ng-component>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -71,7 +71,9 @@ export class TelevisionComponent implements OnInit, OnDestroy {
|
|||||||
mempoolStats = newStats;
|
mempoolStats = newStats;
|
||||||
} else if (['2h', '24h'].includes(this.fragment)) {
|
} else if (['2h', '24h'].includes(this.fragment)) {
|
||||||
mempoolStats.unshift(newStats[0]);
|
mempoolStats.unshift(newStats[0]);
|
||||||
mempoolStats = mempoolStats.slice(0, mempoolStats.length - 1);
|
const now = Date.now() / 1000;
|
||||||
|
const start = now - (this.fragment === '2h' ? (2 * 60 * 60) : (24 * 60 * 60) );
|
||||||
|
mempoolStats = mempoolStats.filter(p => p.added >= start);
|
||||||
}
|
}
|
||||||
return mempoolStats;
|
return mempoolStats;
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
|
|
||||||
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
<p *ngIf="officialMempoolSpace">The <a href="https://mempool.space/">mempool.space</a> website, the <a href="https://liquid.network/">liquid.network</a> website, the <a href="https://bitcoin.gob.sv/">bitcoin.gob.sv</a> website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from <a href="https://bgp.tools/as/142052#connectivity">AS142052</a>.</p>
|
||||||
|
|
||||||
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
<p *ngIf="!officialMempoolSpace">This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.</p>
|
||||||
|
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
<div class="container-xl">
|
||||||
|
<h1 class="text-left" i18n="shared.test-transactions|Test Transactions">Test Transactions</h1>
|
||||||
|
|
||||||
|
<form [formGroup]="testTxsForm" (submit)="testTxsForm.valid && testTxs()" novalidate>
|
||||||
|
<label for="maxfeerate" i18n="test.tx.raw-hex">Raw hex</label>
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea formControlName="txs" class="form-control" rows="5" i18n-placeholder="transaction.test-transactions" placeholder="Comma-separated list of raw transactions"></textarea>
|
||||||
|
</div>
|
||||||
|
<label for="maxfeerate" i18n="test.tx.max-fee-rate">Maximum fee rate (sat/vB)</label>
|
||||||
|
<input type="number" class="form-control input-dark" formControlName="maxfeerate" id="maxfeerate"
|
||||||
|
[value]="10000" placeholder="10,000 s/vb" [class]="{invalid: invalidMaxfeerate}">
|
||||||
|
<br>
|
||||||
|
<button [disabled]="isLoading" type="submit" class="btn btn-primary mr-2" i18n="shared.test-transactions|Test Transactions">Test Transactions</button>
|
||||||
|
<p class="red-color d-inline">{{ error }}</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="box" *ngIf="results?.length">
|
||||||
|
<table class="accept-results table table-fixed table-borderless table-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="allowed" i18n="test-tx.is-allowed">Allowed?</th>
|
||||||
|
<th class="txid" i18n="dashboard.latest-transactions.txid">TXID</th>
|
||||||
|
<th class="rate" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</th>
|
||||||
|
<th class="reason" i18n="test-tx.rejection-reason">Rejection reason</th>
|
||||||
|
</tr>
|
||||||
|
<ng-container *ngFor="let result of results;">
|
||||||
|
<tr>
|
||||||
|
<td class="allowed">
|
||||||
|
<ng-container [ngSwitch]="result.allowed">
|
||||||
|
<span *ngSwitchCase="true">✅</span>
|
||||||
|
<span *ngSwitchCase="false">❌</span>
|
||||||
|
<span *ngSwitchDefault>-</span>
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
|
<td class="txid">
|
||||||
|
<app-truncate [text]="result.txid || '-'"></app-truncate>
|
||||||
|
</td>
|
||||||
|
<td class="rate">
|
||||||
|
<app-fee-rate *ngIf="result.fees?.['effective-feerate'] != null" [fee]="result.fees?.['effective-feerate'] * 100000"></app-fee-rate>
|
||||||
|
<span *ngIf="result.fees?.['effective-feerate'] == null">-</span>
|
||||||
|
</td>
|
||||||
|
<td class="reason">
|
||||||
|
{{ result['reject-reason'] || '-' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,34 @@
|
|||||||
|
.accept-results {
|
||||||
|
td, th {
|
||||||
|
&.allowed {
|
||||||
|
width: 10%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&.txid {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
&.rate {
|
||||||
|
width: 20%;
|
||||||
|
text-align: right;
|
||||||
|
white-space: wrap;
|
||||||
|
}
|
||||||
|
&.reason {
|
||||||
|
width: 20%;
|
||||||
|
text-align: right;
|
||||||
|
white-space: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 950px) {
|
||||||
|
table-layout: auto;
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
&.allowed {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
&.txid {
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { SeoService } from '../../services/seo.service';
|
||||||
|
import { OpenGraphService } from '../../services/opengraph.service';
|
||||||
|
import { TestMempoolAcceptResult } from '../../interfaces/node-api.interface';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-test-transactions',
|
||||||
|
templateUrl: './test-transactions.component.html',
|
||||||
|
styleUrls: ['./test-transactions.component.scss']
|
||||||
|
})
|
||||||
|
export class TestTransactionsComponent implements OnInit {
|
||||||
|
testTxsForm: UntypedFormGroup;
|
||||||
|
error: string = '';
|
||||||
|
results: TestMempoolAcceptResult[] = [];
|
||||||
|
isLoading = false;
|
||||||
|
invalidMaxfeerate = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private formBuilder: UntypedFormBuilder,
|
||||||
|
private apiService: ApiService,
|
||||||
|
public stateService: StateService,
|
||||||
|
private seoService: SeoService,
|
||||||
|
private ogService: OpenGraphService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.testTxsForm = this.formBuilder.group({
|
||||||
|
txs: ['', Validators.required],
|
||||||
|
maxfeerate: ['', Validators.min(0)]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.seoService.setTitle($localize`:@@meta.title.test-txs:Test Transactions`);
|
||||||
|
this.ogService.setManualOgImage('tx-push.jpg');
|
||||||
|
}
|
||||||
|
|
||||||
|
testTxs() {
|
||||||
|
let txs: string[] = [];
|
||||||
|
try {
|
||||||
|
txs = (this.testTxsForm.get('txs')?.value as string).split(',').map(hex => hex.trim());
|
||||||
|
if (!txs?.length) {
|
||||||
|
this.error = 'At least one transaction is required';
|
||||||
|
return;
|
||||||
|
} else if (txs.length > 25) {
|
||||||
|
this.error = 'Exceeded maximum of 25 transactions';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.error = e?.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxfeerate;
|
||||||
|
this.invalidMaxfeerate = false;
|
||||||
|
try {
|
||||||
|
const maxfeerateVal = this.testTxsForm.get('maxfeerate')?.value;
|
||||||
|
if (maxfeerateVal != null && maxfeerateVal !== '') {
|
||||||
|
maxfeerate = parseFloat(maxfeerateVal) / 100_000;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.invalidMaxfeerate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.error = '';
|
||||||
|
this.results = [];
|
||||||
|
this.apiService.testTransactions$(txs, maxfeerate === 0.1 ? null : maxfeerate)
|
||||||
|
.subscribe((result) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.results = result || [];
|
||||||
|
this.testTxsForm.reset();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (typeof error.error === 'string') {
|
||||||
|
const matchText = error.error.match('"message":"(.*?)"');
|
||||||
|
this.error = matchText && matchText[1] || error.error;
|
||||||
|
} else if (error.message) {
|
||||||
|
this.error = error.message;
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,7 +11,7 @@ import { Subscription } from 'rxjs';
|
|||||||
})
|
})
|
||||||
export class ThemeSelectorComponent implements OnInit {
|
export class ThemeSelectorComponent implements OnInit {
|
||||||
themeForm: UntypedFormGroup;
|
themeForm: UntypedFormGroup;
|
||||||
themes = ['default', 'contrast', 'wiz'];
|
themes = ['default', 'contrast', 'wiz', 'bukele'];
|
||||||
themeSubscription: Subscription;
|
themeSubscription: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,15 +1,34 @@
|
|||||||
<div class="mobile-wrapper">
|
<div class="mobile-wrapper">
|
||||||
<div class="mobile-container">
|
<div class="mobile-container">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="field nav-header">
|
<div class="nav-header">
|
||||||
<app-svg-images name="officialMempoolSpace" style="width: 144px; height: 36px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
@if (enterpriseInfo?.header_img) {
|
||||||
<div [ngSwitch]="network" class="network-label">
|
<a class="d-flex" [routerLink]="['/' | relativeUrl]">
|
||||||
|
<img *ngIf="enterpriseInfo.header_img" [src]="enterpriseInfo?.header_img" alt="enterpriseInfo.title" height="42px">
|
||||||
|
</a>
|
||||||
|
} @else if (enterpriseInfo?.img || enterpriseInfo?.imageMd5) {
|
||||||
|
<a [routerLink]="['/' | relativeUrl]">
|
||||||
|
<img [src]="enterpriseInfo.img || '/api/v1/services/enterprise/images/' + enterpriseInfo.name + '/logo?imageMd5=' + enterpriseInfo.imageMd5" class="subdomain_logo" [class]="{'rounded': enterpriseInfo.rounded_corner}">
|
||||||
|
</a>
|
||||||
|
<div class="vertical-line"></div>
|
||||||
|
}
|
||||||
|
@if (!enterpriseInfo?.header_img) {
|
||||||
|
<a [routerLink]="['/' | relativeUrl]">
|
||||||
|
<app-svg-images *ngIf="!officialMempoolSpace" name="mempoolSpace" style="width: 144px; height: 36px" viewBox="0 0 500 126" width="500" height="126" class="mempool-logo"></app-svg-images>
|
||||||
|
<app-svg-images *ngIf="officialMempoolSpace" name="officialMempoolSpace" style="width: 144px; height: 36px" width="500" height="126" viewBox="0 0 500 126"></app-svg-images>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (enterpriseInfo?.header_img || (!enterpriseInfo?.img && !enterpriseInfo?.imageMd5)) {
|
||||||
|
<div [ngSwitch]="network" class="network-label" [class.hide-name]="enterpriseInfo?.header_img">
|
||||||
<span *ngSwitchCase="'signet'" class="network signet"><span class="name">Bitcoin Signet</span><app-svg-images name="signet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'signet'" class="network signet"><span class="name">Bitcoin Signet</span><app-svg-images name="signet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchCase="'testnet'" class="network testnet"><span class="name">Bitcoin Testnet</span><app-svg-images name="testnet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'testnet'" class="network testnet"><span class="name">Bitcoin Testnet3</span><app-svg-images name="testnet" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
|
<span *ngSwitchCase="'testnet4'" class="network testnet"><span class="name">Bitcoin Testnet4</span><app-svg-images name="testnet4" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchCase="'liquid'" class="network liquid"><span class="name">Liquid</span><app-svg-images name="liquid" width="35" height="35" viewBox="0 0 125 125" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'liquid'" class="network liquid"><span class="name">Liquid</span><app-svg-images name="liquid" width="35" height="35" viewBox="0 0 125 125" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchCase="'liquidtestnet'" class="network liquidtestnet"><span class="name">Liquid Testnet</span><app-svg-images name="liquidtestnet" width="35" height="35" viewBox="0 0 125 125" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchCase="'liquidtestnet'" class="network liquidtestnet"><span class="name">Liquid Testnet</span><app-svg-images name="liquidtestnet" width="35" height="35" viewBox="0 0 125 125" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
<span *ngSwitchDefault class="network mainnet"><span class="name">Bitcoin</span><app-svg-images name="bitcoin" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
<span *ngSwitchDefault class="network mainnet"><span class="name">Bitcoin</span><app-svg-images name="bitcoin" width="35" height="35" viewBox="0 0 65 65" style="display: inline-block" class="mainnet ml-2"></app-svg-images></span>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="label" i18n="shared.transaction">Transaction</div>
|
<div class="label" i18n="shared.transaction">Transaction</div>
|
||||||
|
@ -40,7 +40,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-header {
|
.nav-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1em;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background: var(--nav-bg);
|
||||||
box-shadow: 0 -5px 15px #000;
|
box-shadow: 0 -5px 15px #000;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -53,6 +60,40 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hide-name .name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subdomain_logo {
|
||||||
|
height: 35px;
|
||||||
|
overflow: clip;
|
||||||
|
max-width: 140px;
|
||||||
|
margin: auto;
|
||||||
|
align-self: center;
|
||||||
|
.rounded {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subdomain_container {
|
||||||
|
max-width: 140px;
|
||||||
|
text-align: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-line {
|
||||||
|
border-left: 1px solid #444;
|
||||||
|
height: 30px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-holder {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +113,10 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
scrollIntoAccelPreview = false;
|
scrollIntoAccelPreview = false;
|
||||||
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
||||||
|
|
||||||
|
enterpriseInfo: any;
|
||||||
|
enterpriseInfo$: Subscription;
|
||||||
|
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
@ -152,6 +156,10 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.enterpriseService.page();
|
this.enterpriseService.page();
|
||||||
|
|
||||||
|
this.enterpriseInfo$ = this.enterpriseService.info$.subscribe(info => {
|
||||||
|
this.enterpriseInfo = info;
|
||||||
|
});
|
||||||
|
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
this.stateService.networkChanged$.subscribe(
|
this.stateService.networkChanged$.subscribe(
|
||||||
(network) => {
|
(network) => {
|
||||||
@ -702,6 +710,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.miningSubscription?.unsubscribe();
|
this.miningSubscription?.unsubscribe();
|
||||||
this.currencyChangeSubscription?.unsubscribe();
|
this.currencyChangeSubscription?.unsubscribe();
|
||||||
|
this.enterpriseInfo$?.unsubscribe();
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,8 @@ td.amount.large {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
.assetBox {
|
.assetBox {
|
||||||
background-color: #653b9c90;
|
background: color-mix(in srgb, var(--tertiary) 56%, transparent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-container {
|
.details-container {
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
@if (loading) {
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
} @else if (error) {
|
||||||
|
<div class="error-wrapper">
|
||||||
|
<span>failed to load X timeline</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<iframe id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" allowfullscreen="true"
|
||||||
|
title="Twitter Timeline"
|
||||||
|
[src]="iframeSrc"
|
||||||
|
style="position: static; visibility: visible; width: 100%; height: 100%; display: block; flex-grow: 1;"
|
||||||
|
(load)="onReady()"
|
||||||
|
></iframe>
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
.spinner-wrapper, .error-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
import { Component, Input, ChangeDetectionStrategy, SecurityContext, SimpleChanges, OnChanges } from '@angular/core';
|
||||||
|
import { LanguageService } from '../../services/language.service';
|
||||||
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-twitter-widget',
|
||||||
|
templateUrl: './twitter-widget.component.html',
|
||||||
|
styleUrls: ['./twitter-widget.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class TwitterWidgetComponent implements OnChanges {
|
||||||
|
@Input() handle: string;
|
||||||
|
@Input() width = 300;
|
||||||
|
@Input() height = 400;
|
||||||
|
|
||||||
|
loading: boolean = true;
|
||||||
|
error: boolean = false;
|
||||||
|
lang: string = 'en';
|
||||||
|
|
||||||
|
iframeSrc: SafeResourceUrl;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private languageService: LanguageService,
|
||||||
|
public sanitizer: DomSanitizer,
|
||||||
|
) {
|
||||||
|
this.lang = this.languageService.getLanguage();
|
||||||
|
this.setIframeSrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes.handle) {
|
||||||
|
this.setIframeSrc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIframeSrc(): void {
|
||||||
|
if (this.handle) {
|
||||||
|
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL,
|
||||||
|
`https://syndication.twitter.com/srv/timeline-profile/screen-name/${this.handle}?creatorScreenName=mempool`
|
||||||
|
+ '&dnt=true'
|
||||||
|
+ '&embedId=twitter-widget-0'
|
||||||
|
+ '&features=eyJ0ZndfdGltZWxpbmVfgbGlzdCI6eyJidWNrZXQiOltdLCJ2ZXJzaW9uIjpudWxsfSwidGZ3X2ZvbGxvd2VyX2NvdW50X3N1bnNldCI6eyJidWNrZXQiOnRydWUsInZlcnNpb24iOm51bGx9LCJ0ZndfdHdlZXRfZWRpdF9iYWNrZW5kIjp7ImJ1Y2tldCI6Im9uIiwidmVyc2lvbiI6bnVsbH0sInRmd19yZWZzcmNfc2Vzc2lvbiI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9LCJ0ZndfZm9zbnJfc29mdF9pbnRlcnZlbnRpb25zX2VuYWJsZWQiOnsiYnVja2V0Ijoib24iLCJ2ZXJzaW9uIjpudWxsfSwidGZ3X21peGVkX21lZGlhXzE1ODk3Ijp7ImJ1Y2tldCI6InRyZWF0bWVudCIsInZlcnNpb24iOm51bGx9LCJ0ZndfZXhwZXJpbWVudHNfY29va2llX2V4cGlyYXRpb24iOnsiYnVja2V0IjoxMjA5NjAwLCJ2ZXJzaW9uIjpudWxsfSwidGZ3X3Nob3dfYmlyZHdhdGNoX3Bpdm90c19lbmFibGVkIjp7ImJ1Y2tldCI6Im9uIiwidmVyc2lvbiI6bnVsbH0sInRmd19kdXBsaWNhdGVfc2NyaWJlc190b19zZXR0aW5ncyI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9LCJ0ZndfdXNlX3Byb2ZpbGVfaW1hZ2Vfc2hhcGVfZW5hYmxlZCI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9LCJ0ZndfdmlkZW9faGxzX2R5bmFtaWNfbWFuaWZlc3RzXzE1MDgyIjp7ImJ1Y2tldCI6InRydWVfYml0cmF0ZSIsInZlcnNpb24iOm51bGx9LCJ0ZndfbGVnYWN5X3RpbWVsaW5lX3N1bnNldCI6eyJidWNrZXQiOnRydWUsInZlcnNpb24iOm51bGx9LCJ0ZndfdHdlZXRfZWRpdF9mcm9udGVuZCI6eyJidWNrZXQiOiJvbiIsInZlcnNpb24iOm51bGx9fQ%3D%3D'
|
||||||
|
+ '&frame=false'
|
||||||
|
+ '&hideBorder=true'
|
||||||
|
+ '&hideFooter=false'
|
||||||
|
+ '&hideHeader=true'
|
||||||
|
+ '&hideScrollBar=false'
|
||||||
|
+ `&lang=${this.lang}`
|
||||||
|
+ '&maxHeight=500px'
|
||||||
|
+ '&origin=https%3A%2F%2Fmempool.space%2F'
|
||||||
|
// + '&sessionId=88f6d661d0dcca99c43c0a590f6a3e61c89226a9'
|
||||||
|
+ '&showHeader=false'
|
||||||
|
+ '&showReplies=false'
|
||||||
|
+ '&siteScreenName=mempool'
|
||||||
|
+ '&theme=dark'
|
||||||
|
+ '&transparent=true'
|
||||||
|
+ '&widgetsVersion=2615f7e52b7e0%3A1702314776716'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReady(): void {
|
||||||
|
this.loading = false;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFailed(): void {
|
||||||
|
this.loading = false;
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
.bowtie-graph-tooltip {
|
.bowtie-graph-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(#11131f, 0.95);
|
background: color-mix(in srgb, var(--active-bg) 95%, transparent);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
|
||||||
color: var(--tooltip-grey);
|
color: var(--tooltip-grey);
|
||||||
|
@ -84,18 +84,19 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||||||
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
||||||
|
|
||||||
gradientColors = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0', '#9339f400'],
|
'': ['var(--mainnet-alt)', 'var(--primary)', 'color-mix(in srgb, var(--mainnet-alt) 1%, transparent)'],
|
||||||
// liquid: ['#116761', '#183550'],
|
// liquid: ['#116761', '#183550'],
|
||||||
liquid: ['#09a197', '#0f62af', '#09a19700'],
|
liquid: ['#09a197', '#0f62af', '#09a19700'],
|
||||||
// 'liquidtestnet': ['#494a4a', '#272e46'],
|
// 'liquidtestnet': ['#494a4a', '#272e46'],
|
||||||
'liquidtestnet': ['#d2d2d2', '#979797', '#d2d2d200'],
|
'liquidtestnet': ['#d2d2d2', '#979797', '#d2d2d200'],
|
||||||
// testnet: ['#1d486f', '#183550'],
|
// testnet: ['#1d486f', '#183550'],
|
||||||
testnet: ['#4edf77', '#10a0af', '#4edf7700'],
|
testnet: ['#4edf77', '#10a0af', '#4edf7700'],
|
||||||
|
testnet4: ['#4edf77', '#10a0af', '#4edf7700'],
|
||||||
// signet: ['#6f1d5d', '#471850'],
|
// signet: ['#6f1d5d', '#471850'],
|
||||||
signet: ['#d24fc8', '#a84fd2', '#d24fc800'],
|
signet: ['#d24fc8', '#a84fd2', '#d24fc800'],
|
||||||
};
|
};
|
||||||
|
|
||||||
gradient: string[] = ['#105fb0', '#105fb0'];
|
gradient: string[] = ['var(--primary)', 'var(--primary)'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
@ -301,7 +301,8 @@
|
|||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: -13px;
|
margin-top: -13px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -435,7 +436,8 @@
|
|||||||
|
|
||||||
.in-progress-message {
|
.in-progress-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #ffffff91;
|
color: var(--fg);
|
||||||
|
opacity: var(--opacity);
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
@ -231,8 +231,10 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
this.stateService.live2Chart$
|
this.stateService.live2Chart$
|
||||||
.pipe(
|
.pipe(
|
||||||
scan((acc, stats) => {
|
scan((acc, stats) => {
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
const start = now - (2 * 60 * 60);
|
||||||
acc.unshift(stats);
|
acc.unshift(stats);
|
||||||
acc = acc.slice(0, 120);
|
acc = acc.filter(p => p.added >= start);
|
||||||
return acc;
|
return acc;
|
||||||
}, (mempoolStats || []))
|
}, (mempoolStats || []))
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const bitcoinNetworks = ["", "testnet", "signet"];
|
const bitcoinNetworks = ["", "testnet", "testnet4", "signet"];
|
||||||
const liquidNetworks = ["liquid", "liquidtestnet"];
|
const liquidNetworks = ["liquid", "liquidtestnet"];
|
||||||
|
const lightningNetworks = ["", "testnet", "signet"];
|
||||||
const miningTimeIntervals = "<code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>";
|
const miningTimeIntervals = "<code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>";
|
||||||
|
|
||||||
const emptyCodeSample = {
|
const emptyCodeSample = {
|
||||||
@ -6513,7 +6514,7 @@ export const restApiDocsData = [
|
|||||||
category: "lightning",
|
category: "lightning",
|
||||||
fragment: "lightning",
|
fragment: "lightning",
|
||||||
title: "Lightning",
|
title: "Lightning",
|
||||||
showConditions: bitcoinNetworks
|
showConditions: lightningNetworks
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "endpoint",
|
type: "endpoint",
|
||||||
@ -6525,7 +6526,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns network-wide stats such as total number of channels and nodes, total capacity, and average/median fee figures.</p><p>Pass one of the following for <code>:interval</code>: <code>latest</code>, <code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>.</p>"
|
default: "<p>Returns network-wide stats such as total number of channels and nodes, total capacity, and average/median fee figures.</p><p>Pass one of the following for <code>:interval</code>: <code>latest</code>, <code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/statistics/:interval",
|
urlString: "/v1/lightning/statistics/:interval",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -6621,7 +6622,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns Lightning nodes and channels that match a full-text, case-insensitive search <code>:query</code> across node aliases, node pubkeys, channel IDs, and short channel IDs.</p>"
|
default: "<p>Returns Lightning nodes and channels that match a full-text, case-insensitive search <code>:query</code> across node aliases, node pubkeys, channel IDs, and short channel IDs.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/search?searchText=:query",
|
urlString: "/v1/lightning/search?searchText=:query",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -6706,7 +6707,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of Lightning nodes running on clearnet in the requested <code>:country</code>, where <code>:country</code> is an ISO Alpha-2 country code.</p>"
|
default: "<p>Returns a list of Lightning nodes running on clearnet in the requested <code>:country</code>, where <code>:country</code> is an ISO Alpha-2 country code.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/country/:country",
|
urlString: "/v1/lightning/nodes/country/:country",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -6928,7 +6929,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns aggregate capacity and number of clearnet nodes per country. Capacity figures are in satoshis.</p>"
|
default: "<p>Returns aggregate capacity and number of clearnet nodes per country. Capacity figures are in satoshis.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/countries",
|
urlString: "/v1/lightning/nodes/countries",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -7072,7 +7073,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of nodes hosted by a specified <code>:isp</code>, where <code>:isp</code> is an ISP's ASN.</p>"
|
default: "<p>Returns a list of nodes hosted by a specified <code>:isp</code>, where <code>:isp</code> is an ISP's ASN.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/isp/:isp",
|
urlString: "/v1/lightning/nodes/isp/:isp",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -7191,7 +7192,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns aggregate capacity, number of nodes, and number of channels per ISP. Capacity figures are in satoshis.</p>"
|
default: "<p>Returns aggregate capacity, number of nodes, and number of channels per ISP. Capacity figures are in satoshis.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/isp-ranking",
|
urlString: "/v1/lightning/nodes/isp-ranking",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -7303,7 +7304,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns two lists of the top 100 nodes: one ordered by liquidity (aggregate channel capacity) and the other ordered by connectivity (number of open channels).</p>"
|
default: "<p>Returns two lists of the top 100 nodes: one ordered by liquidity (aggregate channel capacity) and the other ordered by connectivity (number of open channels).</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/rankings",
|
urlString: "/v1/lightning/nodes/rankings",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -7426,7 +7427,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of the top 100 nodes by liquidity (aggregate channel capacity).</p>"
|
default: "<p>Returns a list of the top 100 nodes by liquidity (aggregate channel capacity).</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/rankings/liquidity",
|
urlString: "/v1/lightning/nodes/rankings/liquidity",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -7623,7 +7624,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of the top 100 nodes by connectivity (number of open channels).</p>"
|
default: "<p>Returns a list of the top 100 nodes by connectivity (number of open channels).</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/rankings/connectivity",
|
urlString: "/v1/lightning/nodes/rankings/connectivity",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -7819,7 +7820,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of the top 100 oldest nodes.</p>"
|
default: "<p>Returns a list of the top 100 oldest nodes.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/rankings/age",
|
urlString: "/v1/lightning/nodes/rankings/age",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8006,7 +8007,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns details about a node with the given <code>:pubKey</code>.</p>"
|
default: "<p>Returns details about a node with the given <code>:pubKey</code>.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/:pubKey",
|
urlString: "/v1/lightning/nodes/:pubKey",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8170,7 +8171,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns historical stats for a node with the given <code>:pubKey</code>.</p>"
|
default: "<p>Returns historical stats for a node with the given <code>:pubKey</code>.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/nodes/:pubKey/statistics",
|
urlString: "/v1/lightning/nodes/:pubKey/statistics",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8268,7 +8269,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns info about a Lightning channel with the given <code>:channelId</code>.</p>"
|
default: "<p>Returns info about a Lightning channel with the given <code>:channelId</code>.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/channels/:channelId",
|
urlString: "/v1/lightning/channels/:channelId",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8433,7 +8434,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns channels that correspond to the given <code>:txid</code> (multiple transaction IDs can be specified).</p>"
|
default: "<p>Returns channels that correspond to the given <code>:txid</code> (multiple transaction IDs can be specified).</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/channels/txids?txId[]=:txid",
|
urlString: "/v1/lightning/channels/txids?txId[]=:txid",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8634,7 +8635,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of a node's channels given its <code>:pubKey</code>. Ten channels are returned at a time. Use <code>:index</code> for paging. <code>:channelStatus</code> can be <code>open</code>, <code>active</code>, or <code>closed</code>.</p>"
|
default: "<p>Returns a list of a node's channels given its <code>:pubKey</code>. Ten channels are returned at a time. Use <code>:index</code> for paging. <code>:channelStatus</code> can be <code>open</code>, <code>active</code>, or <code>closed</code>.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/channels?public_key=:pubKey&status=:channelStatus",
|
urlString: "/v1/lightning/channels?public_key=:pubKey&status=:channelStatus",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8770,7 +8771,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of channels with corresponding node geodata.</p>"
|
default: "<p>Returns a list of channels with corresponding node geodata.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/channels-geo",
|
urlString: "/v1/lightning/channels-geo",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
@ -8878,7 +8879,7 @@ export const restApiDocsData = [
|
|||||||
default: "<p>Returns a list of channels with corresponding geodata for a node with the given <code>:pubKey</code>.</p>"
|
default: "<p>Returns a list of channels with corresponding geodata for a node with the given <code>:pubKey</code>.</p>"
|
||||||
},
|
},
|
||||||
urlString: "/v1/lightning/channels-geo/:pubKey",
|
urlString: "/v1/lightning/channels-geo/:pubKey",
|
||||||
showConditions: bitcoinNetworks,
|
showConditions: lightningNetworks,
|
||||||
showJsExamples: showJsExamplesDefaultFalse,
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
codeExample: {
|
codeExample: {
|
||||||
default: {
|
default: {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user