/** Network section - On-chain activity and health */ import { colors } from "../utils/colors.js"; import { brk } from "../client.js"; import { Unit } from "../utils/units.js"; import { entries } from "../utils/array.js"; import { priceLine } from "./constants.js"; import { line, dots, baseline, fromSupplyPattern, chartsFromFullPerBlock, chartsFromCount, chartsFromSumPerBlock, rollingWindowsTree, distributionWindowsTree, mapWindows, ROLLING_WINDOWS, chartsFromBlockAnd6b, percentRatio, percentRatioDots, rollingPercentRatioTree, } from "./series.js"; import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js"; /** * Create Network section * @returns {PartialOptionsGroup} */ export function createNetworkSection() { const { blocks, transactions, inputs, outputs, scripts, supply, addresses, cohorts, } = brk.metrics; const st = colors.scriptType; // Addressable types - newest to oldest (for addresses/counts that only support addressable types) const addressTypes = /** @type {const} */ ([ { key: "p2a", name: "P2A", color: st.p2a, defaultActive: false }, { key: "p2tr", name: "P2TR", color: st.p2tr, defaultActive: true }, { key: "p2wsh", name: "P2WSH", color: st.p2wsh, defaultActive: true }, { key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh, defaultActive: true }, { key: "p2sh", name: "P2SH", color: st.p2sh, defaultActive: true }, { key: "p2pkh", name: "P2PKH", color: st.p2pkh, defaultActive: true }, { key: "p2pk33", name: "P2PK33", color: st.p2pk33, defaultActive: false }, { key: "p2pk65", name: "P2PK65", color: st.p2pk65, defaultActive: false }, ]); // Non-addressable script types const nonAddressableTypes = /** @type {const} */ ([ { key: "p2ms", name: "P2MS", color: st.p2ms, defaultActive: false }, { key: "opReturn", name: "OP_RETURN", color: st.opReturn, defaultActive: false, }, { key: "emptyOutput", name: "Empty", color: st.empty, defaultActive: false, }, { key: "unknownOutput", name: "Unknown", color: st.unknown, defaultActive: false, }, ]); // All script types = addressable + non-addressable const scriptTypes = [...addressTypes, ...nonAddressableTypes]; // Address type groups (by era) const taprootAddresses = /** @type {const} */ ([ { key: "p2a", name: "P2A", color: st.p2a }, { key: "p2tr", name: "P2TR", color: st.p2tr }, ]); const segwitAddresses = /** @type {const} */ ([ { key: "p2wsh", name: "P2WSH", color: st.p2wsh }, { key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh }, ]); const legacyAddresses = /** @type {const} */ ([ { key: "p2sh", name: "P2SH", color: st.p2sh }, { key: "p2pkh", name: "P2PKH", color: st.p2pkh }, { key: "p2pk33", name: "P2PK33", color: st.p2pk33 }, { key: "p2pk65", name: "P2PK65", color: st.p2pk65 }, ]); // Transacting types (transaction participation) const transactingTypes = /** @type {const} */ ([ { key: "sending", name: "Sending", title: "Unique Sending Addresses per Block", compareTitle: "Unique Sending Addresses per Block by Type", }, { key: "receiving", name: "Receiving", title: "Unique Receiving Addresses per Block", compareTitle: "Unique Receiving Addresses per Block by Type", }, { key: "both", name: "Sending & Receiving", title: "Unique Addresses Sending & Receiving per Block", compareTitle: "Unique Addresses Sending & Receiving per Block by Type", }, ]); const countTypes = /** @type {const} */ ([ { name: "Funded", title: "Address Count by Type", /** @param {AddressableType} t */ getMetric: (t) => addresses.funded[t], }, { name: "Empty", title: "Empty Address Count by Type", /** @param {AddressableType} t */ getMetric: (t) => addresses.empty[t], }, { name: "Total", title: "Total Address Count by Type", /** @param {AddressableType} t */ getMetric: (t) => addresses.total[t], }, ]); /** * 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: addresses.funded[key], name: "Funded", unit: Unit.count, }), line({ metric: addresses.empty[key], name: "Empty", color: colors.gray, unit: Unit.count, defaultActive: false, }), line({ metric: addresses.total[key], name: "Total", color: colors.default, unit: Unit.count, defaultActive: false, }), ], }, { name: "Trends", tree: [ rollingWindowsTree({ windows: addresses.delta[key].absolute, title: `${titlePrefix}Address Count Change`, unit: Unit.count, series: baseline, }), { name: "New", tree: (() => { const p = addresses.new[key]; const t = `${titlePrefix}New Address Count`; return [ { name: "Sum", title: t, bottom: [ line({ metric: p.base, name: "base", unit: Unit.count }), ], }, rollingWindowsTree({ windows: p.sum, title: t, unit: Unit.count, }), { name: "Cumulative", title: `${t} (Total)`, bottom: [ line({ metric: p.cumulative, name: "all-time", unit: Unit.count, }), ], }, ]; })(), }, { name: "Reactivated", tree: [ { name: "Base", title: `${titlePrefix}Reactivated Addresses per Block`, bottom: [ dots({ metric: addresses.activity[key].reactivated.height, name: "base", unit: Unit.count, }), line({ metric: addresses.activity[key].reactivated._24h, name: "24h avg", color: colors.stat.avg, unit: Unit.count, }), ], }, rollingWindowsTree({ windows: addresses.activity[key].reactivated, title: `${titlePrefix}Reactivated Addresses`, unit: Unit.count, }), ], }, rollingPercentRatioTree({ windows: addresses.delta[key].rate, title: `${titlePrefix}Address Growth Rate`, }), ], }, { name: "Transacting", tree: transactingTypes.map((t) => ({ name: t.name, tree: [ { name: "Base", title: `${titlePrefix}${t.title}`, bottom: [ dots({ metric: addresses.activity[key][t.key].height, name: "base", unit: Unit.count, }), line({ metric: addresses.activity[key][t.key]._24h, name: "24h avg", color: colors.stat.avg, unit: Unit.count, }), ], }, rollingWindowsTree({ windows: addresses.activity[key][t.key], title: `${titlePrefix}${t.title.replace(" per Block", "")}`, unit: Unit.count, }), ], })), }, ]; /** * Create Compare charts for an address group * @template {AddressableType} K * @param {string} groupName * @param {ReadonlyArray<{key: K, name: string, color: Color}>} types */ const createAddressCompare = (groupName, types) => ({ name: "Compare", tree: [ { name: "Count", tree: countTypes.map((c) => ({ name: c.name, title: `${groupName} ${c.title}`, bottom: types.map((t) => line({ metric: c.getMetric(t.key), name: t.name, color: t.color, unit: Unit.count, }), ), })), }, { name: "New", title: `${groupName} New Address Count`, bottom: types.flatMap((t) => [ line({ metric: addresses.new[t.key].base, name: t.name, color: t.color, unit: Unit.count, }), line({ metric: addresses.new[t.key].sum._24h, name: t.name, color: t.color, unit: Unit.count, }), ]), }, { name: "Reactivated", tree: [ { name: "Base", title: `${groupName} Reactivated Addresses per Block`, bottom: types.map((t) => line({ metric: addresses.activity[t.key].reactivated.height, name: t.name, color: t.color, unit: Unit.count, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${groupName} Reactivated Addresses (${w.name})`, bottom: types.map((t) => line({ metric: addresses.activity[t.key].reactivated[w.key], name: t.name, color: t.color, unit: Unit.count, }), ), })), ], }, { name: "Growth Rate", tree: ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${groupName} Address Growth Rate (${w.name})`, bottom: types.flatMap((t) => percentRatio({ pattern: addresses.delta[t.key].rate[w.key], name: t.name, color: t.color, }), ), })), }, { name: "Transacting", tree: transactingTypes.map((tr) => ({ name: tr.name, tree: [ { name: "Base", title: `${groupName} ${tr.compareTitle}`, bottom: types.map((t) => line({ metric: addresses.activity[t.key][tr.key].height, name: t.name, color: t.color, unit: Unit.count, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${groupName} ${tr.compareTitle} (${w.name})`, bottom: types.map((t) => line({ metric: addresses.activity[t.key][tr.key][w.key], name: t.name, color: t.color, unit: Unit.count, }), ), })), ], })), }, ], }); // Script type groups for Output Counts const legacyScripts = legacyAddresses.slice(1); // p2pkh, p2pk33, p2pk65 const scriptHashScripts = [legacyAddresses[0], nonAddressableTypes[0]]; // p2sh, p2ms const segwitScripts = [ /** @type {const} */ ({ key: "segwit", name: "All SegWit", color: colors.segwit, }), ...segwitAddresses, ]; const otherScripts = nonAddressableTypes.slice(1); // opreturn, empty, unknown /** * Create Compare charts for a script group * @template {keyof typeof scripts.count} K * @param {string} groupName * @param {ReadonlyArray<{key: K, name: string, color: Color}>} types */ const createScriptCompare = (groupName, types) => ({ name: "Compare", tree: [ { name: "Sum", title: `${groupName} Output Count`, bottom: types.map((t) => line({ metric: /** @type {CountPattern} */ (scripts.count[t.key]) .sum._24h, name: t.name, color: t.color, unit: Unit.count, }), ), }, { name: "Cumulative", title: `${groupName} Output Count (Total)`, bottom: types.map((t) => line({ metric: /** @type {CountPattern} */ (scripts.count[t.key]) .cumulative, name: t.name, color: t.color, unit: Unit.count, }), ), }, ], }); return { name: "Network", tree: [ // Supply { name: "Supply", tree: [ { name: "Circulating", title: "Circulating Supply", bottom: fromSupplyPattern({ pattern: supply.circulating, title: "Supply", }), }, { name: "Inflation", title: "Inflation Rate", bottom: percentRatioDots({ pattern: supply.inflationRate, name: "Rate", }), }, { name: "Market Cap", tree: [ { name: "Base", title: "Market Cap", bottom: [ line({ metric: supply.marketCap.usd, name: "Market Cap", unit: Unit.usd, }), ], }, rollingWindowsTree({ windows: mapWindows( supply.marketCap.delta.absolute, (c) => c.usd, ), title: "Market Cap Change", unit: Unit.usd, series: baseline, }), rollingPercentRatioTree({ windows: supply.marketCap.delta.rate, title: "Market Cap Growth Rate", }), ], }, { name: "Hodled or Lost", title: "Hodled or Lost Supply", bottom: satsBtcUsd({ pattern: supply.hodledOrLost, name: "Supply", }), }, rollingWindowsTree({ windows: supply.marketMinusRealizedCapGrowthRate, title: "Market - Realized Cap Growth Rate", unit: Unit.ratio, }), { name: "Unspendable", tree: [ { name: "Base", title: "Unspendable Supply", bottom: satsBtcUsdFrom({ source: supply.burned.unspendable, key: "base", name: "sum", }), }, { name: "Rolling", tree: [ { name: "Compare", title: "Unspendable Supply Rolling", bottom: ROLLING_WINDOWS.flatMap((w) => satsBtcUsd({ pattern: supply.burned.unspendable.sum[w.key], name: w.name, color: w.color, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Unspendable Supply ${w.name}`, bottom: satsBtcUsd({ pattern: supply.burned.unspendable.sum[w.key], name: w.name, color: w.color, }), })), ], }, { name: "Cumulative", title: "Unspendable Supply (Total)", bottom: satsBtcUsdFrom({ source: supply.burned.unspendable, key: "cumulative", name: "all-time", }), }, ], }, { name: "OP_RETURN", tree: [ { name: "Base", title: "OP_RETURN Burned", bottom: satsBtcUsd({ pattern: scripts.value.opReturn.base, name: "sum", }), }, { name: "Rolling", tree: [ { name: "Compare", title: "OP_RETURN Burned Rolling", bottom: ROLLING_WINDOWS.flatMap((w) => satsBtcUsd({ pattern: scripts.value.opReturn.sum[w.key], name: w.name, color: w.color, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `OP_RETURN Burned ${w.name}`, bottom: satsBtcUsd({ pattern: scripts.value.opReturn.sum[w.key], name: w.name, color: w.color, }), })), ], }, { name: "Cumulative", title: "OP_RETURN Burned (Total)", bottom: satsBtcUsd({ pattern: scripts.value.opReturn.cumulative, name: "all-time", }), }, ], }, ], }, // Transactions { name: "Transactions", tree: [ { name: "Count", tree: chartsFromFullPerBlock({ pattern: transactions.count.total, title: "Transaction Count", unit: Unit.count, }), }, { name: "Fees", tree: [ { name: "Fee Rate", tree: chartsFromBlockAnd6b({ pattern: transactions.fees.feeRate, title: "Transaction Fee Rate", unit: Unit.feeRate, }), }, { name: "Fee", tree: chartsFromBlockAnd6b({ pattern: transactions.fees.fee, title: "Transaction Fee", unit: Unit.sats, }), }, ], }, { name: "Volume", tree: [ { name: "Transferred", title: "Transaction Volume", bottom: [ ...satsBtcUsd({ pattern: transactions.volume.sentSum.base, name: "Sent", }), ...satsBtcUsd({ pattern: transactions.volume.receivedSum.base, name: "Received", color: colors.entity.output, }), ], }, { name: "Sent Rolling", tree: [ { name: "Compare", title: "Sent Volume Rolling", bottom: ROLLING_WINDOWS.flatMap((w) => satsBtcUsd({ pattern: transactions.volume.sentSum.sum[w.key], name: w.name, color: w.color, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Sent Volume ${w.name}`, bottom: satsBtcUsd({ pattern: transactions.volume.sentSum.sum[w.key], name: w.name, color: w.color, }), })), ], }, { name: "Sent Cumulative", title: "Sent Volume (Total)", bottom: satsBtcUsd({ pattern: transactions.volume.sentSum.cumulative, name: "all-time", }), }, { name: "Received Rolling", tree: [ { name: "Compare", title: "Received Volume Rolling", bottom: ROLLING_WINDOWS.flatMap((w) => satsBtcUsd({ pattern: transactions.volume.receivedSum.sum[w.key], name: w.name, color: w.color, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Received Volume ${w.name}`, bottom: satsBtcUsd({ pattern: transactions.volume.receivedSum.sum[w.key], name: w.name, color: w.color, }), })), ], }, { name: "Received Cumulative", title: "Received Volume (Total)", bottom: satsBtcUsd({ pattern: transactions.volume.receivedSum.cumulative, name: "all-time", }), }, ], }, { name: "Size", tree: [ { name: "Weight", tree: chartsFromBlockAnd6b({ pattern: transactions.size.weight, title: "Transaction Weight", unit: Unit.wu, }), }, { name: "vSize", tree: chartsFromBlockAnd6b({ pattern: transactions.size.vsize, title: "Transaction vSize", unit: Unit.vb, }), }, ], }, { name: "Versions", tree: [ { name: "Base", title: "Transaction Versions", bottom: entries(transactions.versions).map( ([v, data], i, arr) => line({ metric: data.base, name: v, color: colors.at(i, arr.length), unit: Unit.count, }), ), }, { name: "Rolling", tree: [ { name: "Compare", title: "Transaction Versions Rolling", bottom: entries(transactions.versions).flatMap( ([v, data], i, arr) => ROLLING_WINDOWS.map((w) => line({ metric: data.sum[w.key], name: `${v} ${w.name}`, color: colors.at(i, arr.length), unit: Unit.count, }), ), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Transaction Versions (${w.name})`, bottom: entries(transactions.versions).map( ([v, data], i, arr) => line({ metric: data.sum[w.key], name: v, color: colors.at(i, arr.length), unit: Unit.count, }), ), })), ], }, { name: "Cumulative", title: "Transaction Versions (Total)", bottom: entries(transactions.versions).map( ([v, data], i, arr) => line({ metric: data.cumulative, name: v, color: colors.at(i, arr.length), unit: Unit.count, }), ), }, ], }, { name: "Velocity", title: "Transaction Velocity", bottom: [ line({ metric: supply.velocity.native, name: "BTC", unit: Unit.ratio, }), line({ metric: supply.velocity.fiat, name: "USD", color: colors.usd, unit: Unit.ratio, }), ], }, ], }, // Blocks { name: "Blocks", tree: [ { name: "Count", tree: [ { name: "Base", title: "Block Count", bottom: [ line({ metric: blocks.count.total.base, name: "base", unit: Unit.count, }), line({ metric: blocks.count.target, name: "Target", color: colors.gray, unit: Unit.count, options: { lineStyle: 4 }, }), ], }, rollingWindowsTree({ windows: blocks.count.total.sum, title: "Block Count", unit: Unit.count, }), { name: "Cumulative", title: "Block Count (Total)", bottom: [ line({ metric: blocks.count.total.cumulative, name: "all-time", unit: Unit.count, }), ], }, ], }, { name: "Interval", tree: [ { name: "Base", title: "Block Interval", bottom: [ dots({ metric: blocks.interval.height, name: "base", unit: Unit.secs, }), line({ metric: blocks.interval._24h, name: "24h avg", color: colors.stat.avg, unit: Unit.secs, }), priceLine({ unit: Unit.secs, name: "Target", number: 600 }), ], }, rollingWindowsTree({ windows: blocks.interval, title: "Block Interval", unit: Unit.secs, }), ], }, { name: "Size", tree: [ { name: "Base", title: "Block Size", bottom: [ line({ metric: blocks.size.total, name: "base", unit: Unit.bytes, }), ], }, rollingWindowsTree({ windows: blocks.size.sum, title: "Block Size", unit: Unit.bytes, }), distributionWindowsTree({ pattern: blocks.size, base: blocks.size.total, title: "Block Size", unit: Unit.bytes, }), { name: "Cumulative", title: "Block Size (Total)", bottom: [ line({ metric: blocks.size.cumulative, name: "all-time", unit: Unit.bytes, }), ], }, ], }, { name: "Weight", tree: [ { name: "Base", title: "Block Weight", bottom: [ line({ metric: blocks.weight.raw, name: "base", unit: Unit.wu, }), ], }, rollingWindowsTree({ windows: blocks.weight.sum, title: "Block Weight", unit: Unit.wu, }), distributionWindowsTree({ pattern: blocks.weight, base: blocks.weight.raw, title: "Block Weight", unit: Unit.wu, }), { name: "Cumulative", title: "Block Weight (Total)", bottom: [ line({ metric: blocks.weight.cumulative, name: "all-time", unit: Unit.wu, }), ], }, ], }, { name: "vBytes", tree: [ { name: "Base", title: "Block vBytes", bottom: [ line({ metric: blocks.vbytes.base, name: "base", unit: Unit.vb, }), ], }, rollingWindowsTree({ windows: blocks.vbytes.sum, title: "Block vBytes", unit: Unit.vb, }), distributionWindowsTree({ pattern: blocks.vbytes, base: blocks.vbytes.base, title: "Block vBytes", unit: Unit.vb, }), { name: "Cumulative", title: "Block vBytes (Total)", bottom: [ line({ metric: blocks.vbytes.cumulative, name: "all-time", unit: Unit.vb, }), ], }, ], }, { name: "Fullness", title: "Block Fullness", bottom: percentRatioDots({ pattern: blocks.fullness, name: "base", }), }, { name: "Difficulty", tree: [ { name: "Base", title: "Mining Difficulty", bottom: [ line({ metric: blocks.difficulty.value, name: "Difficulty", unit: Unit.count, }), ], }, { name: "Adjustment", title: "Difficulty Adjustment", bottom: percentRatioDots({ pattern: blocks.difficulty.adjustment, name: "Adjustment", }), }, ], }, ], }, // UTXOs { name: "UTXOs", tree: [ { name: "Count", title: "UTXO Count", bottom: [ line({ metric: outputs.count.unspent, name: "Count", unit: Unit.count, }), ], }, { name: "30d Change", title: "UTXO Count 30d Change", bottom: [ baseline({ metric: cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m, name: "30d Change", unit: Unit.count, }), ], }, { name: "Flow", title: "UTXO Flow", bottom: [ line({ metric: outputs.count.total.sum, name: "Created", color: colors.entity.output, unit: Unit.count, }), line({ metric: inputs.count.sum, name: "Spent", color: colors.entity.input, unit: Unit.count, }), ], }, ], }, { name: "Inputs", tree: chartsFromSumPerBlock({ pattern: inputs.count, title: "Input Count", unit: Unit.count, }), }, { name: "Outputs", tree: chartsFromSumPerBlock({ pattern: outputs.count.total, title: "Output Count", unit: Unit.count, }), }, { name: "Activity Rate", title: "Activity Rate", bottom: [ dots({ metric: transactions.volume.txPerSec, name: "TX/sec", color: colors.entity.tx, unit: Unit.perSec, }), dots({ metric: transactions.volume.inputsPerSec, name: "Inputs/sec", color: colors.entity.input, unit: Unit.perSec, }), dots({ metric: transactions.volume.outputsPerSec, name: "Outputs/sec", color: colors.entity.output, unit: Unit.perSec, }), ], }, // Addresses { name: "Addresses", tree: [ // Overview - global metrics for all addresses { name: "Overview", tree: createAddressMetricsTree("all", "") }, // Top-level Compare - all types { name: "Compare", tree: [ { name: "Count", tree: countTypes.map((c) => ({ name: c.name, title: c.title, bottom: addressTypes.map((t) => line({ metric: c.getMetric(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: addresses.new[t.key].base, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), line({ metric: addresses.new[t.key].sum._24h, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ]), }, { name: "Reactivated", tree: [ { name: "Base", title: "Reactivated Addresses per Block by Type", bottom: addressTypes.map((t) => line({ metric: addresses.activity[t.key].reactivated.height, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Reactivated Addresses by Type (${w.name})`, bottom: addressTypes.map((t) => line({ metric: addresses.activity[t.key].reactivated[w.key], name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), })), ], }, { name: "Growth Rate", tree: ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Address Growth Rate by Type (${w.name})`, bottom: addressTypes.flatMap((t) => percentRatio({ pattern: addresses.delta[t.key].rate[w.key], name: t.name, color: t.color, defaultActive: t.defaultActive, }), ), })), }, { name: "Transacting", tree: transactingTypes.map((tr) => ({ name: tr.name, tree: [ { name: "Base", title: tr.compareTitle, bottom: addressTypes.map((t) => line({ metric: addresses.activity[t.key][tr.key].height, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${tr.compareTitle} (${w.name})`, bottom: addressTypes.map((t) => line({ metric: addresses.activity[t.key][tr.key][w.key], name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), })), ], })), }, ], }, // Legacy (pre-SegWit) { name: "Legacy", tree: [ createAddressCompare("Legacy", legacyAddresses), ...legacyAddresses.map((t) => ({ name: t.name, tree: createAddressMetricsTree(t.key, `${t.name} `), })), ], }, // SegWit { name: "SegWit", tree: [ createAddressCompare("SegWit", segwitAddresses), ...segwitAddresses.map((t) => ({ name: t.name, tree: createAddressMetricsTree(t.key, `${t.name} `), })), ], }, // Taproot { name: "Taproot", tree: [ createAddressCompare("Taproot", taprootAddresses), ...taprootAddresses.map((t) => ({ name: t.name, tree: createAddressMetricsTree(t.key, `${t.name} `), })), ], }, ], }, // Scripts { name: "Scripts", tree: [ { name: "By Type", tree: [ // Compare section { name: "Compare", tree: [ { name: "Sum", title: "Output Count by Script Type", bottom: scriptTypes.map((t) => line({ metric: /** @type {CountPattern} */ ( scripts.count[t.key] ).sum._24h, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), }, { name: "Cumulative", title: "Output Count by Script Type (Total)", bottom: scriptTypes.map((t) => line({ metric: scripts.count[t.key].cumulative, name: t.name, color: t.color, unit: Unit.count, defaultActive: t.defaultActive, }), ), }, ], }, { name: "Legacy", tree: [ createScriptCompare("Legacy", legacyScripts), ...legacyScripts.map((t) => ({ name: t.name, tree: chartsFromCount({ pattern: /** @type {CountPattern} */ ( scripts.count[t.key] ), title: `${t.name} Output Count`, unit: Unit.count, }), })), ], }, { name: "Script Hash", tree: [ createScriptCompare("Script Hash", scriptHashScripts), ...scriptHashScripts.map((t) => ({ name: t.name, tree: chartsFromCount({ pattern: /** @type {CountPattern} */ ( scripts.count[t.key] ), title: `${t.name} Output Count`, unit: Unit.count, }), })), ], }, { name: "SegWit", tree: [ createScriptCompare("SegWit", segwitScripts), ...segwitScripts.map((t) => ({ name: t.name, tree: chartsFromCount({ pattern: /** @type {CountPattern} */ ( scripts.count[t.key] ), title: `${t.name} Output Count`, unit: Unit.count, }), })), ], }, { name: "Taproot", tree: [ createScriptCompare("Taproot", taprootAddresses), ...taprootAddresses.map((t) => ({ name: t.name, tree: chartsFromCount({ pattern: /** @type {CountPattern} */ ( scripts.count[t.key] ), title: `${t.name} Output Count`, unit: Unit.count, }), })), ], }, { name: "Other", tree: [ createScriptCompare("Other", otherScripts), ...otherScripts.map((t) => ({ name: t.name, tree: chartsFromCount({ pattern: /** @type {CountPattern} */ ( scripts.count[t.key] ), title: `${t.name} Output Count`, unit: Unit.count, }), })), ], }, ], }, { name: "Adoption", tree: [ { name: "Compare", title: "Script Adoption", bottom: [ line({ metric: scripts.adoption.segwit.percent, name: "SegWit", color: colors.segwit, unit: Unit.percentage, }), line({ metric: scripts.adoption.segwit.ratio, name: "SegWit", color: colors.segwit, unit: Unit.ratio, }), line({ metric: scripts.adoption.taproot.percent, name: "Taproot", color: taprootAddresses[1].color, unit: Unit.percentage, }), line({ metric: scripts.adoption.taproot.ratio, name: "Taproot", color: taprootAddresses[1].color, unit: Unit.ratio, }), ], }, { name: "SegWit", title: "SegWit Adoption", bottom: [ line({ metric: scripts.adoption.segwit.percent, name: "Adoption", unit: Unit.percentage, }), line({ metric: scripts.adoption.segwit.ratio, name: "Adoption", unit: Unit.ratio, }), ], }, { name: "Taproot", title: "Taproot Adoption", bottom: [ line({ metric: scripts.adoption.taproot.percent, name: "Adoption", unit: Unit.percentage, }), line({ metric: scripts.adoption.taproot.ratio, name: "Adoption", unit: Unit.ratio, }), ], }, ], }, ], }, ], }; }