mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-12 11:38:13 -07:00
website: redesign part 25
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
createCohortSeries,
|
||||
createCohortSeriesFromKeys,
|
||||
} from "./cohort-series.js";
|
||||
import { addressableTypes, amountRanges } from "./groups.js";
|
||||
import { createRollingWindowSeries } from "./rolling-windows.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
export const fundedSeries = createCohortSeries([
|
||||
{
|
||||
label: "Funded",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.addrs.funded.all,
|
||||
},
|
||||
]);
|
||||
|
||||
export const newSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.new.all.sum[key],
|
||||
);
|
||||
|
||||
export const changeSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.delta.all.absolute[key],
|
||||
);
|
||||
|
||||
export const growthRateSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.delta.all.rate[key].percent,
|
||||
);
|
||||
|
||||
export const activeSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.activity.all.active[key],
|
||||
);
|
||||
|
||||
export const sendingSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.activity.all.sending[key],
|
||||
);
|
||||
|
||||
export const receivingSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.activity.all.receiving[key],
|
||||
);
|
||||
|
||||
export const bidirectionalSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.activity.all.bidirectional[key],
|
||||
);
|
||||
|
||||
export const reactivatedSeries = createRollingWindowSeries(
|
||||
(key) => (client) => client.series.addrs.activity.all.reactivated[key],
|
||||
);
|
||||
|
||||
export const stateSeries = createCohortSeries([
|
||||
{
|
||||
label: "Funded",
|
||||
color: colors.green,
|
||||
metric: (client) => client.series.addrs.funded.all,
|
||||
},
|
||||
{
|
||||
label: "Empty",
|
||||
color: colors.red,
|
||||
metric: (client) => client.series.addrs.empty.all,
|
||||
},
|
||||
{
|
||||
label: "Total",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.addrs.total.all,
|
||||
},
|
||||
]);
|
||||
|
||||
export const balanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) => client.series.cohorts.addr.amountRange[key].addrCount.base,
|
||||
);
|
||||
|
||||
export const typeSeries = createCohortSeriesFromKeys(
|
||||
addressableTypes,
|
||||
(key) => (client) => client.series.addrs.funded[key],
|
||||
);
|
||||
|
||||
export const reuseSeries = createCohortSeries([
|
||||
{
|
||||
label: "Reused",
|
||||
color: colors.yellow,
|
||||
metric: (client) => client.series.addrs.reused.count.funded.all,
|
||||
},
|
||||
{
|
||||
label: "Respent",
|
||||
color: colors.fuchsia,
|
||||
metric: (client) => client.series.addrs.respent.count.funded.all,
|
||||
},
|
||||
{
|
||||
label: "Exposed",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.addrs.exposed.count.funded.all,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
createCohortSeries,
|
||||
createCohortSeriesFromKeys,
|
||||
} from "./cohort-series.js";
|
||||
import {
|
||||
ageRanges,
|
||||
amountRanges,
|
||||
classes,
|
||||
epochs,
|
||||
spendableTypes,
|
||||
} from "./groups.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
export const capitalizationSeries = createCohortSeries([
|
||||
{
|
||||
label: "Market cap",
|
||||
color: colors.green,
|
||||
metric: (client) => client.series.supply.marketCap.usd,
|
||||
},
|
||||
{
|
||||
label: "Realized cap",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.cohorts.utxo.all.realized.cap.usd,
|
||||
},
|
||||
]);
|
||||
|
||||
const [marketCap, realizedCap] = capitalizationSeries;
|
||||
|
||||
export const marketCapSeries = [marketCap];
|
||||
|
||||
export const realizedCapSeries = [realizedCap];
|
||||
|
||||
export const marketCapProfitabilitySeries = createCohortSeries([
|
||||
{
|
||||
label: "In profit",
|
||||
color: colors.green,
|
||||
metric: (client) =>
|
||||
client.series.cohorts.utxo.profitability.profit.all.supply.all.usd,
|
||||
},
|
||||
{
|
||||
label: "In loss",
|
||||
color: colors.red,
|
||||
metric: (client) =>
|
||||
client.series.cohorts.utxo.profitability.loss.all.supply.all.usd,
|
||||
},
|
||||
]);
|
||||
|
||||
export const realizedCapProfitabilitySeries = createCohortSeries([
|
||||
{
|
||||
label: "In profit",
|
||||
color: colors.green,
|
||||
metric: (client) =>
|
||||
client.series.cohorts.utxo.profitability.profit.all.realizedCap.all,
|
||||
},
|
||||
{
|
||||
label: "In loss",
|
||||
color: colors.red,
|
||||
metric: (client) =>
|
||||
client.series.cohorts.utxo.profitability.loss.all.realizedCap.all,
|
||||
},
|
||||
]);
|
||||
|
||||
export const marketCapTermSeries = createCohortSeries([
|
||||
{
|
||||
label: "STH",
|
||||
color: colors.yellow,
|
||||
metric: (client) => client.series.cohorts.utxo.sth.supply.total.usd,
|
||||
},
|
||||
{
|
||||
label: "LTH",
|
||||
color: colors.fuchsia,
|
||||
metric: (client) => client.series.cohorts.utxo.lth.supply.total.usd,
|
||||
},
|
||||
]);
|
||||
|
||||
export const realizedCapTermSeries = createCohortSeries([
|
||||
{
|
||||
label: "STH",
|
||||
color: colors.yellow,
|
||||
metric: (client) => client.series.cohorts.utxo.sth.realized.cap.usd,
|
||||
},
|
||||
{
|
||||
label: "LTH",
|
||||
color: colors.fuchsia,
|
||||
metric: (client) => client.series.cohorts.utxo.lth.realized.cap.usd,
|
||||
},
|
||||
]);
|
||||
|
||||
export const marketCapAgeSeries = createCohortSeriesFromKeys(
|
||||
ageRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.ageRange[key].supply.total.usd,
|
||||
);
|
||||
|
||||
export const realizedCapAgeSeries = createCohortSeriesFromKeys(
|
||||
ageRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.ageRange[key].realized.cap.usd,
|
||||
);
|
||||
|
||||
export const marketCapUtxoBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.amountRange[key].supply.total.usd,
|
||||
);
|
||||
|
||||
export const realizedCapUtxoBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.amountRange[key].realized.cap.usd,
|
||||
);
|
||||
|
||||
export const marketCapAddressBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.addr.amountRange[key].supply.total.usd,
|
||||
);
|
||||
|
||||
export const realizedCapAddressBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.addr.amountRange[key].realized.cap.usd,
|
||||
);
|
||||
|
||||
export const marketCapTypeSeries = createCohortSeriesFromKeys(
|
||||
spendableTypes,
|
||||
(key) => (client) => client.series.cohorts.utxo.type[key].supply.total.usd,
|
||||
);
|
||||
|
||||
export const realizedCapTypeSeries = createCohortSeriesFromKeys(
|
||||
spendableTypes,
|
||||
(key) => (client) => client.series.cohorts.utxo.type[key].realized.cap.usd,
|
||||
);
|
||||
|
||||
export const marketCapEpochSeries = createCohortSeriesFromKeys(
|
||||
epochs,
|
||||
(key) => (client) => client.series.cohorts.utxo.epoch[key].supply.total.usd,
|
||||
);
|
||||
|
||||
export const realizedCapEpochSeries = createCohortSeriesFromKeys(
|
||||
epochs,
|
||||
(key) => (client) => client.series.cohorts.utxo.epoch[key].realized.cap.usd,
|
||||
);
|
||||
|
||||
export const marketCapClassSeries = createCohortSeriesFromKeys(
|
||||
classes,
|
||||
(key) => (client) => client.series.cohorts.utxo.class[key].supply.total.usd,
|
||||
);
|
||||
|
||||
export const realizedCapClassSeries = createCohortSeriesFromKeys(
|
||||
classes,
|
||||
(key) => (client) => client.series.cohorts.utxo.class[key].realized.cap.usd,
|
||||
);
|
||||
@@ -0,0 +1,53 @@
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
const palette = [
|
||||
colors.red,
|
||||
colors.orange,
|
||||
colors.amber,
|
||||
colors.yellow,
|
||||
colors.avocado,
|
||||
colors.lime,
|
||||
colors.green,
|
||||
colors.emerald,
|
||||
colors.teal,
|
||||
colors.cyan,
|
||||
colors.sky,
|
||||
colors.blue,
|
||||
colors.indigo,
|
||||
colors.violet,
|
||||
colors.purple,
|
||||
colors.fuchsia,
|
||||
colors.pink,
|
||||
colors.rose,
|
||||
];
|
||||
|
||||
/** @param {number} index */
|
||||
function colorAt(index) {
|
||||
return palette[index % palette.length];
|
||||
}
|
||||
|
||||
/** @param {readonly { label: string, color?: ChartColor, metric: Metric }[]} items */
|
||||
export function createCohortSeries(items) {
|
||||
return items.map(({ label, color, metric }, index) => ({
|
||||
label,
|
||||
color: color ?? colorAt(index),
|
||||
metric,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {string} Key
|
||||
* @param {readonly (readonly [string, Key])[]} items
|
||||
* @param {(key: Key) => Metric} createMetric
|
||||
*/
|
||||
export function createCohortSeriesFromKeys(items, createMetric) {
|
||||
return createCohortSeries(
|
||||
items.map(([label, key]) => ({
|
||||
label,
|
||||
metric: createMetric(key),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
/** @typedef {import("../charts/index.js").ChartSeries["color"]} ChartColor */
|
||||
/** @typedef {import("../charts/index.js").ChartSeries["metric"]} Metric */
|
||||
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
createCohortSeries,
|
||||
createCohortSeriesFromKeys,
|
||||
} from "./cohort-series.js";
|
||||
import {
|
||||
addressableTypes,
|
||||
ageRanges,
|
||||
amountRanges,
|
||||
classes,
|
||||
epochs,
|
||||
outputTypes,
|
||||
} from "./groups.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
export const exposedSupplySeries = createCohortSeries([
|
||||
{
|
||||
label: "Exposed",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.addrs.exposed.supply.all.btc,
|
||||
},
|
||||
]);
|
||||
|
||||
export const exposedSupplyTypeSeries = createCohortSeriesFromKeys(
|
||||
addressableTypes,
|
||||
(key) => (client) => client.series.addrs.exposed.supply[key].btc,
|
||||
);
|
||||
|
||||
export const termSeries = createCohortSeries([
|
||||
{
|
||||
label: "STH",
|
||||
color: colors.yellow,
|
||||
metric: (client) => client.series.cohorts.utxo.sth.supply.total.btc,
|
||||
},
|
||||
{
|
||||
label: "LTH",
|
||||
color: colors.fuchsia,
|
||||
metric: (client) => client.series.cohorts.utxo.lth.supply.total.btc,
|
||||
},
|
||||
]);
|
||||
|
||||
export const ageSeries = createCohortSeriesFromKeys(
|
||||
ageRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.ageRange[key].supply.total.btc,
|
||||
);
|
||||
|
||||
export const utxoBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.amountRange[key].supply.total.btc,
|
||||
);
|
||||
|
||||
export const addressBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.addr.amountRange[key].supply.total.btc,
|
||||
);
|
||||
|
||||
export const typeSeries = createCohortSeriesFromKeys(
|
||||
outputTypes,
|
||||
(key) => (client) =>
|
||||
key === "opReturn"
|
||||
? client.series.outputs.value.opReturn.cumulative.btc
|
||||
: client.series.cohorts.utxo.type[key].supply.total.btc,
|
||||
);
|
||||
|
||||
export const epochSeries = createCohortSeriesFromKeys(
|
||||
epochs,
|
||||
(key) => (client) => client.series.cohorts.utxo.epoch[key].supply.total.btc,
|
||||
);
|
||||
|
||||
export const classSeries = createCohortSeriesFromKeys(
|
||||
classes,
|
||||
(key) => (client) => client.series.cohorts.utxo.class[key].supply.total.btc,
|
||||
);
|
||||
@@ -0,0 +1,110 @@
|
||||
export const ageRanges = /** @type {const} */ ([
|
||||
["0-1h", "under1h"],
|
||||
["1h to 1d", "_1hTo1d"],
|
||||
["1d to 1w", "_1dTo1w"],
|
||||
["1w to 1m", "_1wTo1m"],
|
||||
["1m to 2m", "_1mTo2m"],
|
||||
["2m to 3m", "_2mTo3m"],
|
||||
["3m to 4m", "_3mTo4m"],
|
||||
["4m to 5m", "_4mTo5m"],
|
||||
["5m to 6m", "_5mTo6m"],
|
||||
["6m to 1y", "_6mTo1y"],
|
||||
["1y to 2y", "_1yTo2y"],
|
||||
["2y to 3y", "_2yTo3y"],
|
||||
["3y to 4y", "_3yTo4y"],
|
||||
["4y to 5y", "_4yTo5y"],
|
||||
["5y to 6y", "_5yTo6y"],
|
||||
["6y to 7y", "_6yTo7y"],
|
||||
["7y to 8y", "_7yTo8y"],
|
||||
["8y to 10y", "_8yTo10y"],
|
||||
["10y to 12y", "_10yTo12y"],
|
||||
["12y to 15y", "_12yTo15y"],
|
||||
["15y+", "over15y"],
|
||||
]);
|
||||
|
||||
export const amountRanges = /** @type {const} */ ([
|
||||
["0 sats", "_0sats"],
|
||||
["1-10 sats", "_1satTo10sats"],
|
||||
["10-100 sats", "_10satsTo100sats"],
|
||||
["100-1k sats", "_100satsTo1kSats"],
|
||||
["1k-10k sats", "_1kSatsTo10kSats"],
|
||||
["10k-100k sats", "_10kSatsTo100kSats"],
|
||||
["100k-1M sats", "_100kSatsTo1mSats"],
|
||||
["1M-10M sats", "_1mSatsTo10mSats"],
|
||||
["10M sats-1 BTC", "_10mSatsTo1btc"],
|
||||
["1-10 BTC", "_1btcTo10btc"],
|
||||
["10-100 BTC", "_10btcTo100btc"],
|
||||
["100-1k BTC", "_100btcTo1kBtc"],
|
||||
["1k-10k BTC", "_1kBtcTo10kBtc"],
|
||||
["10k-100k BTC", "_10kBtcTo100kBtc"],
|
||||
["100k+ BTC", "over100kBtc"],
|
||||
]);
|
||||
|
||||
export const spendableTypes = /** @type {const} */ ([
|
||||
["P2PK65", "p2pk65"],
|
||||
["P2PK33", "p2pk33"],
|
||||
["P2PKH", "p2pkh"],
|
||||
["P2MS", "p2ms"],
|
||||
["P2SH", "p2sh"],
|
||||
["P2WPKH", "p2wpkh"],
|
||||
["P2WSH", "p2wsh"],
|
||||
["P2TR", "p2tr"],
|
||||
["P2A", "p2a"],
|
||||
["Unknown", "unknown"],
|
||||
["Empty", "empty"],
|
||||
]);
|
||||
|
||||
export const addressableTypes = /** @type {const} */ ([
|
||||
["P2PK65", "p2pk65"],
|
||||
["P2PK33", "p2pk33"],
|
||||
["P2PKH", "p2pkh"],
|
||||
["P2SH", "p2sh"],
|
||||
["P2WPKH", "p2wpkh"],
|
||||
["P2WSH", "p2wsh"],
|
||||
["P2TR", "p2tr"],
|
||||
["P2A", "p2a"],
|
||||
]);
|
||||
|
||||
export const outputTypes = /** @type {const} */ ([
|
||||
["P2PK65", "p2pk65"],
|
||||
["P2PK33", "p2pk33"],
|
||||
["P2PKH", "p2pkh"],
|
||||
["OP_RETURN", "opReturn"],
|
||||
["P2MS", "p2ms"],
|
||||
["P2SH", "p2sh"],
|
||||
["P2WPKH", "p2wpkh"],
|
||||
["P2WSH", "p2wsh"],
|
||||
["P2TR", "p2tr"],
|
||||
["P2A", "p2a"],
|
||||
["Unknown", "unknown"],
|
||||
["Empty", "empty"],
|
||||
]);
|
||||
|
||||
export const epochs = /** @type {const} */ ([
|
||||
["Epoch 0", "_0"],
|
||||
["Epoch 1", "_1"],
|
||||
["Epoch 2", "_2"],
|
||||
["Epoch 3", "_3"],
|
||||
["Epoch 4", "_4"],
|
||||
]);
|
||||
|
||||
export const classes = /** @type {const} */ ([
|
||||
["2009", "_2009"],
|
||||
["2010", "_2010"],
|
||||
["2011", "_2011"],
|
||||
["2012", "_2012"],
|
||||
["2013", "_2013"],
|
||||
["2014", "_2014"],
|
||||
["2015", "_2015"],
|
||||
["2016", "_2016"],
|
||||
["2017", "_2017"],
|
||||
["2018", "_2018"],
|
||||
["2019", "_2019"],
|
||||
["2020", "_2020"],
|
||||
["2021", "_2021"],
|
||||
["2022", "_2022"],
|
||||
["2023", "_2023"],
|
||||
["2024", "_2024"],
|
||||
["2025", "_2025"],
|
||||
["2026", "_2026"],
|
||||
]);
|
||||
@@ -0,0 +1,15 @@
|
||||
import { addressCountSection } from "./sections/address-count.js";
|
||||
import { capitalizationSection } from "./sections/capitalization.js";
|
||||
import { introductionSection } from "./sections/introduction.js";
|
||||
import { miningPoolsSection } from "./sections/mining-pools.js";
|
||||
import { supplySection } from "./sections/supply.js";
|
||||
import { utxoSetSection } from "./sections/utxo-set.js";
|
||||
|
||||
export const sections = [
|
||||
introductionSection,
|
||||
supplySection,
|
||||
utxoSetSection,
|
||||
addressCountSection,
|
||||
miningPoolsSection,
|
||||
capitalizationSection,
|
||||
];
|
||||
@@ -0,0 +1,108 @@
|
||||
import { createCohortSeries } from "./cohort-series.js";
|
||||
import { createRollingWindowSeries } from "./rolling-windows.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { brk } from "../../utils/client.js";
|
||||
|
||||
const poolNames = brk.POOL_ID_TO_POOL_NAME;
|
||||
|
||||
/**
|
||||
* @template {keyof typeof poolNames} Key
|
||||
* @template Pool
|
||||
* @param {Record<Key, Pool>} pools
|
||||
*/
|
||||
function createPools(pools) {
|
||||
const entries = [];
|
||||
|
||||
for (const key in pools) {
|
||||
entries.push({
|
||||
name: poolNames[key],
|
||||
pool: pools[key],
|
||||
});
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(window: WindowKey) => TimeframeMetric} createMetric
|
||||
*/
|
||||
function createWindowSeries(createMetric) {
|
||||
return createRollingWindowSeries((window) => () => createMetric(window));
|
||||
}
|
||||
|
||||
export const majorPools = createPools(brk.series.pools.major);
|
||||
|
||||
export const minorPools = createPools(brk.series.pools.minor);
|
||||
|
||||
export const majorPoolDominanceSeries = createCohortSeries(
|
||||
majorPools.map(({ name, pool }) => ({
|
||||
label: name,
|
||||
metric: () => pool.dominance._1m.percent,
|
||||
})),
|
||||
);
|
||||
|
||||
export const majorPoolBlocksMinedSeries = createCohortSeries(
|
||||
majorPools.map(({ name, pool }) => ({
|
||||
label: name,
|
||||
metric: () => pool.blocksMined.sum._1m,
|
||||
})),
|
||||
);
|
||||
|
||||
export const majorPoolRewardsSeries = createCohortSeries(
|
||||
majorPools.map(({ name, pool }) => ({
|
||||
label: name,
|
||||
metric: () => pool.rewards.sum._1m.btc,
|
||||
})),
|
||||
);
|
||||
|
||||
/** @param {MajorPool} pool */
|
||||
export function createMajorPoolDominanceSeries(pool) {
|
||||
const series = createWindowSeries(
|
||||
(window) => pool.dominance[window].percent,
|
||||
);
|
||||
|
||||
series.push({
|
||||
label: "All time",
|
||||
color: colors.orange,
|
||||
metric: () => pool.dominance.percent,
|
||||
});
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
/** @param {MajorPool} pool */
|
||||
export function createMajorPoolBlocksMinedSeries(pool) {
|
||||
return createWindowSeries(
|
||||
(window) => pool.blocksMined.sum[window],
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {MajorPool} pool */
|
||||
export function createMajorPoolRewardsSeries(pool) {
|
||||
return createWindowSeries(
|
||||
(window) => pool.rewards.sum[window].btc,
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {MinorPool} pool */
|
||||
export function createMinorPoolDominanceSeries(pool) {
|
||||
return createCohortSeries([
|
||||
{
|
||||
label: "All time",
|
||||
color: colors.orange,
|
||||
metric: () => pool.dominance.percent,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/** @param {MinorPool} pool */
|
||||
export function createMinorPoolBlocksMinedSeries(pool) {
|
||||
return createWindowSeries(
|
||||
(window) => pool.blocksMined.sum[window],
|
||||
);
|
||||
}
|
||||
|
||||
/** @typedef {import("./rolling-windows.js").RollingWindowKey} WindowKey */
|
||||
/** @typedef {typeof brk.series.pools.major.unknown} MajorPool */
|
||||
/** @typedef {typeof brk.series.pools.minor.blockfills} MinorPool */
|
||||
/** @typedef {import("../charts/timeframes.js").TimeframeMetric} TimeframeMetric */
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createCohortSeries } from "./cohort-series.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
export const rollingWindows = /** @type {const} */ ([
|
||||
["24h", "_24h", colors.sky],
|
||||
["1w", "_1w", colors.cyan],
|
||||
["1m", "_1m", colors.blue],
|
||||
["1y", "_1y", colors.violet],
|
||||
]);
|
||||
|
||||
/** @param {(key: RollingWindowKey) => Metric} createMetric */
|
||||
export function createRollingWindowSeries(createMetric) {
|
||||
return createCohortSeries(
|
||||
rollingWindows.map(([label, key, color]) => ({
|
||||
label,
|
||||
color,
|
||||
metric: createMetric(key),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
/** @typedef {(typeof rollingWindows)[number][1]} RollingWindowKey */
|
||||
/** @typedef {import("./cohort-series.js").Metric} Metric */
|
||||
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
activeSeries,
|
||||
balanceSeries,
|
||||
bidirectionalSeries,
|
||||
changeSeries,
|
||||
fundedSeries,
|
||||
growthRateSeries,
|
||||
newSeries,
|
||||
reactivatedSeries,
|
||||
receivingSeries,
|
||||
reuseSeries,
|
||||
sendingSeries,
|
||||
stateSeries,
|
||||
typeSeries,
|
||||
} from "../address-count.js";
|
||||
import { units } from "../../charts/units.js";
|
||||
|
||||
const line = /** @type {const} */ ("line");
|
||||
|
||||
export const addressCountSection = {
|
||||
title: "Address Count",
|
||||
description:
|
||||
"Address count measures Bitcoin addresses, not people or entities. A funded address currently has a non-zero balance, while empty addresses have received or spent coins before but no longer hold BTC. These charts show how the address set grows, turns over, and distributes across balances and script types.",
|
||||
chart: {
|
||||
title: "Funded addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: fundedSeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Activity",
|
||||
description:
|
||||
"Shows how addresses appear and participate in transactions over time. These charts focus on address movement and usage, not the current distribution of address balances.",
|
||||
children: [
|
||||
{
|
||||
title: "New",
|
||||
description:
|
||||
"Counts addresses that appear for the first time during each rolling window. A new address is not necessarily a new user, but it does show fresh address creation on-chain.",
|
||||
chart: {
|
||||
title: "New addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: newSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Change",
|
||||
description:
|
||||
"Shows the rolling net change in funded address count. The count rises when more addresses receive a non-zero balance, and falls when more addresses are emptied.",
|
||||
chart: {
|
||||
title: "Funded address count change",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: changeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Growth Rate",
|
||||
description:
|
||||
"Shows the rolling percentage change of funded address count. It measures the same expansion or contraction as Change, but normalizes it by the size of the funded address set.",
|
||||
chart: {
|
||||
title: "Funded address growth rate",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: growthRateSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Active",
|
||||
description:
|
||||
"Counts addresses that are active during each rolling window. Active addresses can send, receive, do both, or return after inactivity.",
|
||||
chart: {
|
||||
title: "Active addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: activeSeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Sending",
|
||||
description:
|
||||
"Counts addresses that spend from at least one output during each rolling window. This shows address-side transaction participation from senders.",
|
||||
chart: {
|
||||
title: "Sending addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: sendingSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Receiving",
|
||||
description:
|
||||
"Counts addresses that receive at least one output during each rolling window. This shows address-side transaction participation from recipients.",
|
||||
chart: {
|
||||
title: "Receiving addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: receivingSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Bidirectional",
|
||||
description:
|
||||
"Counts addresses that both send and receive during each rolling window. This can highlight addresses with more two-sided transaction behavior.",
|
||||
chart: {
|
||||
title: "Bidirectional addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: bidirectionalSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Reactivated",
|
||||
description:
|
||||
"Counts addresses that become active again after a quiet period. This helps show when older address activity returns.",
|
||||
chart: {
|
||||
title: "Reactivated addresses",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: reactivatedSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Distribution",
|
||||
description:
|
||||
"Shows how addresses are split across states, balances, and address types. These charts describe the current address set rather than how addresses are moving.",
|
||||
children: [
|
||||
{
|
||||
title: "State",
|
||||
description:
|
||||
"Splits addresses into funded, empty, and total counts. Funded addresses currently hold BTC; empty addresses have no current balance; total includes both.",
|
||||
chart: {
|
||||
title: "Address count by state",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: stateSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Balance",
|
||||
description:
|
||||
"Groups funded addresses by the BTC amount held at each address. Addresses are not people or entities, but this still shows how address balances are distributed on-chain.",
|
||||
chart: {
|
||||
title: "Address count by balance",
|
||||
unit: units.addresses,
|
||||
series: balanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Groups funded addresses by address type. The type reflects the script format used by the address, such as legacy, SegWit, or Taproot.",
|
||||
chart: {
|
||||
title: "Funded address count by type",
|
||||
unit: units.addresses,
|
||||
series: typeSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Reuse",
|
||||
description:
|
||||
"Shows address patterns that can reduce privacy or reveal public-key information. These counts are address-level signals, not direct counts of people.",
|
||||
chart: {
|
||||
title: "Address count by reuse",
|
||||
unit: units.addresses,
|
||||
defaultType: line,
|
||||
series: reuseSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { capitalizationSeries } from "../capitalization.js";
|
||||
import { units } from "../../charts/units.js";
|
||||
import { marketCapSection } from "./capitalization/market.js";
|
||||
import { realizedCapSection } from "./capitalization/realized.js";
|
||||
|
||||
export const capitalizationSection = {
|
||||
title: "Capitalization",
|
||||
description:
|
||||
"Shows ways to value Bitcoin in US dollars. Market cap uses today's price, while realized cap uses the price when coins last moved on-chain.",
|
||||
chart: {
|
||||
title: "Capitalization",
|
||||
unit: units.usd,
|
||||
defaultType: /** @type {const} */ ("line"),
|
||||
series: capitalizationSeries,
|
||||
},
|
||||
children: [
|
||||
marketCapSection,
|
||||
realizedCapSection,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
marketCapAddressBalanceSeries,
|
||||
marketCapAgeSeries,
|
||||
marketCapClassSeries,
|
||||
marketCapEpochSeries,
|
||||
marketCapProfitabilitySeries,
|
||||
marketCapSeries,
|
||||
marketCapTermSeries,
|
||||
marketCapTypeSeries,
|
||||
marketCapUtxoBalanceSeries,
|
||||
} from "../../capitalization.js";
|
||||
import { units } from "../../../charts/units.js";
|
||||
|
||||
export const marketCapSection = {
|
||||
title: "Market Cap",
|
||||
description:
|
||||
"Market cap is circulating supply multiplied by the current bitcoin price. It answers: what is all circulating BTC worth at today's market price?",
|
||||
chart: {
|
||||
title: "Market cap",
|
||||
unit: units.usd,
|
||||
series: marketCapSeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Profitability",
|
||||
description:
|
||||
"Splits market cap between coins that are currently in profit and coins that are currently in loss. This shows how much current market value sits above or below each coin's last moved price.",
|
||||
chart: {
|
||||
title: "Market cap by profitability",
|
||||
unit: units.usd,
|
||||
series: marketCapProfitabilitySeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Term",
|
||||
description:
|
||||
"Splits market cap between coins that moved recently and coins that have stayed still longer. This shows how much current market value sits with active supply versus long-term holder supply.",
|
||||
chart: {
|
||||
title: "Market cap by term",
|
||||
unit: units.usd,
|
||||
series: marketCapTermSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Age",
|
||||
description:
|
||||
"Groups market cap by how long coins have stayed still since their last on-chain movement. It shows which age bands hold the most current market value.",
|
||||
chart: {
|
||||
title: "Market cap by age",
|
||||
unit: units.usd,
|
||||
series: marketCapAgeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "UTXO Balance",
|
||||
description:
|
||||
"Groups market cap by the size of each unspent output. This shows how current market value is distributed across small and large spendable coin fragments.",
|
||||
chart: {
|
||||
title: "Market cap by UTXO balance",
|
||||
unit: units.usd,
|
||||
series: marketCapUtxoBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Address Balance",
|
||||
description:
|
||||
"Groups market cap by the total BTC held at each address. Addresses are not people or entities, but this still helps show how current market value is distributed across address balances.",
|
||||
chart: {
|
||||
title: "Market cap by address balance",
|
||||
unit: units.usd,
|
||||
series: marketCapAddressBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Groups market cap by Bitcoin output type. This shows how much current market value is held in each script format.",
|
||||
chart: {
|
||||
title: "Market cap by type",
|
||||
unit: units.usd,
|
||||
series: marketCapTypeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Epoch",
|
||||
description:
|
||||
"Groups market cap by the halving epoch when coins were mined. This shows the current value of coins created during each issuance period.",
|
||||
chart: {
|
||||
title: "Market cap by epoch",
|
||||
unit: units.usd,
|
||||
series: marketCapEpochSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
description:
|
||||
"Groups market cap by the calendar year when coins were mined. This shows the current value of supply created in each year.",
|
||||
chart: {
|
||||
title: "Market cap by class",
|
||||
unit: units.usd,
|
||||
series: marketCapClassSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
realizedCapAddressBalanceSeries,
|
||||
realizedCapAgeSeries,
|
||||
realizedCapClassSeries,
|
||||
realizedCapEpochSeries,
|
||||
realizedCapProfitabilitySeries,
|
||||
realizedCapSeries,
|
||||
realizedCapTermSeries,
|
||||
realizedCapTypeSeries,
|
||||
realizedCapUtxoBalanceSeries,
|
||||
} from "../../capitalization.js";
|
||||
import { units } from "../../../charts/units.js";
|
||||
|
||||
export const realizedCapSection = {
|
||||
title: "Realized Cap",
|
||||
description:
|
||||
"Realized cap values each coin at the price when it last moved on-chain. It is often used as a rough view of the market's aggregate cost basis.",
|
||||
chart: {
|
||||
title: "Realized cap",
|
||||
unit: units.usd,
|
||||
series: realizedCapSeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Profitability",
|
||||
description:
|
||||
"Splits realized cap between coins that are currently in profit and coins that are currently in loss. This shows how the market's cost basis is distributed across coins above or below their last moved price.",
|
||||
chart: {
|
||||
title: "Realized cap by profitability",
|
||||
unit: units.usd,
|
||||
series: realizedCapProfitabilitySeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Term",
|
||||
description:
|
||||
"Splits realized cap between coins that moved recently and coins that have stayed still longer. This shows where the market's cost basis sits across active and long-term holder supply.",
|
||||
chart: {
|
||||
title: "Realized cap by term",
|
||||
unit: units.usd,
|
||||
series: realizedCapTermSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Age",
|
||||
description:
|
||||
"Groups realized cap by how long coins have stayed still since their last on-chain movement. This shows which coin ages carry the largest share of the market's cost basis.",
|
||||
chart: {
|
||||
title: "Realized cap by age",
|
||||
unit: units.usd,
|
||||
series: realizedCapAgeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "UTXO Balance",
|
||||
description:
|
||||
"Groups realized cap by the size of each unspent output. This shows how cost basis is distributed across small and large spendable coin fragments.",
|
||||
chart: {
|
||||
title: "Realized cap by UTXO balance",
|
||||
unit: units.usd,
|
||||
series: realizedCapUtxoBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Address Balance",
|
||||
description:
|
||||
"Groups realized cap by the total BTC held at each address. Addresses are not people or entities, but this still helps show how cost basis is distributed across address balances.",
|
||||
chart: {
|
||||
title: "Realized cap by address balance",
|
||||
unit: units.usd,
|
||||
series: realizedCapAddressBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Groups realized cap by Bitcoin output type. This shows how much cost basis is held in each script format.",
|
||||
chart: {
|
||||
title: "Realized cap by type",
|
||||
unit: units.usd,
|
||||
series: realizedCapTypeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Epoch",
|
||||
description:
|
||||
"Groups realized cap by the halving epoch when coins were mined. This shows the cost basis of coins created during each issuance period.",
|
||||
chart: {
|
||||
title: "Realized cap by epoch",
|
||||
unit: units.usd,
|
||||
series: realizedCapEpochSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
description:
|
||||
"Groups realized cap by the calendar year when coins were mined. This shows the cost basis of supply created in each year.",
|
||||
chart: {
|
||||
title: "Realized cap by class",
|
||||
unit: units.usd,
|
||||
series: realizedCapClassSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export const introductionSection = {
|
||||
title: "Introduction",
|
||||
numbered: false,
|
||||
description:
|
||||
"Bitcoin can be measured from many angles, but a single number rarely explains much on its own. This page introduces core Bitcoin concepts through data that changes over time. Each chart is meant to answer a simple question: what is being measured, how has it changed, and how does it compare across different groups? The goal is to make the system easier to read, from the supply itself to the way coins move, age, concentrate, and gain value.",
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
createMajorPoolBlocksMinedSeries,
|
||||
createMajorPoolDominanceSeries,
|
||||
createMajorPoolRewardsSeries,
|
||||
createMinorPoolBlocksMinedSeries,
|
||||
createMinorPoolDominanceSeries,
|
||||
majorPools,
|
||||
majorPoolBlocksMinedSeries,
|
||||
majorPoolDominanceSeries,
|
||||
majorPoolRewardsSeries,
|
||||
minorPools,
|
||||
} from "../mining-pools.js";
|
||||
import { units } from "../../charts/units.js";
|
||||
|
||||
const line = /** @type {const} */ ("line");
|
||||
|
||||
/** @param {string} name */
|
||||
function createPoolDescription(name) {
|
||||
return `${name} is tracked from pool attribution in mined blocks. These charts show share of blocks, blocks found, and rewards where available. Pool attribution is useful for understanding block production, but it is not the same as knowing who owns the underlying mining hardware.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(typeof majorPools)[number]} item
|
||||
*/
|
||||
function createMajorPoolSection({ name, pool }) {
|
||||
return {
|
||||
title: name,
|
||||
description: createPoolDescription(name),
|
||||
children: [
|
||||
{
|
||||
title: "Dominance",
|
||||
description:
|
||||
"Dominance is the pool's share of mined blocks over each rolling window. It is estimated from blocks attributed to the pool, so it is best read as block-production share.",
|
||||
chart: {
|
||||
title: `${name} dominance`,
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: createMajorPoolDominanceSeries(pool),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Blocks Mined",
|
||||
description:
|
||||
"Counts how many blocks were attributed to the pool in each rolling window. This is the raw activity behind the dominance percentage.",
|
||||
chart: {
|
||||
title: `${name} blocks mined`,
|
||||
unit: units.blocks,
|
||||
defaultType: line,
|
||||
series: createMajorPoolBlocksMinedSeries(pool),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Rewards",
|
||||
description:
|
||||
"Sums the BTC earned by blocks attributed to the pool. Rewards include both the block subsidy and transaction fees.",
|
||||
chart: {
|
||||
title: `${name} rewards`,
|
||||
unit: units.btc,
|
||||
defaultType: line,
|
||||
series: createMajorPoolRewardsSeries(pool),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(typeof minorPools)[number]} item
|
||||
*/
|
||||
function createMinorPoolSection({ name, pool }) {
|
||||
return {
|
||||
title: name,
|
||||
description: createPoolDescription(name),
|
||||
children: [
|
||||
{
|
||||
title: "Dominance",
|
||||
description:
|
||||
"Shows the pool's all-time share of mined blocks. Minor pools expose a smaller historical metric set, so rolling dominance is not shown here.",
|
||||
chart: {
|
||||
title: `${name} dominance`,
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: createMinorPoolDominanceSeries(pool),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Blocks Mined",
|
||||
description:
|
||||
"Counts how many blocks were attributed to the pool in each rolling window. For minor pools, this is usually the most useful activity view.",
|
||||
chart: {
|
||||
title: `${name} blocks mined`,
|
||||
unit: units.blocks,
|
||||
defaultType: line,
|
||||
series: createMinorPoolBlocksMinedSeries(pool),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export const miningPoolsSection = {
|
||||
title: "Mining Pools",
|
||||
description:
|
||||
"Mining pools coordinate miners so they can find blocks more steadily and split rewards. Pool charts show which pools are producing blocks, how their share changes, and how much BTC is paid to pools that are large enough to track in detail.",
|
||||
children: [
|
||||
{
|
||||
title: "Major",
|
||||
description:
|
||||
"Major pools have enough historical activity to track dominance, blocks mined, and rewards. This makes them useful for studying mining concentration and how block production changes over time.",
|
||||
children: [
|
||||
{
|
||||
title: "Dominance",
|
||||
description:
|
||||
"Compares the rolling monthly block-production share of all major pools. This is the clearest overview of mining-pool concentration.",
|
||||
chart: {
|
||||
title: "Major pool dominance",
|
||||
unit: units.percent,
|
||||
series: majorPoolDominanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Blocks Mined",
|
||||
description:
|
||||
"Compares rolling monthly blocks mined by major pools. This shows the raw block counts behind dominance percentages.",
|
||||
chart: {
|
||||
title: "Major pool blocks mined",
|
||||
unit: units.blocks,
|
||||
series: majorPoolBlocksMinedSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Rewards",
|
||||
description:
|
||||
"Compares rolling monthly BTC rewards earned by major pools. Rewards include both subsidy and transaction fees.",
|
||||
chart: {
|
||||
title: "Major pool rewards",
|
||||
unit: units.btc,
|
||||
series: majorPoolRewardsSeries,
|
||||
},
|
||||
},
|
||||
...majorPools.map(createMajorPoolSection),
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Minor",
|
||||
description:
|
||||
"Minor pools are smaller or less persistent pools. They matter because the long tail shows how broad or narrow block production is beyond the largest names, even when each pool is individually small.",
|
||||
children: minorPools.map(createMinorPoolSection),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
addressBalanceSeries,
|
||||
ageSeries,
|
||||
classSeries,
|
||||
epochSeries,
|
||||
exposedSupplySeries,
|
||||
exposedSupplyTypeSeries,
|
||||
termSeries,
|
||||
typeSeries,
|
||||
utxoBalanceSeries,
|
||||
} from "../cohorts.js";
|
||||
import {
|
||||
circulatingSupplySeries,
|
||||
supplyProfitabilitySeries,
|
||||
} from "../supply.js";
|
||||
import { units } from "../../charts/units.js";
|
||||
|
||||
export const supplySection = {
|
||||
title: "Supply",
|
||||
description:
|
||||
"Bitcoin has a fixed issuance schedule. This chart shows how many BTC are in circulation over time, so you can see supply rising toward the 21 million limit.",
|
||||
chart: {
|
||||
title: "Circulating supply",
|
||||
unit: units.btc,
|
||||
series: circulatingSupplySeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Profitability",
|
||||
description:
|
||||
"Shows whether coins are in profit or loss based on the price when they last moved on-chain. A coin is in profit when today's price is higher than its last moved price, and in loss when today's price is lower.",
|
||||
chart: {
|
||||
title: "Profitability",
|
||||
unit: units.btc,
|
||||
series: supplyProfitabilitySeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Exposed",
|
||||
description:
|
||||
"Shows BTC held by addresses whose public key is already visible on-chain. This can happen because the address type exposes the key directly, or because coins were spent from that address before.",
|
||||
chart: {
|
||||
title: "Exposed supply",
|
||||
unit: units.btc,
|
||||
defaultType: /** @type {const} */ ("line"),
|
||||
series: exposedSupplySeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Splits exposed supply by address type. This shows which script formats account for the visible-public-key supply.",
|
||||
chart: {
|
||||
title: "Exposed supply by type",
|
||||
unit: units.btc,
|
||||
series: exposedSupplyTypeSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Term",
|
||||
description:
|
||||
"Splits supply between coins that moved recently and coins that have stayed still longer. This helps separate more active supply from long-term holder supply.",
|
||||
chart: {
|
||||
title: "Supply by term",
|
||||
unit: units.btc,
|
||||
series: termSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Age",
|
||||
description:
|
||||
"Groups coins by how long they have stayed still since their last on-chain movement. Older coins are usually more dormant, while younger coins have moved more recently.",
|
||||
chart: {
|
||||
title: "Supply by age",
|
||||
unit: units.btc,
|
||||
series: ageSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "UTXO Balance",
|
||||
description:
|
||||
"Groups supply by the size of each unspent output. A UTXO is a spendable piece of bitcoin created by a transaction, so this shows the size distribution of coin fragments.",
|
||||
chart: {
|
||||
title: "Supply by UTXO balance",
|
||||
unit: units.btc,
|
||||
series: utxoBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Address Balance",
|
||||
description:
|
||||
"Groups supply by the total BTC held at each address. An address is not the same as a person or entity, but this still helps show how balances are distributed on-chain.",
|
||||
chart: {
|
||||
title: "Supply by address balance",
|
||||
unit: units.btc,
|
||||
series: addressBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Groups supply by Bitcoin output type. The output type is the script format that defines how coins can be spent.",
|
||||
chart: {
|
||||
title: "Supply by type",
|
||||
unit: units.btc,
|
||||
series: typeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Epoch",
|
||||
description:
|
||||
"Groups supply by the halving epoch when coins were mined. A halving epoch is a period between two subsidy halvings, when the amount of new BTC paid to miners changes.",
|
||||
chart: {
|
||||
title: "Supply by epoch",
|
||||
unit: units.btc,
|
||||
series: epochSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
description:
|
||||
"Groups supply by the calendar year when coins were mined. This shows how much of today's supply comes from each issuance year.",
|
||||
chart: {
|
||||
title: "Supply by class",
|
||||
unit: units.btc,
|
||||
series: classSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
ageSeries,
|
||||
balanceSeries,
|
||||
changeSeries,
|
||||
classSeries,
|
||||
epochSeries,
|
||||
growthRateSeries,
|
||||
spendingRateAgeSeries,
|
||||
spendingRateBalanceSeries,
|
||||
spendingRateClassSeries,
|
||||
spendingRateEpochSeries,
|
||||
spendingRateSeries,
|
||||
spendingRateTermSeries,
|
||||
spendingRateTypeSeries,
|
||||
spentSeries,
|
||||
termSeries,
|
||||
totalSeries,
|
||||
typeSeries,
|
||||
} from "../utxo-set.js";
|
||||
import { units } from "../../charts/units.js";
|
||||
|
||||
const line = /** @type {const} */ ("line");
|
||||
|
||||
export const utxoSetSection = {
|
||||
title: "UTXO Set",
|
||||
description:
|
||||
"The UTXO set is the collection of all spendable bitcoin outputs that exist right now. Each UTXO is a separate coin fragment created by a transaction and later consumed when it is spent. Counting UTXOs shows how Bitcoin is split into pieces, which is different from counting how much BTC those pieces contain.",
|
||||
chart: {
|
||||
title: "UTXO set",
|
||||
unit: units.utxos,
|
||||
defaultType: line,
|
||||
series: totalSeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Activity",
|
||||
description:
|
||||
"Shows how the UTXO set changes as transactions create new outputs and consume old ones. These charts focus on movement and turnover, not the current composition of the set.",
|
||||
children: [
|
||||
{
|
||||
title: "Change",
|
||||
description:
|
||||
"Shows the rolling net change in the UTXO set. The count rises when transactions create more spendable outputs than they consume, and falls when spending consolidates many old outputs into fewer new ones.",
|
||||
chart: {
|
||||
title: "UTXO set change",
|
||||
unit: units.utxos,
|
||||
defaultType: line,
|
||||
series: changeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Growth Rate",
|
||||
description:
|
||||
"Shows the rolling percentage change of the UTXO set. It measures the same net expansion or contraction as Change, but normalizes it by the size of the set so different periods are easier to compare.",
|
||||
chart: {
|
||||
title: "UTXO set growth rate",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: growthRateSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Spent",
|
||||
description:
|
||||
"Counts how many UTXOs were spent during each rolling window. This measures how much of the existing set was consumed as transaction inputs, regardless of the BTC value inside those outputs.",
|
||||
chart: {
|
||||
title: "Spent UTXOs",
|
||||
unit: units.utxos,
|
||||
defaultType: line,
|
||||
series: spentSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Spending Rate",
|
||||
description:
|
||||
"Shows how quickly the UTXO set is being consumed. Instead of counting spent outputs directly, it expresses spending as a rate, which makes busy and quiet periods easier to compare.",
|
||||
chart: {
|
||||
title: "UTXO set spending rate",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateSeries,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "Term",
|
||||
description:
|
||||
"Splits spending rate between short-term and long-term holder cohorts. This shows whether recent or dormant UTXOs are being consumed faster.",
|
||||
chart: {
|
||||
title: "UTXO spending rate by term",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateTermSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Age",
|
||||
description:
|
||||
"Groups spending rate by how long UTXOs have stayed unspent. This shows which age bands are turning over fastest.",
|
||||
chart: {
|
||||
title: "UTXO spending rate by age",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateAgeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Balance",
|
||||
description:
|
||||
"Groups spending rate by the BTC amount held in each UTXO. This shows whether small or large outputs are being consumed faster.",
|
||||
chart: {
|
||||
title: "UTXO spending rate by balance",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateBalanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Groups spending rate by output type. This shows how quickly UTXOs from each script format are being consumed.",
|
||||
chart: {
|
||||
title: "UTXO spending rate by type",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateTypeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Epoch",
|
||||
description:
|
||||
"Groups spending rate by the halving epoch when coins were mined. This shows which issuance periods are turning over fastest.",
|
||||
chart: {
|
||||
title: "UTXO spending rate by epoch",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateEpochSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
description:
|
||||
"Groups spending rate by the calendar year when coins were mined. This shows which issuance years are turning over fastest.",
|
||||
chart: {
|
||||
title: "UTXO spending rate by class",
|
||||
unit: units.percent,
|
||||
defaultType: line,
|
||||
series: spendingRateClassSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Distribution",
|
||||
description:
|
||||
"Shows how the current UTXO set is split across different groups. These charts describe the composition of existing spendable outputs, not how quickly they are changing.",
|
||||
children: [
|
||||
{
|
||||
title: "Term",
|
||||
description:
|
||||
"Splits the UTXO set between short-term and long-term holder cohorts. This counts pieces, not BTC, so it shows whether recent and dormant supply is made of many small outputs or fewer larger ones.",
|
||||
chart: {
|
||||
title: "UTXO set by term",
|
||||
unit: units.utxos,
|
||||
series: termSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Age",
|
||||
description:
|
||||
"Groups UTXOs by how long they have stayed unspent. A young UTXO was created recently, while an old UTXO has survived many blocks without being consumed in a transaction.",
|
||||
chart: {
|
||||
title: "UTXO set by age",
|
||||
unit: units.utxos,
|
||||
series: ageSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Balance",
|
||||
description:
|
||||
"Groups UTXOs by the BTC amount held in each output. This shows the size distribution of spendable pieces, from tiny fragments to very large outputs.",
|
||||
chart: {
|
||||
title: "UTXO set by balance",
|
||||
unit: units.utxos,
|
||||
series: balanceSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
description:
|
||||
"Groups UTXOs by output type. The type is the script format that defines how the output can be spent, such as legacy, SegWit, or Taproot.",
|
||||
chart: {
|
||||
title: "UTXO set by type",
|
||||
unit: units.utxos,
|
||||
series: typeSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Epoch",
|
||||
description:
|
||||
"Groups UTXOs by the halving epoch when their coins were mined. This shows how many currently spendable pieces trace back to each issuance period.",
|
||||
chart: {
|
||||
title: "UTXO set by epoch",
|
||||
unit: units.utxos,
|
||||
series: epochSeries,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Class",
|
||||
description:
|
||||
"Groups UTXOs by the calendar year when their coins were mined. This shows how the current set of spendable pieces is distributed across issuance years.",
|
||||
chart: {
|
||||
title: "UTXO set by class",
|
||||
unit: units.utxos,
|
||||
series: classSeries,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createCohortSeries } from "./cohort-series.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
export const circulatingSupplySeries = createCohortSeries([
|
||||
{
|
||||
label: "Circulating",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.supply.circulating.btc,
|
||||
},
|
||||
]);
|
||||
|
||||
export const supplyProfitabilitySeries = createCohortSeries([
|
||||
{
|
||||
label: "In profit",
|
||||
color: colors.green,
|
||||
metric: (client) =>
|
||||
client.series.cohorts.utxo.profitability.profit.all.supply.all.btc,
|
||||
},
|
||||
{
|
||||
label: "In loss",
|
||||
color: colors.red,
|
||||
metric: (client) =>
|
||||
client.series.cohorts.utxo.profitability.loss.all.supply.all.btc,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
createCohortSeries,
|
||||
createCohortSeriesFromKeys,
|
||||
} from "./cohort-series.js";
|
||||
import {
|
||||
ageRanges,
|
||||
amountRanges,
|
||||
classes,
|
||||
epochs,
|
||||
spendableTypes,
|
||||
} from "./groups.js";
|
||||
import { createRollingWindowSeries } from "./rolling-windows.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
export const totalSeries = createCohortSeries([
|
||||
{
|
||||
label: "UTXOs",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.cohorts.utxo.all.outputs.unspentCount.base,
|
||||
},
|
||||
]);
|
||||
|
||||
export const changeSeries = createRollingWindowSeries(
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.all.outputs.unspentCount.delta.absolute[key],
|
||||
);
|
||||
|
||||
export const growthRateSeries = createRollingWindowSeries(
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.all.outputs.unspentCount.delta.rate[key].percent,
|
||||
);
|
||||
|
||||
export const spentSeries = createRollingWindowSeries(
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.all.outputs.spentCount.sum[key],
|
||||
);
|
||||
|
||||
export const spendingRateSeries = createCohortSeries([
|
||||
{
|
||||
label: "Spending rate",
|
||||
color: colors.orange,
|
||||
metric: (client) => client.series.cohorts.utxo.all.outputs.spendingRate,
|
||||
},
|
||||
]);
|
||||
|
||||
export const spendingRateTermSeries = createCohortSeries([
|
||||
{
|
||||
label: "STH",
|
||||
color: colors.yellow,
|
||||
metric: (client) => client.series.cohorts.utxo.sth.outputs.spendingRate,
|
||||
},
|
||||
{
|
||||
label: "LTH",
|
||||
color: colors.fuchsia,
|
||||
metric: (client) => client.series.cohorts.utxo.lth.outputs.spendingRate,
|
||||
},
|
||||
]);
|
||||
|
||||
export const spendingRateAgeSeries = createCohortSeriesFromKeys(
|
||||
ageRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.ageRange[key].outputs.spendingRate,
|
||||
);
|
||||
|
||||
export const spendingRateBalanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.amountRange[key].outputs.spendingRate,
|
||||
);
|
||||
|
||||
export const spendingRateTypeSeries = createCohortSeriesFromKeys(
|
||||
spendableTypes,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.type[key].outputs.spendingRate,
|
||||
);
|
||||
|
||||
export const spendingRateEpochSeries = createCohortSeriesFromKeys(
|
||||
epochs,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.epoch[key].outputs.spendingRate,
|
||||
);
|
||||
|
||||
export const spendingRateClassSeries = createCohortSeriesFromKeys(
|
||||
classes,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.class[key].outputs.spendingRate,
|
||||
);
|
||||
|
||||
export const termSeries = createCohortSeries([
|
||||
{
|
||||
label: "STH",
|
||||
color: colors.yellow,
|
||||
metric: (client) => client.series.cohorts.utxo.sth.outputs.unspentCount.base,
|
||||
},
|
||||
{
|
||||
label: "LTH",
|
||||
color: colors.fuchsia,
|
||||
metric: (client) => client.series.cohorts.utxo.lth.outputs.unspentCount.base,
|
||||
},
|
||||
]);
|
||||
|
||||
export const ageSeries = createCohortSeriesFromKeys(
|
||||
ageRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.ageRange[key].outputs.unspentCount.base,
|
||||
);
|
||||
|
||||
export const balanceSeries = createCohortSeriesFromKeys(
|
||||
amountRanges,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.amountRange[key].outputs.unspentCount.base,
|
||||
);
|
||||
|
||||
export const typeSeries = createCohortSeriesFromKeys(
|
||||
spendableTypes,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.type[key].outputs.unspentCount.base,
|
||||
);
|
||||
|
||||
export const epochSeries = createCohortSeriesFromKeys(
|
||||
epochs,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.epoch[key].outputs.unspentCount.base,
|
||||
);
|
||||
|
||||
export const classSeries = createCohortSeriesFromKeys(
|
||||
classes,
|
||||
(key) => (client) =>
|
||||
client.series.cohorts.utxo.class[key].outputs.unspentCount.base,
|
||||
);
|
||||
Reference in New Issue
Block a user