Compare commits
31 Commits
mononaut/b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
419fe73edf | ||
|
|
446b5e84c3 | ||
|
|
a35c764545 | ||
|
|
7ca809a074 | ||
|
|
2f76744634 | ||
|
|
9f26ca7b48 | ||
|
|
265fec4639 | ||
|
|
d63a8b6358 | ||
|
|
eac66f3bd7 | ||
|
|
5825044253 | ||
|
|
f654704169 | ||
|
|
2db57928c4 | ||
|
|
d3317a2e8b | ||
|
|
fd3eee0d30 | ||
|
|
9c1fab54c1 | ||
|
|
e1fe572549 | ||
|
|
a8b1798eda | ||
|
|
8733ce74a0 | ||
|
|
8081393435 | ||
|
|
6bb896eea2 | ||
|
|
5d45a51110 | ||
|
|
a5db030b21 | ||
|
|
7680163d68 | ||
|
|
15bc471ff2 | ||
|
|
db5df69fb0 | ||
|
|
7469394bf8 | ||
|
|
4a90223792 | ||
|
|
bf60cf721e | ||
|
|
85ad3dee3a | ||
|
|
f723a5a385 | ||
|
|
90b0dcf204 |
@ -61,6 +61,7 @@ class BitcoinRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'chain-tips', this.getChainTips.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'stale-tips', this.getStaleTips.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'stale-tips/:height', this.getStaleTips.bind(this))
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'cpfp', this.getCpfpLocalTxs)
|
||||
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||
@ -628,14 +629,18 @@ class BitcoinRoutes {
|
||||
private async getStaleTips(req: Request, res: Response) {
|
||||
try {
|
||||
if (['mainnet', 'testnet', 'signet', 'testnet4', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
const tips = await chainTips.getStaleTips();
|
||||
if (tips.length > 0) {
|
||||
res.json(tips);
|
||||
} else {
|
||||
handleError(req, res, 503, `Temporarily unavailable`);
|
||||
return;
|
||||
let fromHeight: number | undefined;
|
||||
if (req.params.height !== undefined) {
|
||||
fromHeight = parseInt(req.params.height, 10);
|
||||
if (isNaN(fromHeight) || fromHeight < 0) {
|
||||
handleError(req, res, 400, `Parameter 'height' must be a block height (integer)`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
const tips = await chainTips.$getStaleTipsPage(fromHeight, 25);
|
||||
res.json(tips);
|
||||
} else { // Liquid
|
||||
handleError(req, res, 404, `This API is only available for Bitcoin networks`);
|
||||
return;
|
||||
|
||||
@ -37,7 +37,7 @@ import { calculateGoodBlockCpfp } from './cpfp';
|
||||
import blockProcessor, { BlockProcessingResult, detectTemplateAlgorithm, saveCpfpDataToCpfpSummary } from './block-processor';
|
||||
import mempool from './mempool';
|
||||
import CpfpRepository from '../repositories/CpfpRepository';
|
||||
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
|
||||
import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script';
|
||||
import database from '../database';
|
||||
import { getBlockFirstSeenFromLogs, getOldestLogTimestampFromLogs, scanLogsForBlocksFirstSeen } from '../utils/file-read';
|
||||
|
||||
@ -359,6 +359,8 @@ class Blocks {
|
||||
|
||||
if (extras.pool.name === 'OCEAN') {
|
||||
extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
|
||||
} else if (extras.pool.name === 'DMND') {
|
||||
extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import config from '../config';
|
||||
import logger from '../logger';
|
||||
import { BlockExtended } from '../mempool.interfaces';
|
||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
||||
import BlocksRepository from '../repositories/BlocksRepository';
|
||||
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||
@ -23,26 +24,29 @@ export interface StaleTip extends ChainTip {
|
||||
export interface OrphanedBlock {
|
||||
height: number;
|
||||
hash: string;
|
||||
branchlen: number;
|
||||
status: 'valid-fork' | 'valid-headers' | 'headers-only';
|
||||
prevhash: string;
|
||||
}
|
||||
|
||||
class ChainTips {
|
||||
private chainTips: ChainTip[] = [];
|
||||
private staleTips: Record<number, StaleTip> = {};
|
||||
private validChainTips: ChainTip[] = []; // 'valid-fork' and 'valid-headers' only, in descending height order
|
||||
private staleBlocks: Record<string, BlockExtended> = {};
|
||||
private orphanedBlocks: { [hash: string]: OrphanedBlock } = {};
|
||||
private blockCache: { [hash: string]: OrphanedBlock } = {};
|
||||
private orphansByHeight: { [height: number]: OrphanedBlock[] } = {};
|
||||
private indexingOrphanedBlocks = false;
|
||||
private indexingQueue: { blockhash?: string, block?: IEsploraApi.Block, tip: OrphanedBlock }[] = [];
|
||||
|
||||
private staleTipsCacheSize = 50;
|
||||
private staleBlocksCacheSize = 50;
|
||||
private maxIndexingQueueSize = 100;
|
||||
|
||||
/** @asyncSafe */
|
||||
public async updateOrphanedBlocks(): Promise<void> {
|
||||
try {
|
||||
this.chainTips = await bitcoinClient.getChainTips();
|
||||
this.validChainTips = this.chainTips.filter(tip => tip.status === 'valid-fork' || tip.status === 'valid-headers').sort((a, b) => b.height - a.height);
|
||||
|
||||
const activeTipHeight = this.chainTips.find(tip => tip.status === 'active')?.height || (await bitcoinApi.$getBlockHeightTip());
|
||||
let minIndexHeight = 0;
|
||||
@ -69,6 +73,7 @@ class ChainTips {
|
||||
orphan = {
|
||||
height: block.height,
|
||||
hash: block.id,
|
||||
branchlen: chain.branchlen,
|
||||
status: chain.status,
|
||||
prevhash: block.previousblockhash,
|
||||
};
|
||||
@ -119,13 +124,7 @@ class ChainTips {
|
||||
this.orphansByHeight[orphan.height].push(orphan);
|
||||
}
|
||||
|
||||
const heightsToKeep = new Set(this.chainTips.filter(tip => tip.status !== 'active').map(tip => tip.height));
|
||||
const heightsToRemove: number[] = Object.keys(this.staleTips).map(Number).filter(height => !heightsToKeep.has(height));
|
||||
for (const height of heightsToRemove) {
|
||||
delete this.staleTips[height];
|
||||
}
|
||||
|
||||
this.trimStaleTipsCache();
|
||||
this.trimStaleBlocksCache();
|
||||
|
||||
// index new orphaned blocks in the background
|
||||
void this.$indexOrphanedBlocks();
|
||||
@ -157,7 +156,7 @@ class ChainTips {
|
||||
}
|
||||
let staleBlock: BlockExtended | undefined;
|
||||
const alreadyIndexed = await BlocksSummariesRepository.$isSummaryIndexed(block.id);
|
||||
const needToCache = Object.keys(this.staleTips).length < this.staleTipsCacheSize || block.height > Object.keys(this.staleTips).map(Number).sort((a, b) => b - a)[this.staleTipsCacheSize - 1];
|
||||
const needToCache = this.shouldCacheStaleBlock(block.id, block.height);
|
||||
if (!alreadyIndexed) {
|
||||
staleBlock = await blocks.$indexBlock(block.id, block, true);
|
||||
await blocks.$indexBlockSummary(block.id, block.height, true);
|
||||
@ -168,16 +167,9 @@ class ChainTips {
|
||||
}
|
||||
|
||||
if (staleBlock && needToCache) {
|
||||
const canonicalBlock = await blocks.$indexBlockByHeight(staleBlock.height);
|
||||
this.staleTips[staleBlock.height] = {
|
||||
height: staleBlock.height,
|
||||
hash: staleBlock.id,
|
||||
branchlen: tip.height - staleBlock.height,
|
||||
status: tip.status,
|
||||
stale: staleBlock,
|
||||
canonical: canonicalBlock,
|
||||
};
|
||||
this.trimStaleTipsCache();
|
||||
// ensure the canonical block is correctly indexed
|
||||
await blocks.$indexBlockByHeight(staleBlock.height);
|
||||
this.cacheStaleBlock(staleBlock);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Failed to index orphaned block ${block?.id} at height ${block?.height}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
@ -186,12 +178,42 @@ class ChainTips {
|
||||
this.indexingOrphanedBlocks = false;
|
||||
}
|
||||
|
||||
private trimStaleTipsCache(): void {
|
||||
const staleTipHeights = Object.keys(this.staleTips).map(Number).sort((a, b) => b - a);
|
||||
if (staleTipHeights.length > this.staleTipsCacheSize) {
|
||||
const heightsToDiscard = staleTipHeights.slice(this.staleTipsCacheSize);
|
||||
for (const height of heightsToDiscard) {
|
||||
delete this.staleTips[height];
|
||||
private shouldCacheStaleBlock(hash: string, height: number): boolean {
|
||||
// already cached
|
||||
if (this.staleBlocks[hash]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// cache is not full
|
||||
const cachedBlocks = Object.values(this.staleBlocks);
|
||||
if (cachedBlocks.length < this.staleBlocksCacheSize) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise cache if this block is newer than the oldest in the cache
|
||||
const oldestCachedHeight = cachedBlocks.reduce((min, block) => Math.min(min, block.height), Infinity);
|
||||
return height >= oldestCachedHeight;
|
||||
}
|
||||
|
||||
private cacheStaleBlock(block: BlockExtended): void {
|
||||
this.staleBlocks[block.id] = block;
|
||||
this.trimStaleBlocksCache();
|
||||
}
|
||||
|
||||
// evict the oldest stale blocks until the cache is within the size limit
|
||||
private trimStaleBlocksCache(): void {
|
||||
// sort by height
|
||||
const cachedBlocks = Object.values(this.staleBlocks).sort((a, b) => {
|
||||
if (b.height !== a.height) {
|
||||
return b.height - a.height;
|
||||
}
|
||||
// tie-break by hash
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
// delete everything beyond the size limit
|
||||
if (cachedBlocks.length > this.staleBlocksCacheSize) {
|
||||
for (const block of cachedBlocks.slice(this.staleBlocksCacheSize)) {
|
||||
delete this.staleBlocks[block.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,8 +230,51 @@ class ChainTips {
|
||||
return this.chainTips;
|
||||
}
|
||||
|
||||
public getStaleTips(): StaleTip[] {
|
||||
return Object.values(this.staleTips).sort((a, b) => b.height - a.height);
|
||||
/**
|
||||
* get paginated stale chain tips
|
||||
* @param fromHeight - start height (exclusive)
|
||||
* @param count - requested page size (target, but not strictly enforced)
|
||||
*
|
||||
* @asyncSafe
|
||||
*/
|
||||
public async $getStaleTipsPage(fromHeight: number | undefined, count: number): Promise<StaleTip[]> {
|
||||
const start = fromHeight === undefined ? 0 : this.validChainTips.findIndex(tip => tip.height < fromHeight);
|
||||
// no tips beyond the requested height, we can return early
|
||||
if (start === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// fill the response array with hydrated tip data
|
||||
const tips: StaleTip[] = [];
|
||||
let lastHeight;
|
||||
for (let index = start; index < this.validChainTips.length; index++) {
|
||||
const staleTip = this.validChainTips[index];
|
||||
// stretch the page to include any remaining blocks at the last included height to avoid pagination gaps with a height-based cursor
|
||||
if (tips.length >= count) {
|
||||
if (staleTip.height !== lastHeight) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fetch blocks from caches if available, or DB otherwise
|
||||
const canonical = blocks.getBlocks().find(block => block.height === staleTip.height) || await BlocksRepository.$getBlockByHeight(staleTip.height);
|
||||
let stale: BlockExtended | null | undefined = this.staleBlocks[staleTip.hash];
|
||||
if (!stale) {
|
||||
stale = await BlocksRepository.$getBlockByHash(staleTip.hash);
|
||||
}
|
||||
// skip tips with missing block data
|
||||
if (!canonical || !stale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tips.push({
|
||||
...staleTip,
|
||||
stale,
|
||||
canonical,
|
||||
});
|
||||
lastHeight = staleTip.height;
|
||||
}
|
||||
|
||||
return tips;
|
||||
}
|
||||
|
||||
clearOrphanCacheAboveHeight(height: number): void {
|
||||
@ -234,4 +299,4 @@ class ChainTips {
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChainTips();
|
||||
export default new ChainTips();
|
||||
|
||||
@ -749,7 +749,15 @@ export class Common {
|
||||
// fast but bad heuristic to detect possible coinjoins
|
||||
// (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse)
|
||||
const addressReuse = Object.keys(reusedOutputAddresses).reduce((acc, key) => Math.max(acc, (reusedInputAddresses[key] || 0) + (reusedOutputAddresses[key] || 0)), 0) > 1;
|
||||
if (!addressReuse && tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) {
|
||||
const tokenRelated = (flags & (TransactionFlags.inscription | TransactionFlags.op_return)) !== 0n;
|
||||
if (!addressReuse &&
|
||||
tx.vin.length >= 5 &&
|
||||
tx.vout.length >= 5 &&
|
||||
(Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 &&
|
||||
!tokenRelated &&
|
||||
tx.vin.length / tx.vout.length < 5 &&
|
||||
tx.vin.length / tx.vout.length > 0.2
|
||||
) {
|
||||
flags |= TransactionFlags.coinjoin;
|
||||
}
|
||||
// more than 5:1 input:output ratio
|
||||
|
||||
@ -14,7 +14,7 @@ import chainTips from '../api/chain-tips';
|
||||
import blocks from '../api/blocks';
|
||||
import BlocksAuditsRepository from './BlocksAuditsRepository';
|
||||
import transactionUtils from '../api/transaction-utils';
|
||||
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
|
||||
import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script';
|
||||
import poolsUpdater from '../tasks/pools-updater';
|
||||
|
||||
interface DatabaseBlock {
|
||||
@ -575,8 +575,34 @@ class BlocksRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canonical block hash at a given height
|
||||
* @asyncSafe
|
||||
*/
|
||||
public async $getCanonicalBlockHashByHeight(height: number): Promise<string | null> {
|
||||
try {
|
||||
const [rows]: any[] = await DB.query(`
|
||||
SELECT hash
|
||||
FROM blocks
|
||||
WHERE height = ? AND stale = 0
|
||||
LIMIT 1`,
|
||||
[height]
|
||||
);
|
||||
|
||||
if (rows.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rows[0].hash;
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get canonical block hash at height ${height}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one block by hash
|
||||
* @asyncSafe
|
||||
*/
|
||||
public async $getBlockByHash(hash: string): Promise<BlockExtended | null> {
|
||||
try {
|
||||
@ -1256,6 +1282,10 @@ class BlocksRepository {
|
||||
blk.previousblockhash = dbBlk.previousblockhash;
|
||||
blk.mediantime = dbBlk.mediantime;
|
||||
blk.indexVersion = dbBlk.index_version;
|
||||
blk.stale = dbBlk.stale;
|
||||
if (dbBlk.stale) {
|
||||
blk.canonical = await this.$getCanonicalBlockHashByHeight(dbBlk.height) || undefined;
|
||||
}
|
||||
// BlockExtension
|
||||
extras.totalFees = dbBlk.totalFees;
|
||||
extras.medianFee = dbBlk.medianFee;
|
||||
@ -1344,6 +1374,8 @@ class BlocksRepository {
|
||||
|
||||
if (extras.pool.name === 'OCEAN') {
|
||||
extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
|
||||
} else if (extras.pool.name === 'DMND') {
|
||||
extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw);
|
||||
}
|
||||
|
||||
blk.extras = <BlockExtension>extras;
|
||||
|
||||
@ -224,4 +224,27 @@ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null
|
||||
tagString = tagString.replace('\x00', '');
|
||||
|
||||
return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, ''));
|
||||
}
|
||||
}
|
||||
|
||||
/** Extracts miner names from a DMND coinbase transaction */
|
||||
export function parseDMNDTemplateCreator(coinbaseRaw: string): string[] {
|
||||
try {
|
||||
if (!coinbaseRaw || coinbaseRaw.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(coinbaseRaw)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bytes = Buffer.from(coinbaseRaw, 'hex');
|
||||
const blockHeightLength = bytes[0];
|
||||
const tagDelimiterIndex = 1 + blockHeightLength;
|
||||
if (bytes.length <= tagDelimiterIndex || bytes[tagDelimiterIndex] !== 0x00) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tagStart = tagDelimiterIndex + 1;
|
||||
const tagEnd = bytes.indexOf(0x00, tagStart);
|
||||
const tags = bytes.subarray(tagStart, tagEnd === -1 ? undefined : tagEnd);
|
||||
return tags.toString('utf8').split('/').slice(1, -1);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
539
frontend/package-lock.json
generated
539
frontend/package-lock.json
generated
@ -10,24 +10,24 @@
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "^20.3.25",
|
||||
"@angular/animations": "^20.3.19",
|
||||
"@angular/animations": "^20.3.25",
|
||||
"@angular/cli": "^20.3.25",
|
||||
"@angular/common": "^20.3.19",
|
||||
"@angular/compiler": "^20.3.19",
|
||||
"@angular/core": "^20.3.19",
|
||||
"@angular/forms": "^20.3.19",
|
||||
"@angular/localize": "^20.3.19",
|
||||
"@angular/platform-browser": "^20.3.19",
|
||||
"@angular/platform-browser-dynamic": "^20.3.19",
|
||||
"@angular/platform-server": "^20.3.19",
|
||||
"@angular/router": "^20.3.19",
|
||||
"@angular/common": "^20.3.25",
|
||||
"@angular/compiler": "^20.3.25",
|
||||
"@angular/core": "^20.3.25",
|
||||
"@angular/forms": "^20.3.25",
|
||||
"@angular/localize": "^20.3.25",
|
||||
"@angular/platform-browser": "^20.3.25",
|
||||
"@angular/platform-browser-dynamic": "^20.3.25",
|
||||
"@angular/platform-server": "^20.3.25",
|
||||
"@angular/router": "^20.3.25",
|
||||
"@angular/ssr": "^20.3.25",
|
||||
"@fortawesome/angular-fontawesome": "^3.0.0",
|
||||
"@fortawesome/fontawesome-common-types": "~6.7.2",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.7.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "^19.0.0",
|
||||
"@noble/secp256k1": "^3.0.0",
|
||||
"@noble/secp256k1": "^3.1.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
"bootstrap": "~5.3.8",
|
||||
"clipboard": "^2.0.11",
|
||||
@ -41,8 +41,8 @@
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^20.3.19",
|
||||
"@angular/language-service": "^20.3.19",
|
||||
"@angular/compiler-cli": "^20.3.25",
|
||||
"@angular/language-service": "^20.3.25",
|
||||
"@types/node": "^24.9.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
@ -354,23 +354,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@ -389,21 +372,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect/node_modules/source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
@ -1021,23 +989,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@ -1156,21 +1107,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@ -1307,23 +1243,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@ -1342,21 +1261,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics/node_modules/source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
@ -1367,9 +1271,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/animations": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.19.tgz",
|
||||
"integrity": "sha512-/FjU9i7J58/yBURhgVSIiLDcuyOfJxAa0b7ZrOsx6P+FES+M2T2BKZl5V2NuiP2fDFtjsV7U+M/Z9UNUmeHCEw==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.25.tgz",
|
||||
"integrity": "sha512-lQmti3tI85D525TjUVGqCNLzFxSUoZg+vgIyvuGJZPY0UU/o2S6KAxW6ObmcRotZZHNfenLHIxWgzamBDjIjuw==",
|
||||
"deprecated": "@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -1378,7 +1283,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "20.3.19"
|
||||
"@angular/core": "20.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build": {
|
||||
@ -2240,23 +2145,6 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/cli-truncate": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
|
||||
@ -2332,21 +2220,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@ -2434,9 +2307,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/common": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.19.tgz",
|
||||
"integrity": "sha512-hcB1eUEN8LGcKGc4DlRJ+abS6AYfbEHDZKg8LnXNugkbwI6Ebyh2AUYTDhzZL2S4aH+C8biHKgSYHFCqieCRhA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.25.tgz",
|
||||
"integrity": "sha512-rnRGcXbjet0DHgkRL4Dqxk21G2T4UypVfiTV/fay58H8w9U89PJ1L6gRmk8B/uyfpii/9r23cBwnpcguQykxYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2445,14 +2318,14 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "20.3.19",
|
||||
"@angular/core": "20.3.25",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.19.tgz",
|
||||
"integrity": "sha512-ETkgDKm0l2PuaBubgPJe0ccy8kE75DFu6/zKcz7TUuk3KrKF2OZAopbbjftsUSZGeCNvCdqHzjmcL6hQ6oAOwA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.25.tgz",
|
||||
"integrity": "sha512-TSh6gVoQqlLPqWwsYMK0lfVEQYENQO+USzS+BHFXEHFfgBRap6qDpIUGnRdj0Y2PlaVJUVFbeq1855EZUPUEoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2462,9 +2335,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler-cli": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.19.tgz",
|
||||
"integrity": "sha512-ET/JjO8s62kAHfgIsGXlvW5VUwLqHm03q1y/2yD7aQW/WdDvssMsvZv7Knl440989vdOFemIGTMwVPakmWqRmA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.25.tgz",
|
||||
"integrity": "sha512-iqxwVo5Pgzt3EfT49OZ6plxA6KKxwv7ixx1XNH7QRvaOJC9gmsPScWpx+LO7ZsVZdo/NkA+rnXDl0PauUgGciw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
@ -2484,7 +2357,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.19",
|
||||
"@angular/compiler": "20.3.25",
|
||||
"typescript": ">=5.8 <6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@ -2522,9 +2395,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.19.tgz",
|
||||
"integrity": "sha512-SYnwW+q51bQoPtGFoGovm1P5GK9fMEXsG0lGaEAUapjskblAYyX7hLlM/jgueSojv2SjhqNF8aXR+gjHLhZVNA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.25.tgz",
|
||||
"integrity": "sha512-B4XnnR5jzikZDvZ4PjwjAWZMT14dxrKrmJdwa/n0yp7rMPkIJTKF6ZJMg4d1pLWLLSsc2oWHioN3UrWlGqIKnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2533,7 +2406,7 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.19",
|
||||
"@angular/compiler": "20.3.25",
|
||||
"rxjs": "^6.5.3 || ^7.4.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
@ -2547,9 +2420,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/forms": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.19.tgz",
|
||||
"integrity": "sha512-WJotd+Lhl4FG2b0K+aQNyQDHhR515zKCuphjiUqEW7sifWrOQxANLKzPBngGrH75ayANFgPaDf7U3ZRIoblcQA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.25.tgz",
|
||||
"integrity": "sha512-vGRo1LVPFo2Cu0k+QyDTlsBv5UbN0c3Et2YMS+43oyi1c4keocntBccOjLyM5C0kpMz4+pP81MqqYpAWu2k+TQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2558,16 +2431,16 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.3.19",
|
||||
"@angular/core": "20.3.19",
|
||||
"@angular/platform-browser": "20.3.19",
|
||||
"@angular/common": "20.3.25",
|
||||
"@angular/core": "20.3.25",
|
||||
"@angular/platform-browser": "20.3.25",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/language-service": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.19.tgz",
|
||||
"integrity": "sha512-9J0XrAKXInz11KKyNMrMZmn2NSjVbxzt/DsAumbrzzixeZwiY7vDy2Kqw/LLFLi7IlfMQ/gznz/mCVVgUWI5Gg==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.25.tgz",
|
||||
"integrity": "sha512-3PZUwbDUVQXk9BLSiNUpDxTnRXGR3szwK9QFQaGDt8lhmLYaQLh3VJsY0FHDRghHZYIGR2P2MbVxGQHytF+IPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -2575,9 +2448,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/localize": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.19.tgz",
|
||||
"integrity": "sha512-bXOwxzJUvHzmADI6czdjYnuBMil/UK3CW1dfbrC1MrlLtD0R7g4YZs08J7aWXd+/A4LmTPfkpZhI5ApxGAL0Tg==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.25.tgz",
|
||||
"integrity": "sha512-N4wmEBH44h58Av+1ivQ7LAe6Q0QP/2oBngvmnkO39AM6tadlnQGmaOvVzCUZe4BpOWYrrXQNiszSxGD3mUGgHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
@ -2594,14 +2467,14 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.19",
|
||||
"@angular/compiler-cli": "20.3.19"
|
||||
"@angular/compiler": "20.3.25",
|
||||
"@angular/compiler-cli": "20.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.19.tgz",
|
||||
"integrity": "sha512-TRZfatH1B/kreDwFRwtpLEurJQ6044qh6DWpvxzTbugaG5otLQJKTk+1z81/KsJwQqc1+24v+yuywc1LM7aq7w==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.25.tgz",
|
||||
"integrity": "sha512-0k06U/AJRQifGMLkcU3R9uEHWbuKEzkKMuKcGagXTrkeFvCG2Ub4JdsbcjFNWB2bspWgaxIMSceuj7c83U5wOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2610,9 +2483,9 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "20.3.19",
|
||||
"@angular/common": "20.3.19",
|
||||
"@angular/core": "20.3.19"
|
||||
"@angular/animations": "20.3.25",
|
||||
"@angular/common": "20.3.25",
|
||||
"@angular/core": "20.3.25"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
@ -2621,9 +2494,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser-dynamic": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.19.tgz",
|
||||
"integrity": "sha512-OgErw7wjcC+8yKF5h99hJq8x+tvc091wThfmdL5YC+U3HgRmUaNZFgB/jR7cb/NeeeC42QW5Vc0qoUTC9rMnLQ==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.25.tgz",
|
||||
"integrity": "sha512-3Ku+IsN4tQPVBsw75SoLbLf7TsXAGL0rGPHSsyNYFhG2ZZeQuYNIAi8mc4cwz/qMDnuassHFrCxuLDgN6Yab5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2632,16 +2505,16 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.3.19",
|
||||
"@angular/compiler": "20.3.19",
|
||||
"@angular/core": "20.3.19",
|
||||
"@angular/platform-browser": "20.3.19"
|
||||
"@angular/common": "20.3.25",
|
||||
"@angular/compiler": "20.3.25",
|
||||
"@angular/core": "20.3.25",
|
||||
"@angular/platform-browser": "20.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-server": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.19.tgz",
|
||||
"integrity": "sha512-9STNB8Z5uYpaIgzfiJOH81c4CY2lM3oq/650+pdnjJsedxyEi+NAbnn5tF857Cd/N+43lR+OMolKgm0MJziHqw==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.25.tgz",
|
||||
"integrity": "sha512-uOpLILe5QP9WLXhwshA3fbHc+Wx/4nrUBZNns/dw3t06bRHMbbkutF8eVs+n6Atl332y4mOcStzMX1ilBUSFHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0",
|
||||
@ -2651,17 +2524,17 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.3.19",
|
||||
"@angular/compiler": "20.3.19",
|
||||
"@angular/core": "20.3.19",
|
||||
"@angular/platform-browser": "20.3.19",
|
||||
"@angular/common": "20.3.25",
|
||||
"@angular/compiler": "20.3.25",
|
||||
"@angular/core": "20.3.25",
|
||||
"@angular/platform-browser": "20.3.25",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/router": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.19.tgz",
|
||||
"integrity": "sha512-qHrMniHOsCJ4neZmcQVodjutJilyXAXk7EhLa931QyL0qyVKVomv6E0I3UFzRaC3ZeHc+hzBdU6C6bvMFKTl1g==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.25.tgz",
|
||||
"integrity": "sha512-YIjLHWAufTaukNj15hEoys29e7XNhnCRsS1/95h/OqR69R3adbB8hV7ut7gO6XdXokriYqb4gtoUjoESxR+xFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@ -2670,9 +2543,9 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "20.3.19",
|
||||
"@angular/core": "20.3.19",
|
||||
"@angular/platform-browser": "20.3.19",
|
||||
"@angular/common": "20.3.25",
|
||||
"@angular/core": "20.3.25",
|
||||
"@angular/platform-browser": "20.3.25",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
@ -6591,10 +6464,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/secp256k1": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz",
|
||||
"integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==",
|
||||
"license": "MIT",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.1.0.tgz",
|
||||
"integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g==",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
@ -7583,23 +7455,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@ -7618,21 +7473,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
@ -12527,10 +12367,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.16",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz",
|
||||
"integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==",
|
||||
"license": "MIT",
|
||||
"version": "4.12.25",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz",
|
||||
"integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@ -13277,10 +13116,19 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
|
||||
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/puzrin"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/nodeca"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
@ -13435,12 +13283,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/launch-editor": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz",
|
||||
"integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==",
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz",
|
||||
"integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==",
|
||||
"dependencies": {
|
||||
"picocolors": "^1.1.1",
|
||||
"shell-quote": "^1.8.3"
|
||||
"shell-quote": "^1.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/lazy-ass": {
|
||||
@ -17290,9 +17138,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.11",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
|
||||
"integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==",
|
||||
"version": "7.5.16",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
|
||||
"integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
@ -18997,16 +18845,6 @@
|
||||
"ajv": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@ -19017,13 +18855,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
@ -19281,16 +19112,6 @@
|
||||
"ajv": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@ -19371,13 +19192,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@ -19454,16 +19268,6 @@
|
||||
"ajv": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@ -19474,13 +19278,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
@ -19489,9 +19286,9 @@
|
||||
}
|
||||
},
|
||||
"@angular/animations": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.19.tgz",
|
||||
"integrity": "sha512-/FjU9i7J58/yBURhgVSIiLDcuyOfJxAa0b7ZrOsx6P+FES+M2T2BKZl5V2NuiP2fDFtjsV7U+M/Z9UNUmeHCEw==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.25.tgz",
|
||||
"integrity": "sha512-lQmti3tI85D525TjUVGqCNLzFxSUoZg+vgIyvuGJZPY0UU/o2S6KAxW6ObmcRotZZHNfenLHIxWgzamBDjIjuw==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
@ -19890,16 +19687,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"cli-truncate": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
|
||||
@ -19947,13 +19734,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@ -20004,25 +19784,25 @@
|
||||
}
|
||||
},
|
||||
"@angular/common": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.19.tgz",
|
||||
"integrity": "sha512-hcB1eUEN8LGcKGc4DlRJ+abS6AYfbEHDZKg8LnXNugkbwI6Ebyh2AUYTDhzZL2S4aH+C8biHKgSYHFCqieCRhA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.25.tgz",
|
||||
"integrity": "sha512-rnRGcXbjet0DHgkRL4Dqxk21G2T4UypVfiTV/fay58H8w9U89PJ1L6gRmk8B/uyfpii/9r23cBwnpcguQykxYw==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@angular/compiler": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.19.tgz",
|
||||
"integrity": "sha512-ETkgDKm0l2PuaBubgPJe0ccy8kE75DFu6/zKcz7TUuk3KrKF2OZAopbbjftsUSZGeCNvCdqHzjmcL6hQ6oAOwA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.25.tgz",
|
||||
"integrity": "sha512-TSh6gVoQqlLPqWwsYMK0lfVEQYENQO+USzS+BHFXEHFfgBRap6qDpIUGnRdj0Y2PlaVJUVFbeq1855EZUPUEoA==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@angular/compiler-cli": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.19.tgz",
|
||||
"integrity": "sha512-ET/JjO8s62kAHfgIsGXlvW5VUwLqHm03q1y/2yD7aQW/WdDvssMsvZv7Knl440989vdOFemIGTMwVPakmWqRmA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.25.tgz",
|
||||
"integrity": "sha512-iqxwVo5Pgzt3EfT49OZ6plxA6KKxwv7ixx1XNH7QRvaOJC9gmsPScWpx+LO7ZsVZdo/NkA+rnXDl0PauUgGciw==",
|
||||
"requires": {
|
||||
"@babel/core": "7.28.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
@ -20050,31 +19830,31 @@
|
||||
}
|
||||
},
|
||||
"@angular/core": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.19.tgz",
|
||||
"integrity": "sha512-SYnwW+q51bQoPtGFoGovm1P5GK9fMEXsG0lGaEAUapjskblAYyX7hLlM/jgueSojv2SjhqNF8aXR+gjHLhZVNA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.25.tgz",
|
||||
"integrity": "sha512-B4XnnR5jzikZDvZ4PjwjAWZMT14dxrKrmJdwa/n0yp7rMPkIJTKF6ZJMg4d1pLWLLSsc2oWHioN3UrWlGqIKnA==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@angular/forms": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.19.tgz",
|
||||
"integrity": "sha512-WJotd+Lhl4FG2b0K+aQNyQDHhR515zKCuphjiUqEW7sifWrOQxANLKzPBngGrH75ayANFgPaDf7U3ZRIoblcQA==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.25.tgz",
|
||||
"integrity": "sha512-vGRo1LVPFo2Cu0k+QyDTlsBv5UbN0c3Et2YMS+43oyi1c4keocntBccOjLyM5C0kpMz4+pP81MqqYpAWu2k+TQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@angular/language-service": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.19.tgz",
|
||||
"integrity": "sha512-9J0XrAKXInz11KKyNMrMZmn2NSjVbxzt/DsAumbrzzixeZwiY7vDy2Kqw/LLFLi7IlfMQ/gznz/mCVVgUWI5Gg==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.3.25.tgz",
|
||||
"integrity": "sha512-3PZUwbDUVQXk9BLSiNUpDxTnRXGR3szwK9QFQaGDt8lhmLYaQLh3VJsY0FHDRghHZYIGR2P2MbVxGQHytF+IPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@angular/localize": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.19.tgz",
|
||||
"integrity": "sha512-bXOwxzJUvHzmADI6czdjYnuBMil/UK3CW1dfbrC1MrlLtD0R7g4YZs08J7aWXd+/A4LmTPfkpZhI5ApxGAL0Tg==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.25.tgz",
|
||||
"integrity": "sha512-N4wmEBH44h58Av+1ivQ7LAe6Q0QP/2oBngvmnkO39AM6tadlnQGmaOvVzCUZe4BpOWYrrXQNiszSxGD3mUGgHg==",
|
||||
"requires": {
|
||||
"@babel/core": "7.28.3",
|
||||
"@types/babel__core": "7.20.5",
|
||||
@ -20083,34 +19863,34 @@
|
||||
}
|
||||
},
|
||||
"@angular/platform-browser": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.19.tgz",
|
||||
"integrity": "sha512-TRZfatH1B/kreDwFRwtpLEurJQ6044qh6DWpvxzTbugaG5otLQJKTk+1z81/KsJwQqc1+24v+yuywc1LM7aq7w==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.25.tgz",
|
||||
"integrity": "sha512-0k06U/AJRQifGMLkcU3R9uEHWbuKEzkKMuKcGagXTrkeFvCG2Ub4JdsbcjFNWB2bspWgaxIMSceuj7c83U5wOA==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@angular/platform-browser-dynamic": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.19.tgz",
|
||||
"integrity": "sha512-OgErw7wjcC+8yKF5h99hJq8x+tvc091wThfmdL5YC+U3HgRmUaNZFgB/jR7cb/NeeeC42QW5Vc0qoUTC9rMnLQ==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.25.tgz",
|
||||
"integrity": "sha512-3Ku+IsN4tQPVBsw75SoLbLf7TsXAGL0rGPHSsyNYFhG2ZZeQuYNIAi8mc4cwz/qMDnuassHFrCxuLDgN6Yab5w==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@angular/platform-server": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.19.tgz",
|
||||
"integrity": "sha512-9STNB8Z5uYpaIgzfiJOH81c4CY2lM3oq/650+pdnjJsedxyEi+NAbnn5tF857Cd/N+43lR+OMolKgm0MJziHqw==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.25.tgz",
|
||||
"integrity": "sha512-uOpLILe5QP9WLXhwshA3fbHc+Wx/4nrUBZNns/dw3t06bRHMbbkutF8eVs+n6Atl332y4mOcStzMX1ilBUSFHw==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"@angular/router": {
|
||||
"version": "20.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.19.tgz",
|
||||
"integrity": "sha512-qHrMniHOsCJ4neZmcQVodjutJilyXAXk7EhLa931QyL0qyVKVomv6E0I3UFzRaC3ZeHc+hzBdU6C6bvMFKTl1g==",
|
||||
"version": "20.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.25.tgz",
|
||||
"integrity": "sha512-YIjLHWAufTaukNj15hEoys29e7XNhnCRsS1/95h/OqR69R3adbB8hV7ut7gO6XdXokriYqb4gtoUjoESxR+xFQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
@ -22329,9 +22109,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@noble/secp256k1": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz",
|
||||
"integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg=="
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.1.0.tgz",
|
||||
"integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g=="
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
@ -22832,16 +22612,6 @@
|
||||
"ajv": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"readdirp": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@ -22852,13 +22622,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
@ -26316,9 +26079,9 @@
|
||||
}
|
||||
},
|
||||
"hono": {
|
||||
"version": "4.12.16",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz",
|
||||
"integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg=="
|
||||
"version": "4.12.25",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz",
|
||||
"integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ=="
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "9.0.2",
|
||||
@ -26822,9 +26585,9 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
|
||||
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
@ -26946,12 +26709,12 @@
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
|
||||
},
|
||||
"launch-editor": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz",
|
||||
"integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==",
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz",
|
||||
"integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==",
|
||||
"requires": {
|
||||
"picocolors": "^1.1.1",
|
||||
"shell-quote": "^1.8.3"
|
||||
"shell-quote": "^1.8.4"
|
||||
}
|
||||
},
|
||||
"lazy-ass": {
|
||||
@ -29613,9 +29376,9 @@
|
||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="
|
||||
},
|
||||
"tar": {
|
||||
"version": "7.5.11",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
|
||||
"integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==",
|
||||
"version": "7.5.16",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
|
||||
"integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
|
||||
"requires": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
|
||||
@ -60,17 +60,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "^20.3.25",
|
||||
"@angular/animations": "^20.3.19",
|
||||
"@angular/animations": "^20.3.25",
|
||||
"@angular/cli": "^20.3.25",
|
||||
"@angular/common": "^20.3.19",
|
||||
"@angular/compiler": "^20.3.19",
|
||||
"@angular/core": "^20.3.19",
|
||||
"@angular/forms": "^20.3.19",
|
||||
"@angular/localize": "^20.3.19",
|
||||
"@angular/platform-browser": "^20.3.19",
|
||||
"@angular/platform-browser-dynamic": "^20.3.19",
|
||||
"@angular/platform-server": "^20.3.19",
|
||||
"@angular/router": "^20.3.19",
|
||||
"@angular/common": "^20.3.25",
|
||||
"@angular/compiler": "^20.3.25",
|
||||
"@angular/core": "^20.3.25",
|
||||
"@angular/forms": "^20.3.25",
|
||||
"@angular/localize": "^20.3.25",
|
||||
"@angular/platform-browser": "^20.3.25",
|
||||
"@angular/platform-browser-dynamic": "^20.3.25",
|
||||
"@angular/platform-server": "^20.3.25",
|
||||
"@angular/router": "^20.3.25",
|
||||
"@angular/ssr": "^20.3.25",
|
||||
"@fortawesome/angular-fontawesome": "^3.0.0",
|
||||
"@fortawesome/fontawesome-common-types": "~6.7.2",
|
||||
@ -78,7 +78,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "~6.7.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "^19.0.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
"@noble/secp256k1": "^3.0.0",
|
||||
"@noble/secp256k1": "^3.1.0",
|
||||
"bootstrap": "~5.3.8",
|
||||
"clipboard": "^2.0.11",
|
||||
"domino": "^2.1.6",
|
||||
@ -91,8 +91,8 @@
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^20.3.19",
|
||||
"@angular/language-service": "^20.3.19",
|
||||
"@angular/compiler-cli": "^20.3.25",
|
||||
"@angular/language-service": "^20.3.25",
|
||||
"@types/node": "^24.9.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
|
||||
@ -53,15 +53,23 @@
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
|
||||
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
|
||||
@if (block.extras.pool.minerNames[1].length > 16) {
|
||||
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||
{{ block.extras.pool.name }}
|
||||
<ng-container *ngIf="block.extras.pool.minerSlug && block.extras.pool.minerName; else minerName">
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.minerSlug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.minerName + ' miner'">
|
||||
<span class="miner-name">{{ block.extras.pool.minerName }}</span>
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||
{{ block.extras.pool.name }}
|
||||
</ng-container>
|
||||
<ng-template #minerName>
|
||||
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
|
||||
@if (block.extras.pool.minerNames[1].length > 16) {
|
||||
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
|
||||
{{ block.extras.pool.name }}
|
||||
</ng-template>
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
|
||||
@ -188,14 +188,21 @@
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
|
||||
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
|
||||
@if (block.extras.pool.minerNames[1].length > 16) {
|
||||
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
|
||||
<ng-container *ngIf="block.extras.pool.minerSlug && block.extras.pool.minerName; else minerName">
|
||||
<app-mining-pool [slug]="block.extras.pool.minerSlug" [name]="block.extras.pool.minerName" [showName]="false"></app-mining-pool>
|
||||
<span class="miner-name">{{ block.extras.pool.minerName }}</span>
|
||||
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
|
||||
</ng-container>
|
||||
<ng-template #minerName>
|
||||
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
|
||||
@if (block.extras.pool.minerNames[1].length > 16) {
|
||||
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
|
||||
</ng-template>
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
|
||||
@ -79,8 +79,14 @@
|
||||
<div class="animated" *ngIf="block.extras?.pool != undefined && showPools">
|
||||
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge" [class.miner-name]="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
|
||||
<ng-container *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''; else centralisedPool">
|
||||
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.minerNames[1]" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
<ng-container *ngIf="block.extras.pool.minerSlug && block.extras.pool.minerName; else minerName">
|
||||
<app-mining-pool [slug]="block.extras.pool.minerSlug" [name]="block.extras.pool.minerName" [showName]="false"></app-mining-pool>
|
||||
{{ block.extras.pool.minerName }}
|
||||
</ng-container>
|
||||
<ng-template #minerName>
|
||||
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.minerNames[1]" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #centralisedPool>
|
||||
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="chain-tips" [ngStyle]="{ 'min-height': '295px', 'opacity': isLoading ? '0.75' : '1' }">
|
||||
<div class="chain-tips" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollThrottle]="50" (scrolled)="loadMore()" [ngStyle]="{ 'min-height': '295px', 'opacity': isLoading ? '0.75' : '1' }">
|
||||
<ng-container *ngIf="chainTips$ | async as chainTips">
|
||||
<div *ngFor="let chainTip of chainTips" class="chain-tip">
|
||||
<p class="info">
|
||||
@ -17,7 +17,7 @@
|
||||
<span *ngSwitchCase="'invalid'" class="badge bg-info" i18n="chain-tips.invalid">Invalid</span>
|
||||
<span *ngSwitchDefault>{{ chainTip.status }}</span>
|
||||
</span>
|
||||
<span class="badge bg-secondary depth-badge" i18n="chain-tips.depth">Depth {{ chainTip.branchlen + 1 }}</span>
|
||||
<span class="badge bg-secondary depth-badge" i18n="chain-tips.depth">Depth {{ chainTip.branchlen }}</span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="stale-tip-wrapper">
|
||||
@ -57,8 +57,14 @@
|
||||
<div class="animated" *ngIf="chainTip.stale?.extras?.pool != undefined">
|
||||
<a class="badge" [class.miner-name]="chainTip.stale.extras.pool.minerNames?.length > 1 && chainTip.stale.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + chainTip.stale.extras.pool.slug) | relativeUrl]">
|
||||
<ng-container *ngIf="chainTip.stale.extras.pool.minerNames?.length > 1 && chainTip.stale.extras.pool.minerNames[1] != ''; else staleBlockCentralisedPool">
|
||||
<app-mining-pool [slug]="chainTip.stale.extras.pool.slug" [name]="chainTip.stale.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
|
||||
{{ chainTip.stale.extras.pool.minerNames[1] }}
|
||||
<ng-container *ngIf="chainTip.stale.extras.pool.minerSlug && chainTip.stale.extras.pool.minerName; else minerName">
|
||||
<app-mining-pool [slug]="chainTip.stale.extras.pool.minerSlug" [name]="chainTip.stale.extras.pool.minerName" [showName]="false"></app-mining-pool>
|
||||
{{ chainTip.stale.extras.pool.minerName }}
|
||||
</ng-container>
|
||||
<ng-template #minerName>
|
||||
<app-mining-pool [slug]="chainTip.stale.extras.pool.slug" [name]="chainTip.stale.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
|
||||
{{ chainTip.stale.extras.pool.minerNames[1] }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #staleBlockCentralisedPool>
|
||||
<app-mining-pool [slug]="chainTip.stale.extras.pool.slug" [name]="chainTip.stale.extras.pool.name"></app-mining-pool>
|
||||
@ -103,8 +109,14 @@
|
||||
<div class="animated" *ngIf="chainTip.canonical?.extras?.pool != undefined">
|
||||
<a class="badge" [class.miner-name]="chainTip.canonical.extras.pool.minerNames?.length > 1 && chainTip.canonical.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + chainTip.canonical.extras.pool.slug) | relativeUrl]">
|
||||
<ng-container *ngIf="chainTip.canonical.extras.pool.minerNames?.length > 1 && chainTip.canonical.extras.pool.minerNames[1] != ''; else canonicalBlockCentralisedPool">
|
||||
<app-mining-pool [slug]="chainTip.canonical.extras.pool.slug" [name]="chainTip.canonical.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
|
||||
{{ chainTip.canonical.extras.pool.minerNames[1] }}
|
||||
<ng-container *ngIf="chainTip.canonical.extras.pool.minerSlug && chainTip.canonical.extras.pool.minerName; else minerName">
|
||||
<app-mining-pool [slug]="chainTip.canonical.extras.pool.minerSlug" [name]="chainTip.canonical.extras.pool.minerName" [showName]="false"></app-mining-pool>
|
||||
{{ chainTip.canonical.extras.pool.minerName }}
|
||||
</ng-container>
|
||||
<ng-template #minerName>
|
||||
<app-mining-pool [slug]="chainTip.canonical.extras.pool.slug" [name]="chainTip.canonical.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
|
||||
{{ chainTip.canonical.extras.pool.minerNames[1] }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #canonicalBlockCentralisedPool>
|
||||
<app-mining-pool [slug]="chainTip.canonical.extras.pool.slug" [name]="chainTip.canonical.extras.pool.name"></app-mining-pool>
|
||||
@ -116,10 +128,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-chain-tips" *ngIf="!chainTips?.length">
|
||||
<div class="no-chain-tips" *ngIf="!chainTips?.length && !isLoading && !error">
|
||||
<p i18n="chain-tips.no-stale-blocks-yet">This node hasn't seen any stale blocks yet!</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="isLoadingMore" class="text-center loading-more">
|
||||
<div class="spinner-border" role="status"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="error && !isLoading && !isLoadingMore" class="alert alert-danger text-center loading-more" role="alert">
|
||||
<span i18n="chain-tips.load-error">Failed to load more stale chain tips.</span>
|
||||
<button type="button" class="btn btn-sm btn-danger ms-2" (click)="retryLoadMore()" i18n="chain-tips.retry">Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, of, timer } from 'rxjs';
|
||||
import { catchError, map, retry, share, switchMap, tap } from 'rxjs/operators';
|
||||
import { StaleTip, BlockExtended } from '@interfaces/node-api.interface';
|
||||
import { ApiService } from '@app/services/api.service';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
@ -16,9 +16,12 @@ import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
})
|
||||
export class StaleList implements OnInit {
|
||||
chainTips$: Observable<StaleTip[]>;
|
||||
nextChainTipSubject = new BehaviorSubject(null);
|
||||
urlFragmentSubscription: Subscription;
|
||||
loadMoreSubject = new BehaviorSubject<number | undefined>(undefined);
|
||||
chainTips: StaleTip[] = [];
|
||||
isLoading = true;
|
||||
isLoadingMore = false;
|
||||
fullyLoaded = false;
|
||||
error: unknown = null;
|
||||
|
||||
gradientColors = {
|
||||
'': ['var(--mainnet-alt)', 'var(--primary)'],
|
||||
@ -37,32 +40,73 @@ export class StaleList implements OnInit {
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.chainTips$ = this.apiService.getStaleTips$().pipe(
|
||||
map((chainTips) => {
|
||||
const filtered = chainTips.filter((chainTip) => chainTip.status !== 'active') as StaleTip[];
|
||||
|
||||
filtered.forEach((chainTip) => {
|
||||
if (chainTip.stale?.extras) {
|
||||
chainTip.stale.extras.minFee = this.getMinBlockFee(chainTip.stale);
|
||||
chainTip.stale.extras.maxFee = this.getMaxBlockFee(chainTip.stale);
|
||||
}
|
||||
if (chainTip.canonical?.extras) {
|
||||
chainTip.canonical.extras.minFee = this.getMinBlockFee(chainTip.canonical);
|
||||
chainTip.canonical.extras.maxFee = this.getMaxBlockFee(chainTip.canonical);
|
||||
}
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}),
|
||||
tap(() => {
|
||||
this.chainTips$ = this.loadMoreSubject.pipe(
|
||||
switchMap((height) => this.apiService.getStaleTips$(height).pipe(
|
||||
map((chainTips) => chainTips.filter((chainTip) => chainTip.status !== 'active') as StaleTip[]),
|
||||
retry({
|
||||
count: 2,
|
||||
delay: (err) => {
|
||||
this.error = err;
|
||||
return timer(1000);
|
||||
},
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.error = err;
|
||||
this.isLoading = false;
|
||||
this.isLoadingMore = false;
|
||||
return of(null);
|
||||
}),
|
||||
)),
|
||||
tap((newChainTips) => {
|
||||
if (newChainTips === null) {
|
||||
return;
|
||||
}
|
||||
this.error = null;
|
||||
if (!newChainTips.length) {
|
||||
this.fullyLoaded = true;
|
||||
} else {
|
||||
newChainTips.forEach((chainTip) => {
|
||||
if (chainTip.stale?.extras) {
|
||||
chainTip.stale.extras.minFee = this.getMinBlockFee(chainTip.stale);
|
||||
chainTip.stale.extras.maxFee = this.getMaxBlockFee(chainTip.stale);
|
||||
}
|
||||
if (chainTip.canonical?.extras) {
|
||||
chainTip.canonical.extras.minFee = this.getMinBlockFee(chainTip.canonical);
|
||||
chainTip.canonical.extras.maxFee = this.getMaxBlockFee(chainTip.canonical);
|
||||
}
|
||||
});
|
||||
this.chainTips = this.chainTips.concat(newChainTips);
|
||||
}
|
||||
this.isLoading = false;
|
||||
})
|
||||
this.isLoadingMore = false;
|
||||
}),
|
||||
map(() => this.chainTips),
|
||||
share(),
|
||||
);
|
||||
|
||||
this.seoService.setTitle($localize`:@@page.stale-chain-tips:Stale Chain Tips`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.stale-chain-tips:See the most recent stale chain tips on the Bitcoin${seoDescriptionNetwork(this.stateService.network)} network.`);
|
||||
}
|
||||
|
||||
loadMore(): void {
|
||||
if (this.isLoading || this.isLoadingMore || this.fullyLoaded || this.error) {
|
||||
return;
|
||||
}
|
||||
this.isLoadingMore = true;
|
||||
const height = this.chainTips[this.chainTips.length - 1]?.height;
|
||||
this.loadMoreSubject.next(height);
|
||||
}
|
||||
|
||||
retryLoadMore(): void {
|
||||
if (this.isLoading || this.isLoadingMore || this.fullyLoaded) {
|
||||
return;
|
||||
}
|
||||
this.error = null;
|
||||
this.isLoadingMore = true;
|
||||
const height = this.chainTips[this.chainTips.length - 1]?.height;
|
||||
this.loadMoreSubject.next(height);
|
||||
}
|
||||
|
||||
getBlockGradient(block: BlockExtended): string {
|
||||
if (!block || !block.weight) {
|
||||
return 'var(--secondary)';
|
||||
|
||||
@ -333,14 +333,21 @@
|
||||
@if (pool) {
|
||||
<td class="wrap-cell">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: var(--fg);padding:0;">
|
||||
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
|
||||
@if (pool.minerNames[1].length > 16) {
|
||||
{{ pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<app-mining-pool [slug]="pool.slug" [name]="pool.name"></app-mining-pool>
|
||||
<ng-container *ngIf="pool.minerSlug && pool.minerName; else minerName">
|
||||
<app-mining-pool [slug]="pool.minerSlug" [name]="pool.minerName" [showName]="false"></app-mining-pool>
|
||||
<span class="miner-name">{{ pool.minerName }}</span>
|
||||
<app-mining-pool [slug]="pool.slug" [name]="pool.name"></app-mining-pool>
|
||||
</ng-container>
|
||||
<ng-template #minerName>
|
||||
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
|
||||
@if (pool.minerNames[1].length > 16) {
|
||||
{{ pool.minerNames[1].slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ pool.minerNames[1] }}
|
||||
}
|
||||
</span>
|
||||
<app-mining-pool [slug]="pool.slug" [name]="pool.name"></app-mining-pool>
|
||||
</ng-template>
|
||||
</a>
|
||||
</td>
|
||||
} @else {
|
||||
|
||||
@ -45,6 +45,8 @@ export interface Pool {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
minerName?: string;
|
||||
minerSlug?: string;
|
||||
minerNames: string[] | null;
|
||||
}
|
||||
|
||||
|
||||
@ -330,7 +330,7 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="why-block-timestamps-dont-always-increase">
|
||||
<p>Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it. See more details <a href="https://en.bitcoin.it/wiki/Block_timestamp" target="_blank">here</a>.</p><p>As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.</p>
|
||||
<p>Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that <a href="https://en.bitcoin.it/wiki/Block_timestamp" target="_blank">a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it</a>.</p><p>As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="why-dont-fee-ranges-match">
|
||||
|
||||
@ -224,6 +224,8 @@ export interface BlockExtension {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
minerName?: string;
|
||||
minerSlug?: string;
|
||||
minerNames: string[] | null;
|
||||
}
|
||||
orphans?: {
|
||||
|
||||
@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights,
|
||||
RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult, WalletAddress, Treasury, SubmitPackageResult, ChainTip, StaleTip } from '@interfaces/node-api.interface';
|
||||
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, switchMap, take, tap } from 'rxjs';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { Transaction } from '@interfaces/electrs.interface';
|
||||
import { Conversion } from '@app/services/price.service';
|
||||
import { StorageService } from '@app/services/storage.service';
|
||||
import { WebsocketResponse } from '@interfaces/websocket.interface';
|
||||
import { TxAuditStatus } from '@components/transaction/transaction.component';
|
||||
import { MinersService } from '@app/services/miners.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -24,7 +25,8 @@ export class ApiService {
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private stateService: StateService,
|
||||
private storageService: StorageService
|
||||
private storageService: StorageService,
|
||||
private minersService: MinersService,
|
||||
) {
|
||||
this.apiBaseUrl = ''; // use relative URL by default
|
||||
if (!stateService.isBrowser) { // except when inside AU SSR process
|
||||
@ -172,8 +174,10 @@ export class ApiService {
|
||||
return this.httpClient.get<ChainTip[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/chain-tips');
|
||||
}
|
||||
|
||||
getStaleTips$(): Observable<StaleTip[]> {
|
||||
return this.httpClient.get<StaleTip[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips');
|
||||
getStaleTips$(height?: number): Observable<StaleTip[]> {
|
||||
return this.httpClient.get<StaleTip[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``)).pipe(
|
||||
switchMap((staleTips) => this.minersService.applyStaleTipsMinerDetails$(staleTips))
|
||||
);
|
||||
}
|
||||
|
||||
liquidPegs$(): Observable<CurrentPegs> {
|
||||
@ -308,6 +312,8 @@ export class ApiService {
|
||||
return this.httpClient.get<BlockExtended[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/blocks` +
|
||||
(fromHeight !== undefined ? `/${fromHeight}` : '')
|
||||
).pipe(
|
||||
switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks))
|
||||
);
|
||||
}
|
||||
|
||||
@ -315,11 +321,15 @@ export class ApiService {
|
||||
return this.httpClient.get<BlockExtended[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks` +
|
||||
(from !== undefined ? `/${from}` : ``)
|
||||
).pipe(
|
||||
switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks))
|
||||
);
|
||||
}
|
||||
|
||||
getBlock$(hash: string): Observable<BlockExtended> {
|
||||
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash);
|
||||
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash).pipe(
|
||||
switchMap((block) => this.minersService.applyBlockMinerDetails$(block))
|
||||
);
|
||||
}
|
||||
|
||||
getBlockDataFromTimestamp$(timestamp: number): Observable<any> {
|
||||
|
||||
74
frontend/src/app/services/miners.service.ts
Normal file
74
frontend/src/app/services/miners.service.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError, map, shareReplay } from 'rxjs/operators';
|
||||
import { BlockExtended, StaleTip } from '@interfaces/node-api.interface';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
|
||||
interface Miner {
|
||||
name: string;
|
||||
slug: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MinersService {
|
||||
private apiBaseUrl = '';
|
||||
private miners$: Observable<Miner[]>;
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private stateService: StateService,
|
||||
) {
|
||||
if (!stateService.isBrowser) {
|
||||
this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
|
||||
}
|
||||
this.miners$ = this.httpClient.get<Miner[]>(this.apiBaseUrl + '/resources/miners.json').pipe(
|
||||
catchError(() => of([])),
|
||||
shareReplay(1)
|
||||
);
|
||||
}
|
||||
|
||||
public applyBlockMinerDetails$(block: BlockExtended): Observable<BlockExtended> {
|
||||
if (!block?.extras?.pool?.minerNames?.length) {
|
||||
return of(block);
|
||||
}
|
||||
return this.miners$.pipe(map((miners) => this.applyBlockMinerDetails(block, miners)));
|
||||
}
|
||||
|
||||
public applyBlocksMinerDetails$(blocks: BlockExtended[]): Observable<BlockExtended[]> {
|
||||
if (!blocks?.some((block) => block?.extras?.pool?.minerNames?.length)) {
|
||||
return of(blocks);
|
||||
}
|
||||
return this.miners$.pipe(map((miners) => blocks.map((block) => this.applyBlockMinerDetails(block, miners))));
|
||||
}
|
||||
|
||||
public applyStaleTipsMinerDetails$(staleTips: StaleTip[]): Observable<StaleTip[]> {
|
||||
if (!staleTips?.some((staleTip) => staleTip.stale?.extras?.pool?.minerNames?.length || staleTip.canonical?.extras?.pool?.minerNames?.length)) {
|
||||
return of(staleTips);
|
||||
}
|
||||
return this.miners$.pipe(
|
||||
map((miners) => staleTips.map((staleTip) => {
|
||||
this.applyBlockMinerDetails(staleTip.stale, miners);
|
||||
this.applyBlockMinerDetails(staleTip.canonical, miners);
|
||||
return staleTip;
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private applyBlockMinerDetails(block: BlockExtended, miners: Miner[]): BlockExtended {
|
||||
const minerNames = block?.extras?.pool?.minerNames;
|
||||
if (!minerNames?.length) {
|
||||
return block;
|
||||
}
|
||||
|
||||
const miner = miners.find((minerEntry) => minerEntry.tags.some((tag) => minerNames.includes(tag)));
|
||||
if (miner?.name && miner.slug) {
|
||||
block.extras.pool.minerName = miner.name;
|
||||
block.extras.pool.minerSlug = miner.slug;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
}
|
||||
@ -3,12 +3,13 @@ import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
import { WebsocketResponse } from '@interfaces/websocket.interface';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { Transaction } from '@interfaces/electrs.interface';
|
||||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
import { firstValueFrom, of, Subscription } from 'rxjs';
|
||||
import { ApiService } from '@app/services/api.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { catchError, take } from 'rxjs/operators';
|
||||
import { TransferState, makeStateKey } from '@angular/core';
|
||||
import { CacheService } from '@app/services/cache.service';
|
||||
import { uncompressDeltaChange, uncompressTx } from '@app/shared/common.utils';
|
||||
import { MinersService } from '@app/services/miners.service';
|
||||
|
||||
const OFFLINE_RETRY_AFTER_MS = 2000;
|
||||
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
||||
@ -51,6 +52,7 @@ export class WebsocketService {
|
||||
private apiService: ApiService,
|
||||
private transferState: TransferState,
|
||||
private cacheService: CacheService,
|
||||
private minersService: MinersService,
|
||||
) {
|
||||
if (!this.stateService.isBrowser) {
|
||||
// @ts-ignore
|
||||
@ -356,9 +358,14 @@ export class WebsocketService {
|
||||
|
||||
if (response.blocks && response.blocks.length) {
|
||||
const blocks = response.blocks;
|
||||
this.stateService.resetBlocks(blocks);
|
||||
const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight);
|
||||
this.stateService.updateChainTip(maxHeight);
|
||||
this.minersService.applyBlocksMinerDetails$(blocks).pipe(
|
||||
take(1),
|
||||
catchError(() => of(blocks))
|
||||
).subscribe((mappedBlocks) => {
|
||||
this.stateService.resetBlocks(mappedBlocks);
|
||||
const maxHeight = mappedBlocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight);
|
||||
this.stateService.updateChainTip(maxHeight);
|
||||
});
|
||||
}
|
||||
|
||||
if (response.tx) {
|
||||
@ -371,9 +378,14 @@ export class WebsocketService {
|
||||
|
||||
if (response.block) {
|
||||
if (response.block.height === this.stateService.latestBlockHeight + 1) {
|
||||
this.stateService.updateChainTip(response.block.height);
|
||||
this.stateService.addBlock(response.block);
|
||||
this.stateService.txConfirmed$.next([response.txConfirmed, response.block]);
|
||||
this.minersService.applyBlockMinerDetails$(response.block).pipe(
|
||||
take(1),
|
||||
catchError(() => of(response.block))
|
||||
).subscribe((block) => {
|
||||
this.stateService.updateChainTip(block.height);
|
||||
this.stateService.addBlock(block);
|
||||
this.stateService.txConfirmed$.next([response.txConfirmed, block]);
|
||||
});
|
||||
} else if (response.block.height > this.stateService.latestBlockHeight + 1) {
|
||||
reinitBlocks = true;
|
||||
}
|
||||
|
||||
@ -935,7 +935,15 @@ export function getTransactionFlags(tx: Transaction, cpfpInfo?: CpfpInfo, replac
|
||||
// fast but bad heuristic to detect possible coinjoins
|
||||
// (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse)
|
||||
const addressReuse = Object.keys(reusedOutputAddresses).reduce((acc, key) => Math.max(acc, (reusedInputAddresses[key] || 0) + (reusedOutputAddresses[key] || 0)), 0) > 1;
|
||||
if (!addressReuse && tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) {
|
||||
const tokenRelated = (flags & (TransactionFlags.inscription | TransactionFlags.op_return)) !== 0n;
|
||||
if (!addressReuse &&
|
||||
tx.vin.length >= 5 &&
|
||||
tx.vout.length >= 5 &&
|
||||
(Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 &&
|
||||
!tokenRelated &&
|
||||
tx.vin.length / tx.vout.length < 5 &&
|
||||
tx.vin.length / tx.vout.length > 0.2
|
||||
) {
|
||||
flags |= TransactionFlags.coinjoin;
|
||||
}
|
||||
// more than 5:1 input:output ratio
|
||||
|
||||
8
frontend/src/resources/miners.json
Normal file
8
frontend/src/resources/miners.json
Normal file
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"name": "GoMining",
|
||||
"tags": ["GM", "GoMining"],
|
||||
"link": "https://gomining.com/",
|
||||
"slug": "gomining"
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user