/** Chain section builder - typed tree-based patterns */ import { Unit } from "../utils/units.js"; import { priceLine } from "./constants.js"; import { line, baseline, dots } from "./series.js"; import { satsBtcUsd } from "./shared.js"; import { spendableTypeColors } from "./colors/index.js"; /** Major pools to show in Compare section (by current hashrate dominance) */ const MAJOR_POOL_IDS = [ "foundryusa", // ~32% - largest pool "antpool", // ~18% - Bitmain-owned "viabtc", // ~14% - independent "f2pool", // ~10% - one of the oldest pools "marapool", // MARA Holdings "braiinspool", // formerly Slush Pool "spiderpool", // growing Asian pool "ocean", // decentralization-focused ]; /** * AntPool & friends - pools sharing AntPool's block templates * Based on b10c's research: https://b10c.me/blog/015-bitcoin-mining-centralization/ * Collectively ~35-40% of network hashrate */ const ANTPOOL_AND_FRIENDS_IDS = [ "antpool", // Bitmain-owned, template source "poolin", // shares AntPool templates "btccom", // CloverPool (formerly BTC.com) "braiinspool", // shares AntPool templates "ultimuspool", // shares AntPool templates "binancepool", // shares AntPool templates "secpool", // shares AntPool templates "sigmapoolcom", // SigmaPool "rawpool", // shares AntPool templates "luxor", // shares AntPool templates ]; /** * Create Chain section * @param {PartialContext} ctx * @returns {PartialOptionsGroup} */ export function createChainSection(ctx) { const { colors, brk, fromSumStatsPattern, fromBaseStatsPattern, fromFullStatsPattern, fromStatsPattern, fromCoinbasePattern, fromValuePattern, fromCountPattern, fromSupplyPattern, } = ctx; const { blocks, transactions, pools, inputs, outputs, scripts, supply, distribution, } = brk.metrics; // Address types for mapping (using spendableTypeColors for consistency) /** @type {ReadonlyArray<{key: AddressableType, name: string, color: Color, defaultActive?: boolean}>} */ const addressTypes = [ { key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] }, { key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] }, { key: "p2wpkh", name: "P2WPKH", color: colors[spendableTypeColors.p2wpkh] }, { key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] }, { key: "p2tr", name: "P2TR", color: colors[spendableTypeColors.p2tr] }, { key: "p2pk65", name: "P2PK65", color: colors[spendableTypeColors.p2pk65], defaultActive: false }, { key: "p2pk33", name: "P2PK33", color: colors[spendableTypeColors.p2pk33], defaultActive: false }, { key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a], defaultActive: false }, ]; // Activity types for mapping /** @type {ReadonlyArray<{key: "sending" | "receiving" | "both" | "reactivated" | "balanceIncreased" | "balanceDecreased", name: string, title: string, compareTitle: string}>} */ const activityTypes = [ { key: "sending", name: "Sending", title: "Sending Address Count", compareTitle: "Sending Address Count by Type" }, { key: "receiving", name: "Receiving", title: "Receiving Address Count", compareTitle: "Receiving Address Count by Type" }, { key: "both", name: "Both", title: "Addresses Sending & Receiving (Same Block)", compareTitle: "Addresses Sending & Receiving by Type" }, { key: "reactivated", name: "Reactivated", title: "Reactivated Address Count (Was Empty)", compareTitle: "Reactivated Address Count by Type" }, { key: "balanceIncreased", name: "Balance Increased", title: "Addresses with Increased Balance", compareTitle: "Addresses with Increased Balance by Type" }, { key: "balanceDecreased", name: "Balance Decreased", title: "Addresses with Decreased Balance", compareTitle: "Addresses with Decreased Balance by Type" }, ]; // Count types for comparison charts /** @type {ReadonlyArray<{key: "addrCount" | "emptyAddrCount" | "totalAddrCount", name: string, title: string}>} */ const countTypes = [ { key: "addrCount", name: "Loaded", title: "Address Count by Type" }, { key: "emptyAddrCount", name: "Empty", title: "Empty Address Count by Type" }, { key: "totalAddrCount", name: "Total", title: "Total Address Count by Type" }, ]; /** * Create address metrics tree for a given type key * @param {AddressableType | "all"} key * @param {string} titlePrefix */ const createAddressMetricsTree = (key, titlePrefix) => [ { name: "Count", title: `${titlePrefix}Address Count`, bottom: [ line({ metric: distribution.addrCount[key], name: "Loaded", unit: Unit.count, }), line({ metric: distribution.totalAddrCount[key], name: "Total", color: colors.default, unit: Unit.count, defaultActive: false, }), line({ metric: distribution.emptyAddrCount[key], name: "Empty", color: colors.gray, unit: Unit.count, defaultActive: false, }), ], }, { name: "New", title: `${titlePrefix}New Address Count`, bottom: fromFullStatsPattern(distribution.newAddrCount[key], Unit.count), }, { name: "Growth Rate", title: `${titlePrefix}Address Growth Rate`, bottom: fromBaseStatsPattern(distribution.growthRate[key], Unit.ratio), }, { name: "Activity", tree: activityTypes.map((a) => ({ name: a.name, title: `${titlePrefix}${a.name} Address Count`, bottom: fromBaseStatsPattern( distribution.addressActivity[key][a.key], Unit.count, ), })), }, ]; // Build pools tree dynamically const poolEntries = Object.entries(pools.vecs); const poolsTree = poolEntries.map(([key, pool]) => { const poolName = brk.POOL_ID_TO_POOL_NAME[ /** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase()) ] || key; return { name: poolName, tree: [ { name: "Dominance", title: `${poolName} Dominance`, bottom: [ dots({ metric: pool._24hDominance, name: "24h", color: colors.pink, unit: Unit.percentage, defaultActive: false, }), line({ metric: pool._1wDominance, name: "1w", color: colors.red, unit: Unit.percentage, defaultActive: false, }), line({ metric: pool._1mDominance, name: "1m", unit: Unit.percentage, }), line({ metric: pool._1yDominance, name: "1y", color: colors.lime, unit: Unit.percentage, defaultActive: false, }), line({ metric: pool.dominance, name: "All Time", color: colors.teal, unit: Unit.percentage, defaultActive: false, }), ], }, { name: "Blocks mined", title: `${poolName} Blocks`, bottom: [ dots({ metric: pool.blocksMined.sum, name: "Sum", unit: Unit.count, }), line({ metric: pool.blocksMined.cumulative, name: "Cumulative", color: colors.blue, unit: Unit.count, defaultActive: false, }), line({ metric: pool._24hBlocksMined, name: "24h sum", color: colors.pink, unit: Unit.count, defaultActive: false, }), line({ metric: pool._1wBlocksMined, name: "1w sum", color: colors.red, unit: Unit.count, defaultActive: false, }), line({ metric: pool._1mBlocksMined, name: "1m sum", color: colors.pink, unit: Unit.count, defaultActive: false, }), line({ metric: pool._1yBlocksMined, name: "1y sum", color: colors.purple, unit: Unit.count, defaultActive: false, }), ], }, { name: "Rewards", title: `${poolName} Rewards`, bottom: [ ...fromValuePattern( pool.coinbase, "coinbase", colors.orange, colors.red, ), ...fromValuePattern( pool.subsidy, "subsidy", colors.lime, colors.emerald, ), ...fromValuePattern(pool.fee, "fee", colors.cyan, colors.indigo), ], }, { name: "Since last block", title: `${poolName} Since Last Block`, bottom: [ line({ metric: pool.blocksSinceBlock, name: "Blocks", unit: Unit.count, }), line({ metric: pool.daysSinceBlock, name: "Days", unit: Unit.days, }), ], }, ], }; }); return { name: "Chain", tree: [ // Block { name: "Block", tree: [ { name: "Count", title: "Block Count", bottom: [ ...fromCountPattern(blocks.count.blockCount, Unit.count), line({ metric: blocks.count.blockCountTarget, name: "Target", color: colors.gray, unit: Unit.count, options: { lineStyle: 4 }, }), line({ metric: blocks.count._24hBlockCount, name: "24h sum", color: colors.pink, unit: Unit.count, defaultActive: false, }), line({ metric: blocks.count._1wBlockCount, name: "1w sum", color: colors.red, unit: Unit.count, defaultActive: false, }), line({ metric: blocks.count._1mBlockCount, name: "1m sum", color: colors.orange, unit: Unit.count, defaultActive: false, }), line({ metric: blocks.count._1yBlockCount, name: "1y sum", color: colors.purple, unit: Unit.count, defaultActive: false, }), ], }, { name: "Interval", title: "Block Interval", bottom: [ ...fromBaseStatsPattern(blocks.interval, Unit.secs, "", { avgActive: false }), priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }), ], }, { name: "Size", title: "Block Size", bottom: [ ...fromSumStatsPattern(blocks.size, Unit.bytes), line({ metric: blocks.totalSize, name: "Total", color: colors.purple, unit: Unit.bytes, defaultActive: false, }), ...fromBaseStatsPattern(blocks.vbytes, Unit.vb), ...fromBaseStatsPattern(blocks.weight, Unit.wu), line({ metric: blocks.weight.sum, name: "Sum", color: colors.stat.sum, unit: Unit.wu, defaultActive: false, }), line({ metric: blocks.weight.cumulative, name: "Cumulative", color: colors.stat.cumulative, unit: Unit.wu, defaultActive: false, }), ], }, { name: "Fullness", title: "Block Fullness", bottom: fromBaseStatsPattern(blocks.fullness, Unit.percentage), }, ], }, // Transaction { name: "Transaction", tree: [ { name: "Count", title: "Transaction Count", bottom: fromFullStatsPattern(transactions.count.txCount, Unit.count), }, { name: "Speed", title: "Transactions Per Second", bottom: [ dots({ metric: transactions.volume.txPerSec, name: "Transactions", unit: Unit.perSec, }), ], }, { name: "Volume", title: "Transaction Volume", bottom: [ ...satsBtcUsd(transactions.volume.sentSum, "Sent"), ...satsBtcUsd( transactions.volume.receivedSum, "Received", colors.cyan, { defaultActive: false, }, ), ...satsBtcUsd( transactions.volume.annualizedVolume, "Annualized", colors.red, { defaultActive: false }, ), ], }, { name: "Size", title: "Transaction Size", bottom: [ ...fromStatsPattern(transactions.size.weight, Unit.wu), ...fromStatsPattern(transactions.size.vsize, Unit.vb), ], }, { name: "Fee Rate", title: "Fee Rate", bottom: fromStatsPattern(transactions.fees.feeRate, Unit.feeRate), }, { name: "Versions", title: "Transaction Versions", bottom: [ ...fromCountPattern( transactions.versions.v1, Unit.count, "v1", colors.orange, colors.red, ), ...fromCountPattern( transactions.versions.v2, Unit.count, "v2", colors.cyan, colors.blue, ), ...fromCountPattern( transactions.versions.v3, Unit.count, "v3", colors.lime, colors.green, ), ], }, { name: "Velocity", title: "Transactions Velocity", bottom: [ line({ metric: supply.velocity.btc, name: "Bitcoin", unit: Unit.ratio, }), line({ metric: supply.velocity.usd, name: "Dollars", color: colors.emerald, unit: Unit.ratio, }), ], }, ], }, // UTXO Set (merged Input, Output, UTXO) { name: "UTXO Set", tree: [ { name: "Input Count", title: "Input Count", bottom: [...fromSumStatsPattern(inputs.count, Unit.count)], }, { name: "Output Count", title: "Output Count", bottom: [...fromSumStatsPattern(outputs.count.totalCount, Unit.count)], }, { name: "Inputs/sec", title: "Inputs Per Second", bottom: [ dots({ metric: transactions.volume.inputsPerSec, name: "Inputs", unit: Unit.perSec, }), ], }, { name: "Outputs/sec", title: "Outputs Per Second", bottom: [ dots({ metric: transactions.volume.outputsPerSec, name: "Outputs", unit: Unit.perSec, }), ], }, { name: "UTXO Count", title: "UTXO Count", bottom: [ line({ metric: outputs.count.utxoCount, name: "Count", unit: Unit.count, }), ], }, ], }, // Scripts { name: "Scripts", tree: [ { name: "Count", tree: [ // Legacy scripts { name: "Legacy", tree: [ { name: "P2PKH", title: "P2PKH Output Count", bottom: fromFullStatsPattern(scripts.count.p2pkh, Unit.count), }, { name: "P2PK33", title: "P2PK33 Output Count", bottom: fromFullStatsPattern( scripts.count.p2pk33, Unit.count, ), }, { name: "P2PK65", title: "P2PK65 Output Count", bottom: fromFullStatsPattern( scripts.count.p2pk65, Unit.count, ), }, ], }, // Script Hash { name: "Script Hash", tree: [ { name: "P2SH", title: "P2SH Output Count", bottom: fromFullStatsPattern(scripts.count.p2sh, Unit.count), }, { name: "P2MS", title: "P2MS Output Count", bottom: fromFullStatsPattern(scripts.count.p2ms, Unit.count), }, ], }, // SegWit scripts { name: "SegWit", tree: [ { name: "All SegWit", title: "SegWit Output Count", bottom: fromFullStatsPattern( scripts.count.segwit, Unit.count, ), }, { name: "P2WPKH", title: "P2WPKH Output Count", bottom: fromFullStatsPattern( scripts.count.p2wpkh, Unit.count, ), }, { name: "P2WSH", title: "P2WSH Output Count", bottom: fromFullStatsPattern(scripts.count.p2wsh, Unit.count), }, ], }, // Taproot scripts { name: "Taproot", tree: [ { name: "P2TR", title: "P2TR Output Count", bottom: fromFullStatsPattern(scripts.count.p2tr, Unit.count), }, { name: "P2A", title: "P2A Output Count", bottom: fromFullStatsPattern(scripts.count.p2a, Unit.count), }, ], }, // Other scripts { name: "Other", tree: [ { name: "OP_RETURN", title: "OP_RETURN Output Count", bottom: fromFullStatsPattern( scripts.count.opreturn, Unit.count, ), }, { name: "Empty", title: "Empty Output Count", bottom: fromFullStatsPattern( scripts.count.emptyoutput, Unit.count, ), }, { name: "Unknown", title: "Unknown Output Count", bottom: fromFullStatsPattern( scripts.count.unknownoutput, Unit.count, ), }, ], }, ], }, { name: "Adoption", tree: [ { name: "SegWit", title: "SegWit Adoption", bottom: [ line({ metric: scripts.count.segwitAdoption.base, name: "Base", unit: Unit.percentage, }), line({ metric: scripts.count.segwitAdoption.sum, name: "Sum", color: colors.stat.sum, unit: Unit.percentage, }), line({ metric: scripts.count.segwitAdoption.cumulative, name: "Cumulative", color: colors.stat.cumulative, unit: Unit.percentage, defaultActive: false, }), ], }, { name: "Taproot", title: "Taproot Adoption", bottom: [ line({ metric: scripts.count.taprootAdoption.base, name: "Base", unit: Unit.percentage, }), line({ metric: scripts.count.taprootAdoption.sum, name: "Sum", color: colors.stat.sum, unit: Unit.percentage, }), line({ metric: scripts.count.taprootAdoption.cumulative, name: "Cumulative", color: colors.stat.cumulative, unit: Unit.percentage, defaultActive: false, }), ], }, ], }, { name: "OP_RETURN Value", title: "OP_RETURN Value", bottom: fromCoinbasePattern(scripts.value.opreturn), }, ], }, // Supply { name: "Supply", tree: [ { name: "Circulating", title: "Circulating Supply", bottom: fromSupplyPattern(supply.circulating, "Supply"), }, { name: "Inflation", title: "Inflation Rate", bottom: [ dots({ metric: supply.inflation, name: "Rate", unit: Unit.percentage, }), ], }, { name: "Unspendable", title: "Unspendable Supply", bottom: fromValuePattern(supply.burned.unspendable), }, { name: "OP_RETURN", title: "OP_RETURN Supply", bottom: fromValuePattern(supply.burned.opreturn), }, ], }, // Rewards { name: "Rewards", tree: [ { name: "Coinbase", title: "Coinbase Rewards", bottom: [ ...fromCoinbasePattern(blocks.rewards.coinbase), ...satsBtcUsd( blocks.rewards._24hCoinbaseSum, "24h sum", colors.pink, { defaultActive: false }, ), ], }, { name: "Subsidy", title: "Block Subsidy", bottom: [ ...fromCoinbasePattern(blocks.rewards.subsidy), line({ metric: blocks.rewards.subsidyDominance, name: "Dominance", color: colors.purple, unit: Unit.percentage, defaultActive: false, }), line({ metric: blocks.rewards.subsidyUsd1ySma, name: "1y SMA", color: colors.lime, unit: Unit.usd, defaultActive: false, }), ], }, { name: "Fee", title: "Transaction Fees", bottom: [ ...fromSumStatsPattern(transactions.fees.fee.bitcoin, Unit.btc), ...fromSumStatsPattern(transactions.fees.fee.sats, Unit.sats), ...fromSumStatsPattern(transactions.fees.fee.dollars, Unit.usd), line({ metric: blocks.rewards.feeDominance, name: "Dominance", color: colors.purple, unit: Unit.percentage, defaultActive: false, }), ], }, { name: "Unclaimed", title: "Unclaimed Rewards", bottom: fromValuePattern( blocks.rewards.unclaimedRewards, "Unclaimed", ), }, ], }, // Addresses { name: "Addresses", tree: [ // Overview - global metrics for all addresses { name: "Overview", tree: createAddressMetricsTree("all", "") }, // Compare - cross-type comparisons (base + average, system selects appropriate one) { name: "Compare", tree: [ { name: "Count", tree: countTypes.map((c) => ({ name: c.name, title: c.title, bottom: addressTypes.map((t) => line({ metric: distribution[c.key][t.key], name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), })), }, { name: "New", title: "New Address Count by Type", bottom: addressTypes.flatMap((t) => [ line({ metric: distribution.newAddrCount[t.key].base, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), line({ metric: distribution.newAddrCount[t.key].average, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ]), }, { name: "Growth Rate", title: "Address Growth Rate by Type", bottom: addressTypes.flatMap((t) => [ line({ metric: distribution.growthRate[t.key].base, name: t.name, color: t.color, unit: Unit.ratio, defaultActive: t.defaultActive, }), line({ metric: distribution.growthRate[t.key].average, name: t.name, color: t.color, unit: Unit.ratio, defaultActive: t.defaultActive, }), ]), }, { name: "Activity", tree: activityTypes.map((a) => ({ name: a.name, title: a.compareTitle, bottom: addressTypes.flatMap((t) => [ line({ metric: distribution.addressActivity[t.key][a.key].base, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), line({ metric: distribution.addressActivity[t.key][a.key].average, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ]), })), }, ], }, // Individual address types - each with same structure as Overview ...addressTypes.map((t) => ({ name: t.name, tree: createAddressMetricsTree(t.key, `${t.name} `), })), ], }, // Mining { name: "Mining", tree: [ // Hashrate { name: "Hashrate", title: "Network Hashrate", bottom: [ dots({ metric: blocks.mining.hashRate, name: "Hashrate", unit: Unit.hashRate, }), line({ metric: blocks.mining.hashRate1wSma, name: "1w SMA", color: colors.red, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.mining.hashRate1mSma, name: "1m SMA", color: colors.orange, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.mining.hashRate2mSma, name: "2m SMA", color: colors.yellow, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.mining.hashRate1ySma, name: "1y SMA", color: colors.lime, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.difficulty.asHash, name: "Difficulty", color: colors.default, unit: Unit.hashRate, options: { lineStyle: 1 }, }), ], }, // Difficulty group { name: "Difficulty", tree: [ { name: "Level", title: "Network Difficulty", bottom: [ line({ metric: blocks.difficulty.raw, name: "Difficulty", unit: Unit.difficulty, }), line({ metric: blocks.difficulty.epoch, name: "Epoch", color: colors.teal, unit: Unit.epoch, }), ], }, { name: "Adjustment", title: "Difficulty Adjustment", bottom: [ baseline({ metric: blocks.difficulty.adjustment, name: "Difficulty Change", unit: Unit.percentage, }), priceLine({ ctx, number: 0, unit: Unit.percentage }), ], }, { name: "Countdown", title: "Next Adjustment", bottom: [ line({ metric: blocks.difficulty.blocksBeforeNextAdjustment, name: "Before Next", color: colors.indigo, unit: Unit.blocks, }), line({ metric: blocks.difficulty.daysBeforeNextAdjustment, name: "Before Next", color: colors.purple, unit: Unit.days, }), ], }, ], }, // Economics group { name: "Economics", tree: [ { name: "Hash Price", title: "Hash Price", bottom: [ line({ metric: blocks.mining.hashPriceThs, name: "TH/s", color: colors.emerald, unit: Unit.usdPerThsPerDay, }), line({ metric: blocks.mining.hashPricePhs, name: "PH/s", color: colors.emerald, unit: Unit.usdPerPhsPerDay, }), line({ metric: blocks.mining.hashPriceRebound, name: "Rebound", color: colors.yellow, unit: Unit.percentage, }), line({ metric: blocks.mining.hashPriceThsMin, name: "TH/s Min", color: colors.red, unit: Unit.usdPerThsPerDay, options: { lineStyle: 1 }, }), line({ metric: blocks.mining.hashPricePhsMin, name: "PH/s Min", color: colors.red, unit: Unit.usdPerPhsPerDay, options: { lineStyle: 1 }, }), ], }, { name: "Hash Value", title: "Hash Value", bottom: [ line({ metric: blocks.mining.hashValueThs, name: "TH/s", color: colors.orange, unit: Unit.satsPerThsPerDay, }), line({ metric: blocks.mining.hashValuePhs, name: "PH/s", color: colors.orange, unit: Unit.satsPerPhsPerDay, }), line({ metric: blocks.mining.hashValueRebound, name: "Rebound", color: colors.yellow, unit: Unit.percentage, }), line({ metric: blocks.mining.hashValueThsMin, name: "TH/s Min", color: colors.red, unit: Unit.satsPerThsPerDay, options: { lineStyle: 1 }, }), line({ metric: blocks.mining.hashValuePhsMin, name: "PH/s Min", color: colors.red, unit: Unit.satsPerPhsPerDay, options: { lineStyle: 1 }, }), ], }, ], }, // Halving (at top level for quick access) { name: "Halving", title: "Halving", bottom: [ line({ metric: blocks.halving.epoch, name: "Epoch", color: colors.purple, unit: Unit.epoch, }), line({ metric: blocks.halving.blocksBeforeNextHalving, name: "Before Next", unit: Unit.blocks, }), line({ metric: blocks.halving.daysBeforeNextHalving, name: "Before Next", color: colors.blue, unit: Unit.days, }), ], }, ], }, // Pools { name: "Pools", tree: [ // Compare section (major pools only) { name: "Compare", tree: [ { name: "Dominance", title: "Pool Dominance (Major Pools)", bottom: poolEntries .filter(([key]) => MAJOR_POOL_IDS.includes(key.toLowerCase())) .map(([key, pool]) => { const poolName = brk.POOL_ID_TO_POOL_NAME[ /** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ ( key.toLowerCase() ) ] || key; return line({ metric: pool._1mDominance, name: poolName, unit: Unit.percentage, }); }), }, { name: "Blocks Mined", title: "Blocks Mined - 1m (Major Pools)", bottom: poolEntries .filter(([key]) => MAJOR_POOL_IDS.includes(key.toLowerCase())) .map(([key, pool]) => { const poolName = brk.POOL_ID_TO_POOL_NAME[ /** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ ( key.toLowerCase() ) ] || key; return line({ metric: pool._1mBlocksMined, name: poolName, unit: Unit.count, }); }), }, ], }, // AntPool & friends - pools sharing block templates { name: "AntPool & Friends", tree: [ { name: "Dominance", title: "AntPool & Friends Dominance", bottom: poolEntries .filter(([key]) => ANTPOOL_AND_FRIENDS_IDS.includes(key.toLowerCase()), ) .map(([key, pool]) => { const poolName = brk.POOL_ID_TO_POOL_NAME[ /** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ ( key.toLowerCase() ) ] || key; return line({ metric: pool._1mDominance, name: poolName, unit: Unit.percentage, }); }), }, { name: "Blocks Mined", title: "AntPool & Friends Blocks Mined (1m)", bottom: poolEntries .filter(([key]) => ANTPOOL_AND_FRIENDS_IDS.includes(key.toLowerCase()), ) .map(([key, pool]) => { const poolName = brk.POOL_ID_TO_POOL_NAME[ /** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ ( key.toLowerCase() ) ] || key; return line({ metric: pool._1mBlocksMined, name: poolName, unit: Unit.count, }); }), }, ], }, // Individual pools ...poolsTree, ], }, ], }; }