From b23d20ea05cfe0f12378e0e82187bdcb88e69cde Mon Sep 17 00:00:00 2001 From: nym21 Date: Mon, 2 Feb 2026 18:39:42 +0100 Subject: [PATCH] website: snapshot --- crates/brk_client/src/lib.rs | 8 +- crates/brk_cohort/src/by_any_address.rs | 4 +- crates/brk_computer/examples/computer_read.rs | 38 +- .../src/distribution/address/data.rs | 28 +- .../src/distribution/address/indexes/any.rs | 2 +- .../src/distribution/block/cache/address.rs | 44 +- .../src/distribution/block/cache/lookup.rs | 26 +- .../block/cohort/address_updates.rs | 70 +- .../src/distribution/block/cohort/sent.rs | 13 +- .../distribution/block/cohort/tx_counts.rs | 10 +- .../distribution/block/cohort/with_source.rs | 28 +- .../src/distribution/block/utxo/inputs.rs | 6 +- .../src/distribution/block/utxo/outputs.rs | 6 +- .../src/distribution/compute/block_loop.rs | 12 +- .../src/distribution/compute/readers.rs | 2 +- .../src/distribution/compute/write.rs | 12 +- .../src/distribution/state/cohort/address.rs | 37 +- crates/brk_computer/src/distribution/vecs.rs | 20 +- .../examples/indexer_read_speed.rs | 2 +- crates/brk_query/src/impl/address.rs | 4 +- crates/brk_types/src/anyaddressindex.rs | 14 +- crates/brk_types/src/emptyaddressdata.rs | 10 +- ...dedaddressdata.rs => fundedaddressdata.rs} | 18 +- ...daddressindex.rs => fundedaddressindex.rs} | 28 +- crates/brk_types/src/index.rs | 12 +- crates/brk_types/src/lib.rs | 16 +- crates/brk_types/src/supply_state.rs | 6 +- website/scripts/chart/colors.js | 30 +- website/scripts/chart/index.js | 2 +- website/scripts/options/chain.js | 1373 ----------------- website/scripts/options/context.js | 38 +- .../scripts/options/distribution/address.js | 289 +++- website/scripts/options/distribution/data.js | 11 +- website/scripts/options/distribution/utxo.js | 678 +++++--- website/scripts/options/market/momentum.js | 1 - website/scripts/options/market/performance.js | 2 - website/scripts/options/market/volatility.js | 2 - website/scripts/options/mining.js | 668 ++++---- website/scripts/options/network.js | 1245 +++++++++++---- website/scripts/options/series.js | 515 +++---- website/scripts/options/shared.js | 72 +- website/scripts/types.js | 10 +- website/scripts/utils/array.js | 17 + website/scripts/utils/units.js | 8 - 44 files changed, 2637 insertions(+), 2800 deletions(-) rename crates/brk_types/src/{loadedaddressdata.rs => fundedaddressdata.rs} (94%) rename crates/brk_types/src/{loadedaddressindex.rs => fundedaddressindex.rs} (64%) delete mode 100644 website/scripts/options/chain.js diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index fc49f6c98..2498af893 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -368,7 +368,7 @@ const _I27: &[Index] = &[Index::TxIndex]; const _I28: &[Index] = &[Index::UnknownOutputIndex]; const _I29: &[Index] = &[Index::WeekIndex]; const _I30: &[Index] = &[Index::YearIndex]; -const _I31: &[Index] = &[Index::LoadedAddressIndex]; +const _I31: &[Index] = &[Index::FundedAddressIndex]; const _I32: &[Index] = &[Index::EmptyAddressIndex]; #[inline] @@ -829,7 +829,7 @@ impl MetricPattern for MetricPattern30 { fn get(&self pub struct MetricPattern31By { client: Arc, name: Arc, _marker: std::marker::PhantomData } impl MetricPattern31By { - pub fn loadedaddressindex(&self) -> MetricEndpointBuilder { _ep(&self.client, &self.name, Index::LoadedAddressIndex) } + pub fn loadedaddressindex(&self) -> MetricEndpointBuilder { _ep(&self.client, &self.name, Index::FundedAddressIndex) } } pub struct MetricPattern31 { name: Arc, pub by: MetricPattern31By } @@ -5112,7 +5112,7 @@ pub struct MetricsTree_Distribution { pub total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, pub new_addr_count: MetricsTree_Distribution_NewAddrCount, pub growth_rate: MetricsTree_Distribution_GrowthRate, - pub loadedaddressindex: MetricPattern31, + pub loadedaddressindex: MetricPattern31, pub emptyaddressindex: MetricPattern32, } @@ -5165,7 +5165,7 @@ impl MetricsTree_Distribution_AnyAddressIndexes { /// Metrics tree node. pub struct MetricsTree_Distribution_AddressesData { - pub loaded: MetricPattern31, + pub loaded: MetricPattern31, pub empty: MetricPattern32, } diff --git a/crates/brk_cohort/src/by_any_address.rs b/crates/brk_cohort/src/by_any_address.rs index 46607e3e2..997f51839 100644 --- a/crates/brk_cohort/src/by_any_address.rs +++ b/crates/brk_cohort/src/by_any_address.rs @@ -2,13 +2,13 @@ use brk_traversable::Traversable; #[derive(Debug, Default, Traversable)] pub struct ByAnyAddress { - pub loaded: T, + pub funded: T, pub empty: T, } impl ByAnyAddress> { pub fn take(&mut self) { - self.loaded.take(); + self.funded.take(); self.empty.take(); } } diff --git a/crates/brk_computer/examples/computer_read.rs b/crates/brk_computer/examples/computer_read.rs index 9918dc524..78ec9993a 100644 --- a/crates/brk_computer/examples/computer_read.rs +++ b/crates/brk_computer/examples/computer_read.rs @@ -36,27 +36,47 @@ fn run() -> Result<()> { let start = Instant::now(); let mut buf = Vec::new(); empty_data.write_json(Some(empty_data.len() - 1), Some(empty_data.len()), &mut buf)?; - println!("emptyaddressdata last item JSON: {}", String::from_utf8_lossy(&buf)); + println!( + "emptyaddressdata last item JSON: {}", + String::from_utf8_lossy(&buf) + ); println!("Time for BytesVec write_json: {:?}", start.elapsed()); // Test emptyaddressindex (LazyVecFrom1 wrapper) - computed access let empty_index = &computer.distribution.emptyaddressindex; - println!("\nemptyaddressindex (LazyVecFrom1) len: {}", empty_index.len()); + println!( + "\nemptyaddressindex (LazyVecFrom1) len: {}", + empty_index.len() + ); let start = Instant::now(); let mut buf = Vec::new(); - empty_index.write_json(Some(empty_index.len() - 1), Some(empty_index.len()), &mut buf)?; - println!("emptyaddressindex last item JSON: {}", String::from_utf8_lossy(&buf)); + empty_index.write_json( + Some(empty_index.len() - 1), + Some(empty_index.len()), + &mut buf, + )?; + println!( + "emptyaddressindex last item JSON: {}", + String::from_utf8_lossy(&buf) + ); println!("Time for LazyVecFrom1 write_json: {:?}", start.elapsed()); - // Compare with loaded versions - let loaded_data = &computer.distribution.addresses_data.loaded; - println!("\nloadedaddressdata (BytesVec) len: {}", loaded_data.len()); + // Compare with funded versions + let funded_data = &computer.distribution.addresses_data.funded; + println!("\nfundedaddressdata (BytesVec) len: {}", funded_data.len()); let start = Instant::now(); let mut buf = Vec::new(); - loaded_data.write_json(Some(loaded_data.len() - 1), Some(loaded_data.len()), &mut buf)?; - println!("loadedaddressdata last item JSON: {}", String::from_utf8_lossy(&buf)); + funded_data.write_json( + Some(funded_data.len() - 1), + Some(funded_data.len()), + &mut buf, + )?; + println!( + "fundedaddressdata last item JSON: {}", + String::from_utf8_lossy(&buf) + ); println!("Time for BytesVec write_json: {:?}", start.elapsed()); Ok(()) diff --git a/crates/brk_computer/src/distribution/address/data.rs b/crates/brk_computer/src/distribution/address/data.rs index 97658f189..b227b3f9d 100644 --- a/crates/brk_computer/src/distribution/address/data.rs +++ b/crates/brk_computer/src/distribution/address/data.rs @@ -1,7 +1,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{ - EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex, Version, + EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height, Version, }; use rayon::prelude::*; use vecdb::{ @@ -10,10 +10,10 @@ use vecdb::{ const SAVED_STAMPED_CHANGES: u16 = 10; -/// Storage for both loaded and empty address data. +/// Storage for both funded and empty address data. #[derive(Clone, Traversable)] pub struct AddressesDataVecs { - pub loaded: BytesVec, + pub funded: BytesVec, pub empty: BytesVec, } @@ -21,8 +21,8 @@ impl AddressesDataVecs { /// Import from database. pub fn forced_import(db: &Database, version: Version) -> Result { Ok(Self { - loaded: BytesVec::forced_import_with( - ImportOptions::new(db, "loadedaddressdata", version) + funded: BytesVec::forced_import_with( + ImportOptions::new(db, "fundedaddressdata", version) .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), )?, empty: BytesVec::forced_import_with( @@ -32,31 +32,31 @@ impl AddressesDataVecs { }) } - /// Get minimum stamped height across loaded and empty data. + /// Get minimum stamped height across funded and empty data. pub fn min_stamped_height(&self) -> Height { - Height::from(self.loaded.stamp()) + Height::from(self.funded.stamp()) .incremented() .min(Height::from(self.empty.stamp()).incremented()) } - /// Rollback both loaded and empty data to before the given stamp. + /// Rollback both funded and empty data to before the given stamp. pub fn rollback_before(&mut self, stamp: Stamp) -> Result<[Stamp; 2]> { Ok([ - self.loaded.rollback_before(stamp)?, + self.funded.rollback_before(stamp)?, self.empty.rollback_before(stamp)?, ]) } - /// Reset both loaded and empty data. + /// Reset both funded and empty data. pub fn reset(&mut self) -> Result<()> { - self.loaded.reset()?; + self.funded.reset()?; self.empty.reset()?; Ok(()) } - /// Flush both loaded and empty data with stamp. + /// Flush both funded and empty data with stamp. pub fn write(&mut self, stamp: Stamp, with_changes: bool) -> Result<()> { - self.loaded + self.funded .stamped_write_maybe_with_changes(stamp, with_changes)?; self.empty .stamped_write_maybe_with_changes(stamp, with_changes)?; @@ -66,7 +66,7 @@ impl AddressesDataVecs { /// Returns a parallel iterator over all vecs for parallel writing. pub fn par_iter_mut(&mut self) -> impl ParallelIterator { vec![ - &mut self.loaded as &mut dyn AnyStoredVec, + &mut self.funded as &mut dyn AnyStoredVec, &mut self.empty as &mut dyn AnyStoredVec, ] .into_par_iter() diff --git a/crates/brk_computer/src/distribution/address/indexes/any.rs b/crates/brk_computer/src/distribution/address/indexes/any.rs index 406a0a447..8f6434c1f 100644 --- a/crates/brk_computer/src/distribution/address/indexes/any.rs +++ b/crates/brk_computer/src/distribution/address/indexes/any.rs @@ -136,7 +136,7 @@ define_any_address_indexes_vecs!( impl AnyAddressIndexesVecs { /// Process index updates in parallel by address type. - /// Accepts two maps (e.g. from empty and loaded processing) and merges per-thread. + /// Accepts two maps (e.g. from empty and funded processing) and merges per-thread. /// Updates existing entries and pushes new ones (sorted). /// Returns (update_count, push_count). pub fn par_batch_update( diff --git a/crates/brk_computer/src/distribution/block/cache/address.rs b/crates/brk_computer/src/distribution/block/cache/address.rs index a6539e0da..0ca9f6363 100644 --- a/crates/brk_computer/src/distribution/block/cache/address.rs +++ b/crates/brk_computer/src/distribution/block/cache/address.rs @@ -1,5 +1,5 @@ use brk_cohort::ByAddressType; -use brk_types::{AnyAddressDataIndexEnum, LoadedAddressData, OutputType, TypeIndex}; +use brk_types::{AnyAddressDataIndexEnum, FundedAddressData, OutputType, TypeIndex}; use crate::distribution::{ address::{AddressTypeToTypeIndexMap, AddressesDataVecs, AnyAddressIndexesVecs}, @@ -7,7 +7,7 @@ use crate::distribution::{ }; use super::super::cohort::{ - EmptyAddressDataWithSource, LoadedAddressDataWithSource, TxIndexVec, WithAddressDataSource, + EmptyAddressDataWithSource, FundedAddressDataWithSource, TxIndexVec, WithAddressDataSource, update_tx_counts, }; use super::lookup::AddressLookup; @@ -15,7 +15,7 @@ use super::lookup::AddressLookup; /// Cache for address data within a flush interval. pub struct AddressCache { /// Addresses with non-zero balance - loaded: AddressTypeToTypeIndexMap, + funded: AddressTypeToTypeIndexMap, /// Addresses that became empty (zero balance) empty: AddressTypeToTypeIndexMap, } @@ -29,15 +29,15 @@ impl Default for AddressCache { impl AddressCache { pub fn new() -> Self { Self { - loaded: AddressTypeToTypeIndexMap::default(), + funded: AddressTypeToTypeIndexMap::default(), empty: AddressTypeToTypeIndexMap::default(), } } - /// Check if address is in cache (either loaded or empty). + /// Check if address is in cache (either funded or empty). #[inline] pub fn contains(&self, address_type: OutputType, typeindex: TypeIndex) -> bool { - self.loaded + self.funded .get(address_type) .is_some_and(|m| m.contains_key(&typeindex)) || self @@ -46,24 +46,24 @@ impl AddressCache { .is_some_and(|m| m.contains_key(&typeindex)) } - /// Merge address data into loaded cache. + /// Merge address data into funded cache. #[inline] - pub fn merge_loaded(&mut self, data: AddressTypeToTypeIndexMap) { - self.loaded.merge_mut(data); + pub fn merge_funded(&mut self, data: AddressTypeToTypeIndexMap) { + self.funded.merge_mut(data); } /// Create an AddressLookup view into this cache. #[inline] pub fn as_lookup(&mut self) -> AddressLookup<'_> { AddressLookup { - loaded: &mut self.loaded, + funded: &mut self.funded, empty: &mut self.empty, } } /// Update transaction counts for addresses. pub fn update_tx_counts(&mut self, txindex_vecs: AddressTypeToTypeIndexMap) { - update_tx_counts(&mut self.loaded, &mut self.empty, txindex_vecs); + update_tx_counts(&mut self.funded, &mut self.empty, txindex_vecs); } /// Take the cache contents for flushing, leaving empty caches. @@ -71,18 +71,18 @@ impl AddressCache { &mut self, ) -> ( AddressTypeToTypeIndexMap, - AddressTypeToTypeIndexMap, + AddressTypeToTypeIndexMap, ) { ( std::mem::take(&mut self.empty), - std::mem::take(&mut self.loaded), + std::mem::take(&mut self.funded), ) } } /// Load address data from storage or create new. /// -/// Returns None if address is already in cache (loaded or empty). +/// Returns None if address is already in cache (funded or empty). #[allow(clippy::too_many_arguments)] pub fn load_uncached_address_data( address_type: OutputType, @@ -92,11 +92,11 @@ pub fn load_uncached_address_data( vr: &VecsReaders, any_address_indexes: &AnyAddressIndexesVecs, addresses_data: &AddressesDataVecs, -) -> Option { +) -> Option { // Check if this is a new address (typeindex >= first for this height) let first = *first_addressindexes.get(address_type).unwrap(); if first <= typeindex { - return Some(WithAddressDataSource::New(LoadedAddressData::default())); + return Some(WithAddressDataSource::New(FundedAddressData::default())); } // Skip if already in cache @@ -109,13 +109,13 @@ pub fn load_uncached_address_data( let anyaddressindex = any_address_indexes.get(address_type, typeindex, reader); Some(match anyaddressindex.to_enum() { - AnyAddressDataIndexEnum::Loaded(loaded_index) => { - let reader = &vr.anyaddressindex_to_anyaddressdata.loaded; + AnyAddressDataIndexEnum::Funded(funded_index) => { + let reader = &vr.anyaddressindex_to_anyaddressdata.funded; // Use get_any_or_read_unwrap to check updated layer (needed after rollback) - let loaded_data = addresses_data - .loaded - .get_any_or_read_unwrap(loaded_index, reader); - WithAddressDataSource::FromLoaded(loaded_index, loaded_data) + let funded_data = addresses_data + .funded + .get_any_or_read_unwrap(funded_index, reader); + WithAddressDataSource::FromFunded(funded_index, funded_data) } AnyAddressDataIndexEnum::Empty(empty_index) => { let reader = &vr.anyaddressindex_to_anyaddressdata.empty; diff --git a/crates/brk_computer/src/distribution/block/cache/lookup.rs b/crates/brk_computer/src/distribution/block/cache/lookup.rs index 8f2674c57..1fc17230c 100644 --- a/crates/brk_computer/src/distribution/block/cache/lookup.rs +++ b/crates/brk_computer/src/distribution/block/cache/lookup.rs @@ -1,9 +1,9 @@ -use brk_types::{LoadedAddressData, OutputType, TypeIndex}; +use brk_types::{FundedAddressData, OutputType, TypeIndex}; use crate::distribution::address::AddressTypeToTypeIndexMap; use super::super::cohort::{ - EmptyAddressDataWithSource, LoadedAddressDataWithSource, WithAddressDataSource, + EmptyAddressDataWithSource, FundedAddressDataWithSource, WithAddressDataSource, }; /// Tracking status of an address - determines cohort update strategy. @@ -19,7 +19,7 @@ pub enum TrackingStatus { /// Context for looking up and storing address data during block processing. pub struct AddressLookup<'a> { - pub loaded: &'a mut AddressTypeToTypeIndexMap, + pub funded: &'a mut AddressTypeToTypeIndexMap, pub empty: &'a mut AddressTypeToTypeIndexMap, } @@ -28,21 +28,21 @@ impl<'a> AddressLookup<'a> { &mut self, output_type: OutputType, type_index: TypeIndex, - ) -> (&mut LoadedAddressDataWithSource, TrackingStatus) { + ) -> (&mut FundedAddressDataWithSource, TrackingStatus) { use std::collections::hash_map::Entry; - let map = self.loaded.get_mut(output_type).unwrap(); + let map = self.funded.get_mut(output_type).unwrap(); match map.entry(type_index) { Entry::Occupied(entry) => { // Address is in cache. Need to determine if it's been processed - // by process_received (added to a cohort) or just loaded this block. + // by process_received (added to a cohort) or just funded this block. // // - If wrapper is New AND funded_txo_count == 0: hasn't received yet, // was just created in process_outputs this block → New // - If wrapper is New AND funded_txo_count > 0: received in previous // block but still in cache (no flush) → Tracked - // - If wrapper is FromLoaded: loaded from storage → Tracked + // - If wrapper is FromFunded: funded from storage → Tracked // - If wrapper is FromEmpty AND utxo_count == 0: still empty → WasEmpty // - If wrapper is FromEmpty AND utxo_count > 0: already received → Tracked let status = match entry.get() { @@ -53,7 +53,7 @@ impl<'a> AddressLookup<'a> { TrackingStatus::Tracked } } - WithAddressDataSource::FromLoaded(..) => TrackingStatus::Tracked, + WithAddressDataSource::FromFunded(..) => TrackingStatus::Tracked, WithAddressDataSource::FromEmpty(_, data) => { if data.utxo_count() == 0 { TrackingStatus::WasEmpty @@ -71,7 +71,7 @@ impl<'a> AddressLookup<'a> { return (entry.insert(empty_data.into()), TrackingStatus::WasEmpty); } ( - entry.insert(WithAddressDataSource::New(LoadedAddressData::default())), + entry.insert(WithAddressDataSource::New(FundedAddressData::default())), TrackingStatus::New, ) } @@ -83,18 +83,18 @@ impl<'a> AddressLookup<'a> { &mut self, output_type: OutputType, type_index: TypeIndex, - ) -> &mut LoadedAddressDataWithSource { - self.loaded + ) -> &mut FundedAddressDataWithSource { + self.funded .get_mut(output_type) .unwrap() .get_mut(&type_index) .expect("Address must exist for send") } - /// Move address from loaded to empty set. + /// Move address from funded to empty set. pub fn move_to_empty(&mut self, output_type: OutputType, type_index: TypeIndex) { let data = self - .loaded + .funded .get_mut(output_type) .unwrap() .remove(&type_index) diff --git a/crates/brk_computer/src/distribution/block/cohort/address_updates.rs b/crates/brk_computer/src/distribution/block/cohort/address_updates.rs index 3b36f8647..24825297e 100644 --- a/crates/brk_computer/src/distribution/block/cohort/address_updates.rs +++ b/crates/brk_computer/src/distribution/block/cohort/address_updates.rs @@ -1,40 +1,40 @@ use brk_error::Result; use brk_types::{ - AnyAddressIndex, EmptyAddressData, EmptyAddressIndex, LoadedAddressData, LoadedAddressIndex, + AnyAddressIndex, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, OutputType, TypeIndex, }; use vecdb::AnyVec; use crate::distribution::{AddressTypeToTypeIndexMap, AddressesDataVecs}; -use super::with_source::{EmptyAddressDataWithSource, LoadedAddressDataWithSource}; +use super::with_source::{EmptyAddressDataWithSource, FundedAddressDataWithSource}; -/// Process loaded address data updates. +/// Process funded address data updates. /// /// Handles: -/// - New loaded address: push to loaded storage -/// - Updated loaded address (was loaded): update in place -/// - Transition empty -> loaded: delete from empty, push to loaded -pub fn process_loaded_addresses( +/// - New funded address: push to funded storage +/// - Updated funded address (was funded): update in place +/// - Transition empty -> funded: delete from empty, push to funded +pub fn process_funded_addresses( addresses_data: &mut AddressesDataVecs, - loaded_updates: AddressTypeToTypeIndexMap, + funded_updates: AddressTypeToTypeIndexMap, ) -> Result> { - let total: usize = loaded_updates.iter().map(|(_, m)| m.len()).sum(); + let total: usize = funded_updates.iter().map(|(_, m)| m.len()).sum(); - let mut updates: Vec<(LoadedAddressIndex, LoadedAddressData)> = Vec::with_capacity(total); + let mut updates: Vec<(FundedAddressIndex, FundedAddressData)> = Vec::with_capacity(total); let mut deletes: Vec = Vec::with_capacity(total); - let mut pushes: Vec<(OutputType, TypeIndex, LoadedAddressData)> = Vec::with_capacity(total); + let mut pushes: Vec<(OutputType, TypeIndex, FundedAddressData)> = Vec::with_capacity(total); - for (address_type, items) in loaded_updates.into_iter() { + for (address_type, items) in funded_updates.into_iter() { for (typeindex, source) in items { match source { - LoadedAddressDataWithSource::New(data) => { + FundedAddressDataWithSource::New(data) => { pushes.push((address_type, typeindex, data)); } - LoadedAddressDataWithSource::FromLoaded(index, data) => { + FundedAddressDataWithSource::FromFunded(index, data) => { updates.push((index, data)); } - LoadedAddressDataWithSource::FromEmpty(empty_index, data) => { + FundedAddressDataWithSource::FromEmpty(empty_index, data) => { deletes.push(empty_index); pushes.push((address_type, typeindex, data)); } @@ -49,16 +49,16 @@ pub fn process_loaded_addresses( // Phase 2: Updates (in-place) for (index, data) in updates { - addresses_data.loaded.update(index, data)?; + addresses_data.funded.update(index, data)?; } // Phase 3: Pushes (fill holes first, then pure pushes) let mut result = AddressTypeToTypeIndexMap::with_capacity(pushes.len() / 4); - let holes_count = addresses_data.loaded.holes().len(); + let holes_count = addresses_data.funded.holes().len(); let mut pushes_iter = pushes.into_iter(); for (address_type, typeindex, data) in pushes_iter.by_ref().take(holes_count) { - let index = addresses_data.loaded.fill_first_hole_or_push(data)?; + let index = addresses_data.funded.fill_first_hole_or_push(data)?; result .get_mut(address_type) .unwrap() @@ -66,14 +66,14 @@ pub fn process_loaded_addresses( } // Pure pushes - no holes remain - addresses_data.loaded.reserve_pushed(pushes_iter.len()); - let mut next_index = addresses_data.loaded.len(); + addresses_data.funded.reserve_pushed(pushes_iter.len()); + let mut next_index = addresses_data.funded.len(); for (address_type, typeindex, data) in pushes_iter { - addresses_data.loaded.push(data); - result - .get_mut(address_type) - .unwrap() - .insert(typeindex, AnyAddressIndex::from(LoadedAddressIndex::from(next_index))); + addresses_data.funded.push(data); + result.get_mut(address_type).unwrap().insert( + typeindex, + AnyAddressIndex::from(FundedAddressIndex::from(next_index)), + ); next_index += 1; } @@ -85,7 +85,7 @@ pub fn process_loaded_addresses( /// Handles: /// - New empty address: push to empty storage /// - Updated empty address (was empty): update in place -/// - Transition loaded -> empty: delete from loaded, push to empty +/// - Transition funded -> empty: delete from funded, push to empty pub fn process_empty_addresses( addresses_data: &mut AddressesDataVecs, empty_updates: AddressTypeToTypeIndexMap, @@ -93,7 +93,7 @@ pub fn process_empty_addresses( let total: usize = empty_updates.iter().map(|(_, m)| m.len()).sum(); let mut updates: Vec<(EmptyAddressIndex, EmptyAddressData)> = Vec::with_capacity(total); - let mut deletes: Vec = Vec::with_capacity(total); + let mut deletes: Vec = Vec::with_capacity(total); let mut pushes: Vec<(OutputType, TypeIndex, EmptyAddressData)> = Vec::with_capacity(total); for (address_type, items) in empty_updates.into_iter() { @@ -105,8 +105,8 @@ pub fn process_empty_addresses( EmptyAddressDataWithSource::FromEmpty(index, data) => { updates.push((index, data)); } - EmptyAddressDataWithSource::FromLoaded(loaded_index, data) => { - deletes.push(loaded_index); + EmptyAddressDataWithSource::FromFunded(funded_index, data) => { + deletes.push(funded_index); pushes.push((address_type, typeindex, data)); } } @@ -114,8 +114,8 @@ pub fn process_empty_addresses( } // Phase 1: Deletes (creates holes) - for loaded_index in deletes { - addresses_data.loaded.delete(loaded_index); + for funded_index in deletes { + addresses_data.funded.delete(funded_index); } // Phase 2: Updates (in-place) @@ -141,10 +141,10 @@ pub fn process_empty_addresses( let mut next_index = addresses_data.empty.len(); for (address_type, typeindex, data) in pushes_iter { addresses_data.empty.push(data); - result - .get_mut(address_type) - .unwrap() - .insert(typeindex, AnyAddressIndex::from(EmptyAddressIndex::from(next_index))); + result.get_mut(address_type).unwrap().insert( + typeindex, + AnyAddressIndex::from(EmptyAddressIndex::from(next_index)), + ); next_index += 1; } diff --git a/crates/brk_computer/src/distribution/block/cohort/sent.rs b/crates/brk_computer/src/distribution/block/cohort/sent.rs index ed9d89769..a185f3382 100644 --- a/crates/brk_computer/src/distribution/block/cohort/sent.rs +++ b/crates/brk_computer/src/distribution/block/cohort/sent.rs @@ -2,7 +2,7 @@ use brk_cohort::{AmountBucket, ByAddressType}; use brk_error::Result; use brk_types::{Age, CentsUnsigned, CheckedSub, Height, Sats, Timestamp, TypeIndex}; use rustc_hash::FxHashSet; -use vecdb::{unlikely, VecIndex}; +use vecdb::{VecIndex, unlikely}; use crate::distribution::{ address::{AddressTypeToActivityCounts, HeightToAddressTypeToVec}, @@ -131,7 +131,7 @@ pub fn process_sent( *type_addr_count -= 1; *type_empty_count += 1; - // Move from loaded to empty + // Move from funded to empty lookup.move_to_empty(output_type, type_index); } else { // Add to new cohort @@ -151,7 +151,14 @@ pub fn process_sent( .state .as_mut() .unwrap() - .send(addr_data, value, current_price.unwrap(), prev_price.unwrap(), peak_price.unwrap(), age)?; + .send( + addr_data, + value, + current_price.unwrap(), + prev_price.unwrap(), + peak_price.unwrap(), + age, + )?; } } } diff --git a/crates/brk_computer/src/distribution/block/cohort/tx_counts.rs b/crates/brk_computer/src/distribution/block/cohort/tx_counts.rs index 72838f0ab..d4cd2b04e 100644 --- a/crates/brk_computer/src/distribution/block/cohort/tx_counts.rs +++ b/crates/brk_computer/src/distribution/block/cohort/tx_counts.rs @@ -1,6 +1,6 @@ use crate::distribution::address::AddressTypeToTypeIndexMap; -use super::with_source::{EmptyAddressDataWithSource, LoadedAddressDataWithSource, TxIndexVec}; +use super::with_source::{EmptyAddressDataWithSource, FundedAddressDataWithSource, TxIndexVec}; /// Update tx_count for addresses based on unique transactions they participated in. /// @@ -8,10 +8,10 @@ use super::with_source::{EmptyAddressDataWithSource, LoadedAddressDataWithSource /// 1. Deduplicate transaction indexes (an address may appear in multiple inputs/outputs of same tx) /// 2. Add the unique count to the address's tx_count field /// -/// Addresses are looked up in loaded_cache first, then empty_cache. -/// NOTE: This should be called AFTER merging parallel-fetched address data into loaded_cache. +/// Addresses are looked up in funded_cache first, then empty_cache. +/// NOTE: This should be called AFTER merging parallel-fetched address data into funded_cache. pub fn update_tx_counts( - loaded_cache: &mut AddressTypeToTypeIndexMap, + funded_cache: &mut AddressTypeToTypeIndexMap, empty_cache: &mut AddressTypeToTypeIndexMap, mut txindex_vecs: AddressTypeToTypeIndexMap, ) { @@ -32,7 +32,7 @@ pub fn update_tx_counts( { let tx_count = txindex_vec.len() as u32; - if let Some(addr_data) = loaded_cache + if let Some(addr_data) = funded_cache .get_mut(address_type) .unwrap() .get_mut(&typeindex) diff --git a/crates/brk_computer/src/distribution/block/cohort/with_source.rs b/crates/brk_computer/src/distribution/block/cohort/with_source.rs index 0ea36f665..5c95f5937 100644 --- a/crates/brk_computer/src/distribution/block/cohort/with_source.rs +++ b/crates/brk_computer/src/distribution/block/cohort/with_source.rs @@ -1,8 +1,10 @@ -use brk_types::{EmptyAddressData, EmptyAddressIndex, LoadedAddressData, LoadedAddressIndex, TxIndex}; +use brk_types::{ + EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, TxIndex, +}; use smallvec::SmallVec; -/// Loaded address data with source tracking for flush operations. -pub type LoadedAddressDataWithSource = WithAddressDataSource; +/// Funded address data with source tracking for flush operations. +pub type FundedAddressDataWithSource = WithAddressDataSource; /// Empty address data with source tracking for flush operations. pub type EmptyAddressDataWithSource = WithAddressDataSource; @@ -18,9 +20,9 @@ pub type TxIndexVec = SmallVec<[TxIndex; 4]>; pub enum WithAddressDataSource { /// Brand new address (never seen before) New(T), - /// Loaded from loaded address storage (with original index) - FromLoaded(LoadedAddressIndex, T), - /// Loaded from empty address storage (with original index) + /// Funded from funded address storage (with original index) + FromFunded(FundedAddressIndex, T), + /// Funded from empty address storage (with original index) FromEmpty(EmptyAddressIndex, T), } @@ -29,7 +31,7 @@ impl std::ops::Deref for WithAddressDataSource { fn deref(&self) -> &Self::Target { match self { - Self::New(v) | Self::FromLoaded(_, v) | Self::FromEmpty(_, v) => v, + Self::New(v) | Self::FromFunded(_, v) | Self::FromEmpty(_, v) => v, } } } @@ -37,28 +39,28 @@ impl std::ops::Deref for WithAddressDataSource { impl std::ops::DerefMut for WithAddressDataSource { fn deref_mut(&mut self) -> &mut Self::Target { match self { - Self::New(v) | Self::FromLoaded(_, v) | Self::FromEmpty(_, v) => v, + Self::New(v) | Self::FromFunded(_, v) | Self::FromEmpty(_, v) => v, } } } -impl From> for WithAddressDataSource { +impl From> for WithAddressDataSource { #[inline] fn from(value: WithAddressDataSource) -> Self { match value { WithAddressDataSource::New(v) => Self::New(v.into()), - WithAddressDataSource::FromLoaded(i, v) => Self::FromLoaded(i, v.into()), + WithAddressDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()), WithAddressDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()), } } } -impl From> for WithAddressDataSource { +impl From> for WithAddressDataSource { #[inline] - fn from(value: WithAddressDataSource) -> Self { + fn from(value: WithAddressDataSource) -> Self { match value { WithAddressDataSource::New(v) => Self::New(v.into()), - WithAddressDataSource::FromLoaded(i, v) => Self::FromLoaded(i, v.into()), + WithAddressDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()), WithAddressDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()), } } diff --git a/crates/brk_computer/src/distribution/block/utxo/inputs.rs b/crates/brk_computer/src/distribution/block/utxo/inputs.rs index 9185e689e..81274df0c 100644 --- a/crates/brk_computer/src/distribution/block/utxo/inputs.rs +++ b/crates/brk_computer/src/distribution/block/utxo/inputs.rs @@ -13,7 +13,7 @@ use crate::distribution::address::HeightToAddressTypeToVec; use super::super::{ cache::{AddressCache, load_uncached_address_data}, - cohort::{LoadedAddressDataWithSource, TxIndexVec}, + cohort::{FundedAddressDataWithSource, TxIndexVec}, }; /// Result of processing inputs for a block. @@ -23,7 +23,7 @@ pub struct InputsResult { /// Per-height, per-address-type sent data: (typeindex, value) for each address. pub sent_data: HeightToAddressTypeToVec<(TypeIndex, Sats)>, /// Address data looked up during processing, keyed by (address_type, typeindex). - pub address_data: AddressTypeToTypeIndexMap, + pub address_data: AddressTypeToTypeIndexMap, /// Transaction indexes per address for tx_count tracking. pub txindex_vecs: AddressTypeToTypeIndexMap, } @@ -100,7 +100,7 @@ pub fn process_inputs( ); let mut sent_data = HeightToAddressTypeToVec::with_capacity(estimated_unique_heights); let mut address_data = - AddressTypeToTypeIndexMap::::with_capacity(estimated_per_type); + AddressTypeToTypeIndexMap::::with_capacity(estimated_per_type); let mut txindex_vecs = AddressTypeToTypeIndexMap::::with_capacity(estimated_per_type); diff --git a/crates/brk_computer/src/distribution/block/utxo/outputs.rs b/crates/brk_computer/src/distribution/block/utxo/outputs.rs index e06289376..8879b0e3f 100644 --- a/crates/brk_computer/src/distribution/block/utxo/outputs.rs +++ b/crates/brk_computer/src/distribution/block/utxo/outputs.rs @@ -11,7 +11,7 @@ use crate::distribution::{ use super::super::{ cache::{AddressCache, load_uncached_address_data}, - cohort::{LoadedAddressDataWithSource, TxIndexVec}, + cohort::{FundedAddressDataWithSource, TxIndexVec}, }; /// Result of processing outputs for a block. @@ -21,7 +21,7 @@ pub struct OutputsResult { /// Per-address-type received data: (typeindex, value) for each address. pub received_data: AddressTypeToVec<(TypeIndex, Sats)>, /// Address data looked up during processing, keyed by (address_type, typeindex). - pub address_data: AddressTypeToTypeIndexMap, + pub address_data: AddressTypeToTypeIndexMap, /// Transaction indexes per address for tx_count tracking. pub txindex_vecs: AddressTypeToTypeIndexMap, } @@ -50,7 +50,7 @@ pub fn process_outputs( let mut transacted = Transacted::default(); let mut received_data = AddressTypeToVec::with_capacity(estimated_per_type); let mut address_data = - AddressTypeToTypeIndexMap::::with_capacity(estimated_per_type); + AddressTypeToTypeIndexMap::::with_capacity(estimated_per_type); let mut txindex_vecs = AddressTypeToTypeIndexMap::::with_capacity(estimated_per_type); diff --git a/crates/brk_computer/src/distribution/compute/block_loop.rs b/crates/brk_computer/src/distribution/compute/block_loop.rs index ec508ed13..338347b30 100644 --- a/crates/brk_computer/src/distribution/compute/block_loop.rs +++ b/crates/brk_computer/src/distribution/compute/block_loop.rs @@ -263,8 +263,8 @@ pub fn process_blocks( }); // Merge new address data into current cache - cache.merge_loaded(outputs_result.address_data); - cache.merge_loaded(inputs_result.address_data); + cache.merge_funded(outputs_result.address_data); + cache.merge_funded(inputs_result.address_data); // Combine txindex_vecs from outputs and inputs, then update tx_count let combined_txindex_vecs = outputs_result @@ -425,14 +425,14 @@ pub fn process_blocks( // Drop readers to release mmap handles drop(vr); - let (empty_updates, loaded_updates) = cache.take(); + let (empty_updates, funded_updates) = cache.take(); // Process address updates (mutations) process_address_updates( &mut vecs.addresses_data, &mut vecs.any_address_indexes, empty_updates, - loaded_updates, + funded_updates, )?; let _lock = exit.lock(); @@ -451,14 +451,14 @@ pub fn process_blocks( let _lock = exit.lock(); drop(vr); - let (empty_updates, loaded_updates) = cache.take(); + let (empty_updates, funded_updates) = cache.take(); // Process address updates (mutations) process_address_updates( &mut vecs.addresses_data, &mut vecs.any_address_indexes, empty_updates, - loaded_updates, + funded_updates, )?; // Write to disk (pure I/O) - save changes for rollback diff --git a/crates/brk_computer/src/distribution/compute/readers.rs b/crates/brk_computer/src/distribution/compute/readers.rs index 32410de7a..868ef6168 100644 --- a/crates/brk_computer/src/distribution/compute/readers.rs +++ b/crates/brk_computer/src/distribution/compute/readers.rs @@ -140,7 +140,7 @@ impl VecsReaders { p2wsh: any_address_indexes.p2wsh.create_reader(), }, anyaddressindex_to_anyaddressdata: ByAnyAddress { - loaded: addresses_data.loaded.create_reader(), + funded: addresses_data.funded.create_reader(), empty: addresses_data.empty.create_reader(), }, } diff --git a/crates/brk_computer/src/distribution/compute/write.rs b/crates/brk_computer/src/distribution/compute/write.rs index f7d0b85e8..fa1bbe5e7 100644 --- a/crates/brk_computer/src/distribution/compute/write.rs +++ b/crates/brk_computer/src/distribution/compute/write.rs @@ -9,8 +9,8 @@ use vecdb::{AnyStoredVec, GenericStoredVec, Stamp}; use crate::distribution::{ Vecs, block::{ - EmptyAddressDataWithSource, LoadedAddressDataWithSource, process_empty_addresses, - process_loaded_addresses, + EmptyAddressDataWithSource, FundedAddressDataWithSource, process_empty_addresses, + process_funded_addresses, }, state::BlockState, }; @@ -21,7 +21,7 @@ use super::super::address::{AddressTypeToTypeIndexMap, AddressesDataVecs, AnyAdd /// /// Applies all accumulated address changes to storage structures: /// - Processes empty address transitions -/// - Processes loaded address transitions +/// - Processes funded address transitions /// - Updates address indexes /// /// Call this before `flush()` to prepare data for writing. @@ -29,14 +29,14 @@ pub fn process_address_updates( addresses_data: &mut AddressesDataVecs, address_indexes: &mut AnyAddressIndexesVecs, empty_updates: AddressTypeToTypeIndexMap, - loaded_updates: AddressTypeToTypeIndexMap, + funded_updates: AddressTypeToTypeIndexMap, ) -> Result<()> { info!("Processing address updates..."); let i = Instant::now(); let empty_result = process_empty_addresses(addresses_data, empty_updates)?; - let loaded_result = process_loaded_addresses(addresses_data, loaded_updates)?; - address_indexes.par_batch_update(empty_result, loaded_result)?; + let funded_result = process_funded_addresses(addresses_data, funded_updates)?; + address_indexes.par_batch_update(empty_result, funded_result)?; info!("Processed address updates in {:?}", i.elapsed()); diff --git a/crates/brk_computer/src/distribution/state/cohort/address.rs b/crates/brk_computer/src/distribution/state/cohort/address.rs index 4d4b8e748..fb867af91 100644 --- a/crates/brk_computer/src/distribution/state/cohort/address.rs +++ b/crates/brk_computer/src/distribution/state/cohort/address.rs @@ -1,7 +1,7 @@ use std::path::Path; use brk_error::Result; -use brk_types::{Age, CentsUnsigned, Height, LoadedAddressData, Sats, SupplyState}; +use brk_types::{Age, CentsUnsigned, FundedAddressData, Height, Sats, SupplyState}; use vecdb::unlikely; use super::{super::cost_basis::RealizedState, base::CohortState}; @@ -42,7 +42,7 @@ impl AddressCohortState { pub fn send( &mut self, - addressdata: &mut LoadedAddressData, + addressdata: &mut FundedAddressData, value: Sats, current_price: CentsUnsigned, prev_price: CentsUnsigned, @@ -54,7 +54,10 @@ impl AddressCohortState { let current = addressdata.cost_basis_snapshot(); self.inner.send_address( - &SupplyState { utxo_count: 1, value }, + &SupplyState { + utxo_count: 1, + value, + }, current_price, prev_price, ath, @@ -68,7 +71,7 @@ impl AddressCohortState { pub fn receive( &mut self, - address_data: &mut LoadedAddressData, + address_data: &mut FundedAddressData, value: Sats, price: CentsUnsigned, ) { @@ -77,7 +80,7 @@ impl AddressCohortState { pub fn receive_outputs( &mut self, - address_data: &mut LoadedAddressData, + address_data: &mut FundedAddressData, value: Sats, price: CentsUnsigned, output_count: u32, @@ -87,19 +90,23 @@ impl AddressCohortState { let current = address_data.cost_basis_snapshot(); self.inner.receive_address( - &SupplyState { utxo_count: output_count as u64, value }, + &SupplyState { + utxo_count: output_count as u64, + value, + }, price, ¤t, &prev, ); } - pub fn add(&mut self, addressdata: &LoadedAddressData) { + pub fn add(&mut self, addressdata: &FundedAddressData) { self.addr_count += 1; - self.inner.increment_snapshot(&addressdata.cost_basis_snapshot()); + self.inner + .increment_snapshot(&addressdata.cost_basis_snapshot()); } - pub fn subtract(&mut self, addressdata: &LoadedAddressData) { + pub fn subtract(&mut self, addressdata: &FundedAddressData) { let snapshot = addressdata.cost_basis_snapshot(); // Check for potential underflow before it happens @@ -111,7 +118,11 @@ impl AddressCohortState { Address supply: {}\n\ Realized price: {}\n\ This means the address is not properly tracked in this cohort.", - self.addr_count, self.inner.supply, addressdata, snapshot.supply_state, snapshot.realized_price + self.addr_count, + self.inner.supply, + addressdata, + snapshot.supply_state, + snapshot.realized_price ); } if unlikely(self.inner.supply.value < snapshot.supply_state.value) { @@ -122,7 +133,11 @@ impl AddressCohortState { Address supply: {}\n\ Realized price: {}\n\ This means the address is not properly tracked in this cohort.", - self.addr_count, self.inner.supply, addressdata, snapshot.supply_state, snapshot.realized_price + self.addr_count, + self.inner.supply, + addressdata, + snapshot.supply_state, + snapshot.realized_price ); } diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index 2b567761e..362287901 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -4,7 +4,7 @@ use brk_error::Result; use brk_indexer::Indexer; use brk_traversable::Traversable; use brk_types::{ - DateIndex, EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex, + DateIndex, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height, SupplyState, Version, }; use tracing::{debug, info}; @@ -55,8 +55,8 @@ pub struct Vecs { /// Growth rate (new / addr_count) - lazy ratio with distribution stats, global + per-type pub growth_rate: GrowthRateVecs, - pub loadedaddressindex: - LazyVecFrom1, + pub fundedaddressindex: + LazyVecFrom1, pub emptyaddressindex: LazyVecFrom1, } @@ -92,8 +92,8 @@ impl Vecs { )?; // Create address data BytesVecs first so we can also use them for identity mappings - let loadedaddressindex_to_loadedaddressdata = BytesVec::forced_import_with( - vecdb::ImportOptions::new(&db, "loadedaddressdata", version) + let fundedaddressindex_to_fundedaddressdata = BytesVec::forced_import_with( + vecdb::ImportOptions::new(&db, "fundedaddressdata", version) .with_saved_stamped_changes(SAVED_STAMPED_CHANGES), )?; let emptyaddressindex_to_emptyaddressdata = BytesVec::forced_import_with( @@ -102,10 +102,10 @@ impl Vecs { )?; // Identity mappings for traversable - let loadedaddressindex = LazyVecFrom1::init( - "loadedaddressindex", + let fundedaddressindex = LazyVecFrom1::init( + "fundedaddressindex", version, - loadedaddressindex_to_loadedaddressdata.boxed_clone(), + fundedaddressindex_to_fundedaddressdata.boxed_clone(), |index, _| Some(index), ); let emptyaddressindex = LazyVecFrom1::init( @@ -156,10 +156,10 @@ impl Vecs { any_address_indexes: AnyAddressIndexesVecs::forced_import(&db, version)?, addresses_data: AddressesDataVecs { - loaded: loadedaddressindex_to_loadedaddressdata, + funded: fundedaddressindex_to_fundedaddressdata, empty: emptyaddressindex_to_emptyaddressdata, }, - loadedaddressindex, + fundedaddressindex, emptyaddressindex, db, diff --git a/crates/brk_indexer/examples/indexer_read_speed.rs b/crates/brk_indexer/examples/indexer_read_speed.rs index 61b888dcf..c2ab372db 100644 --- a/crates/brk_indexer/examples/indexer_read_speed.rs +++ b/crates/brk_indexer/examples/indexer_read_speed.rs @@ -30,7 +30,7 @@ fn main() -> Result<()> { println!("Loading indexer from: {}", outputs_dir.display()); let indexer = Indexer::forced_import(&outputs_dir)?; - println!("✅ Indexer loaded\n"); + println!("✅ Indexer funded\n"); // Warmup run println!("🔥 Warmup run..."); diff --git a/crates/brk_query/src/impl/address.rs b/crates/brk_query/src/impl/address.rs index 886f79093..a8695682d 100644 --- a/crates/brk_query/src/impl/address.rs +++ b/crates/brk_query/src/impl/address.rs @@ -58,10 +58,10 @@ impl Query { .get_once(outputtype, type_index)?; let address_data = match any_address_index.to_enum() { - AnyAddressDataIndexEnum::Loaded(index) => computer + AnyAddressDataIndexEnum::Funded(index) => computer .distribution .addresses_data - .loaded + .funded .iter()? .get_unwrap(index), AnyAddressDataIndexEnum::Empty(index) => computer diff --git a/crates/brk_types/src/anyaddressindex.rs b/crates/brk_types/src/anyaddressindex.rs index 7290662e7..f07c24721 100644 --- a/crates/brk_types/src/anyaddressindex.rs +++ b/crates/brk_types/src/anyaddressindex.rs @@ -4,11 +4,11 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use vecdb::{Bytes, Formattable}; -use crate::{EmptyAddressIndex, LoadedAddressIndex, TypeIndex}; +use crate::{EmptyAddressIndex, FundedAddressIndex, TypeIndex}; const MIN_EMPTY_INDEX: u32 = u32::MAX - 4_000_000_000; -/// Unified index for any address type (loaded or empty) +/// Unified index for any address type (funded or empty) #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Bytes, JsonSchema)] pub struct AnyAddressIndex(TypeIndex); @@ -18,9 +18,9 @@ impl AnyAddressIndex { } } -impl From for AnyAddressIndex { +impl From for AnyAddressIndex { #[inline] - fn from(value: LoadedAddressIndex) -> Self { + fn from(value: FundedAddressIndex) -> Self { if u32::from(value) >= MIN_EMPTY_INDEX { panic!("{value} is higher than MIN_EMPTY_INDEX ({MIN_EMPTY_INDEX})") } @@ -70,7 +70,7 @@ impl Formattable for AnyAddressIndex { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum AnyAddressDataIndexEnum { - Loaded(LoadedAddressIndex), + Funded(FundedAddressIndex), Empty(EmptyAddressIndex), } @@ -81,7 +81,7 @@ impl From for AnyAddressDataIndexEnum { if uvalue >= MIN_EMPTY_INDEX { Self::Empty(EmptyAddressIndex::from(uvalue - MIN_EMPTY_INDEX)) } else { - Self::Loaded(LoadedAddressIndex::from(value.0)) + Self::Funded(FundedAddressIndex::from(value.0)) } } } @@ -90,7 +90,7 @@ impl From for AnyAddressIndex { #[inline] fn from(value: AnyAddressDataIndexEnum) -> Self { match value { - AnyAddressDataIndexEnum::Loaded(idx) => Self::from(idx), + AnyAddressDataIndexEnum::Funded(idx) => Self::from(idx), AnyAddressDataIndexEnum::Empty(idx) => Self::from(idx), } } diff --git a/crates/brk_types/src/emptyaddressdata.rs b/crates/brk_types/src/emptyaddressdata.rs index e64b4a71a..99c2f9976 100644 --- a/crates/brk_types/src/emptyaddressdata.rs +++ b/crates/brk_types/src/emptyaddressdata.rs @@ -2,7 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use vecdb::{Bytes, Formattable}; -use crate::{LoadedAddressData, Sats}; +use crate::{FundedAddressData, Sats}; /// Data of an empty address #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] @@ -16,16 +16,16 @@ pub struct EmptyAddressData { pub transfered: Sats, } -impl From for EmptyAddressData { +impl From for EmptyAddressData { #[inline] - fn from(value: LoadedAddressData) -> Self { + fn from(value: FundedAddressData) -> Self { Self::from(&value) } } -impl From<&LoadedAddressData> for EmptyAddressData { +impl From<&FundedAddressData> for EmptyAddressData { #[inline] - fn from(value: &LoadedAddressData) -> Self { + fn from(value: &FundedAddressData) -> Self { if value.sent != value.received { dbg!(&value); panic!("Trying to convert not empty wallet to empty !"); diff --git a/crates/brk_types/src/loadedaddressdata.rs b/crates/brk_types/src/fundedaddressdata.rs similarity index 94% rename from crates/brk_types/src/loadedaddressdata.rs rename to crates/brk_types/src/fundedaddressdata.rs index 5deceefe4..04165a762 100644 --- a/crates/brk_types/src/loadedaddressdata.rs +++ b/crates/brk_types/src/fundedaddressdata.rs @@ -31,10 +31,10 @@ impl CostBasisSnapshot { } } -/// Data for a loaded (non-empty) address with current balance +/// Data for a funded (non-empty) address with current balance #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] #[repr(C)] -pub struct LoadedAddressData { +pub struct FundedAddressData { /// Total transaction count pub tx_count: u32, /// Number of transaction outputs funded to this address @@ -53,7 +53,7 @@ pub struct LoadedAddressData { pub investor_cap_raw: CentsSquaredSats, } -impl LoadedAddressData { +impl FundedAddressData { pub fn balance(&self) -> Sats { (u64::from(self.received) - u64::from(self.sent)).into() } @@ -87,7 +87,7 @@ impl LoadedAddressData { .checked_sub(self.spent_txo_count) .unwrap_or_else(|| { panic!( - "LoadedAddressData corruption: spent_txo_count ({}) > funded_txo_count ({}). \ + "FundedAddressData corruption: spent_txo_count ({}) > funded_txo_count ({}). \ Address data: {:?}", self.spent_txo_count, self.funded_txo_count, self ) @@ -138,14 +138,14 @@ impl LoadedAddressData { } } -impl From for LoadedAddressData { +impl From for FundedAddressData { #[inline] fn from(value: EmptyAddressData) -> Self { Self::from(&value) } } -impl From<&EmptyAddressData> for LoadedAddressData { +impl From<&EmptyAddressData> for FundedAddressData { #[inline] fn from(value: &EmptyAddressData) -> Self { Self { @@ -161,7 +161,7 @@ impl From<&EmptyAddressData> for LoadedAddressData { } } -impl std::fmt::Display for LoadedAddressData { +impl std::fmt::Display for FundedAddressData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -177,14 +177,14 @@ impl std::fmt::Display for LoadedAddressData { } } -impl Formattable for LoadedAddressData { +impl Formattable for FundedAddressData { #[inline(always)] fn may_need_escaping() -> bool { true } } -impl Bytes for LoadedAddressData { +impl Bytes for FundedAddressData { type Array = [u8; size_of::()]; fn to_bytes(&self) -> Self::Array { diff --git a/crates/brk_types/src/loadedaddressindex.rs b/crates/brk_types/src/fundedaddressindex.rs similarity index 64% rename from crates/brk_types/src/loadedaddressindex.rs rename to crates/brk_types/src/fundedaddressindex.rs index 57af7a729..10ac33414 100644 --- a/crates/brk_types/src/loadedaddressindex.rs +++ b/crates/brk_types/src/fundedaddressindex.rs @@ -22,61 +22,61 @@ use crate::TypeIndex; Pco, JsonSchema, )] -pub struct LoadedAddressIndex(TypeIndex); +pub struct FundedAddressIndex(TypeIndex); -impl From for LoadedAddressIndex { +impl From for FundedAddressIndex { #[inline] fn from(value: TypeIndex) -> Self { Self(value) } } -impl From for LoadedAddressIndex { +impl From for FundedAddressIndex { #[inline] fn from(value: usize) -> Self { Self(TypeIndex::from(value)) } } -impl From for usize { +impl From for usize { #[inline] - fn from(value: LoadedAddressIndex) -> Self { + fn from(value: FundedAddressIndex) -> Self { usize::from(value.0) } } -impl From for u32 { +impl From for u32 { #[inline] - fn from(value: LoadedAddressIndex) -> Self { + fn from(value: FundedAddressIndex) -> Self { u32::from(value.0) } } -impl Add for LoadedAddressIndex { +impl Add for FundedAddressIndex { type Output = Self; fn add(self, rhs: usize) -> Self::Output { Self(self.0 + rhs) } } -impl CheckedSub for LoadedAddressIndex { +impl CheckedSub for FundedAddressIndex { fn checked_sub(self, rhs: Self) -> Option { self.0.checked_sub(rhs.0).map(Self) } } -impl PrintableIndex for LoadedAddressIndex { +impl PrintableIndex for FundedAddressIndex { fn to_string() -> &'static str { - "loadedaddressindex" + "fundedaddressindex" } fn to_possible_strings() -> &'static [&'static str] { - &["loadedaddr", "loadedaddressindex"] + &["fundedaddr", "fundedaddressindex"] } } -impl std::fmt::Display for LoadedAddressIndex { +impl std::fmt::Display for FundedAddressIndex { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } -impl Formattable for LoadedAddressIndex { +impl Formattable for FundedAddressIndex { #[inline(always)] fn may_need_escaping() -> bool { false diff --git a/crates/brk_types/src/index.rs b/crates/brk_types/src/index.rs index c58ee74aa..a909971a2 100644 --- a/crates/brk_types/src/index.rs +++ b/crates/brk_types/src/index.rs @@ -9,7 +9,7 @@ use crate::PairOutputIndex; use super::{ Date, DateIndex, DecadeIndex, DifficultyEpoch, EmptyAddressIndex, EmptyOutputIndex, HalvingEpoch, - Height, LoadedAddressIndex, MonthIndex, OpReturnIndex, P2AAddressIndex, P2MSOutputIndex, + Height, FundedAddressIndex, MonthIndex, OpReturnIndex, P2AAddressIndex, P2MSOutputIndex, P2PK33AddressIndex, P2PK65AddressIndex, P2PKHAddressIndex, P2SHAddressIndex, P2TRAddressIndex, P2WPKHAddressIndex, P2WSHAddressIndex, QuarterIndex, SemesterIndex, TxInIndex, TxIndex, TxOutIndex, UnknownOutputIndex, WeekIndex, YearIndex, @@ -46,7 +46,7 @@ pub enum Index { UnknownOutputIndex, WeekIndex, YearIndex, - LoadedAddressIndex, + FundedAddressIndex, EmptyAddressIndex, PairOutputIndex, } @@ -79,7 +79,7 @@ impl Index { Self::UnknownOutputIndex, Self::WeekIndex, Self::YearIndex, - Self::LoadedAddressIndex, + Self::FundedAddressIndex, Self::EmptyAddressIndex, Self::PairOutputIndex, ] @@ -112,7 +112,7 @@ impl Index { Self::UnknownOutputIndex => UnknownOutputIndex::to_possible_strings(), Self::WeekIndex => WeekIndex::to_possible_strings(), Self::YearIndex => YearIndex::to_possible_strings(), - Self::LoadedAddressIndex => LoadedAddressIndex::to_possible_strings(), + Self::FundedAddressIndex => FundedAddressIndex::to_possible_strings(), Self::EmptyAddressIndex => EmptyAddressIndex::to_possible_strings(), Self::PairOutputIndex => PairOutputIndex::to_possible_strings(), } @@ -216,8 +216,8 @@ impl TryFrom<&str> for Index { v if (Self::UnknownOutputIndex).possible_values().contains(&v) => { Self::UnknownOutputIndex } - v if (Self::LoadedAddressIndex).possible_values().contains(&v) => { - Self::LoadedAddressIndex + v if (Self::FundedAddressIndex).possible_values().contains(&v) => { + Self::FundedAddressIndex } v if (Self::EmptyAddressIndex).possible_values().contains(&v) => { Self::EmptyAddressIndex diff --git a/crates/brk_types/src/lib.rs b/crates/brk_types/src/lib.rs index 2a02dcb13..8918c276b 100644 --- a/crates/brk_types/src/lib.rs +++ b/crates/brk_types/src/lib.rs @@ -35,11 +35,11 @@ mod blockstatus; mod blocktimestamp; mod blockweightentry; mod bytes; +mod cents_sats; mod cents_signed; +mod cents_squared_sats; mod cents_unsigned; mod cents_unsigned_compact; -mod cents_sats; -mod cents_squared_sats; mod datarange; mod datarangeformat; mod date; @@ -60,6 +60,8 @@ mod feerate; mod feeratepercentiles; mod format; mod formatresponse; +mod fundedaddressdata; +mod fundedaddressindex; mod halvingepoch; mod hashrateentry; mod hashratesummary; @@ -72,8 +74,6 @@ mod indexes; mod indexinfo; mod limit; mod limitparam; -mod loadedaddressdata; -mod loadedaddressindex; mod mempoolblock; mod mempoolentryinfo; mod mempoolinfo; @@ -211,11 +211,11 @@ pub use blockstatus::*; pub use blocktimestamp::*; pub use blockweightentry::*; pub use bytes::*; +pub use cents_sats::*; pub use cents_signed::*; +pub use cents_squared_sats::*; pub use cents_unsigned::*; pub use cents_unsigned_compact::*; -pub use cents_sats::*; -pub use cents_squared_sats::*; pub use datarange::*; pub use datarangeformat::*; pub use date::*; @@ -236,6 +236,8 @@ pub use feerate::*; pub use feeratepercentiles::*; pub use format::*; pub use formatresponse::*; +pub use fundedaddressdata::*; +pub use fundedaddressindex::*; pub use halvingepoch::*; pub use hashrateentry::*; pub use hashratesummary::*; @@ -248,8 +250,6 @@ pub use indexes::*; pub use indexinfo::*; pub use limit::*; pub use limitparam::*; -pub use loadedaddressdata::*; -pub use loadedaddressindex::*; pub use mempoolblock::*; pub use mempoolentryinfo::*; pub use mempoolinfo::*; diff --git a/crates/brk_types/src/supply_state.rs b/crates/brk_types/src/supply_state.rs index 2e50cc158..0dea478f8 100644 --- a/crates/brk_types/src/supply_state.rs +++ b/crates/brk_types/src/supply_state.rs @@ -7,7 +7,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use vecdb::{Bytes, Formattable}; -use crate::{CheckedSub, LoadedAddressData, Sats}; +use crate::{CheckedSub, FundedAddressData, Sats}; /// Current supply state tracking UTXO count and total value #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] @@ -65,9 +65,9 @@ impl SubAssign<&SupplyState> for SupplyState { } } -impl From<&LoadedAddressData> for SupplyState { +impl From<&FundedAddressData> for SupplyState { #[inline] - fn from(value: &LoadedAddressData) -> Self { + fn from(value: &FundedAddressData) -> Self { Self { utxo_count: value.utxo_count() as u64, value: value.balance(), diff --git a/website/scripts/chart/colors.js b/website/scripts/chart/colors.js index f79be0a9b..8f12450f5 100644 --- a/website/scripts/chart/colors.js +++ b/website/scripts/chart/colors.js @@ -86,10 +86,7 @@ const fuchsia = createColor(() => getColor("fuchsia")); const pink = createColor(() => getColor("pink")); const rose = createColor(() => getColor("rose")); -const baseColors = { - default: createColor(() => getLightDarkValue("--color")), - gray: createColor(() => getColor("gray")), - border: createColor(() => getLightDarkValue("--border-color")), +const spectrumColors = { red, orange, amber, @@ -110,6 +107,13 @@ const baseColors = { rose, }; +const baseColors = { + default: createColor(() => getLightDarkValue("--color")), + gray: createColor(() => getColor("gray")), + border: createColor(() => getLightDarkValue("--border-color")), + ...spectrumColors, +}; + export const colors = { ...baseColors, @@ -127,6 +131,15 @@ export const colors = { min: red, }, + /** Common time period colors */ + time: { + _24h: pink, + _1w: red, + _1m: yellow, + _1y: lime, + all: teal, + }, + /** DCA period colors by term */ dcaPeriods: { // Short term @@ -170,3 +183,12 @@ export const colors = { * @typedef {typeof colors} Colors * @typedef {keyof typeof baseColors} ColorName */ + +/** Palette for indexed series */ +const palette = Object.values(spectrumColors); + +/** + * Get a color by index (cycles through palette) + * @param {number} index + */ +export const colorAt = (index) => palette[index % palette.length]; diff --git a/website/scripts/chart/index.js b/website/scripts/chart/index.js index e3908b865..bfb9030ef 100644 --- a/website/scripts/chart/index.js +++ b/website/scripts/chart/index.js @@ -1351,7 +1351,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) { const options = blueprint.options; const indexes = Object.keys(blueprint.metric.by); - const defaultColor = unit === Unit.usd || unit === Unit.usdCumulative ? colors.green : colors.orange; + const defaultColor = unit === Unit.usd ? colors.green : colors.orange; if (indexes.includes(idx)) { switch (blueprint.type) { diff --git a/website/scripts/options/chain.js b/website/scripts/options/chain.js deleted file mode 100644 index b803514eb..000000000 --- a/website/scripts/options/chain.js +++ /dev/null @@ -1,1373 +0,0 @@ -/** Chain section builder - typed tree-based patterns */ - -import { Unit } from "../utils/units.js"; -import { priceLine } from "./constants.js"; -import { line, baseline, dots, dotted } 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({ - pattern: distribution.newAddrCount[key], - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "Growth Rate", - title: `${titlePrefix}Address Growth Rate`, - bottom: fromBaseStatsPattern({ - pattern: distribution.growthRate[key], - unit: Unit.ratio, - }), - }, - { - name: "Activity", - tree: activityTypes.map((a) => ({ - name: a.name, - title: `${titlePrefix}${a.name} Address Count`, - bottom: fromBaseStatsPattern({ - pattern: distribution.addressActivity[key][a.key], - unit: 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({ - pattern: pool.coinbase, - title: "coinbase", - color: colors.orange, - }), - ...fromValuePattern({ - pattern: pool.subsidy, - title: "subsidy", - color: colors.lime, - }), - ...fromValuePattern({ - pattern: pool.fee, - title: "fee", - color: colors.cyan, - }), - ], - }, - { - 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({ - pattern: blocks.count.blockCount, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - 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({ - pattern: blocks.interval, - unit: Unit.secs, - avgActive: false, - }), - priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }), - ], - }, - { - name: "Size", - title: "Block Size", - bottom: [ - ...fromSumStatsPattern({ - pattern: blocks.size, - unit: Unit.bytes, - cumulativeUnit: Unit.bytesCumulative, - }), - line({ - metric: blocks.totalSize, - name: "Total", - color: colors.purple, - unit: Unit.bytes, - defaultActive: false, - }), - ...fromBaseStatsPattern({ - pattern: blocks.vbytes, - unit: Unit.vb, - }), - ...fromBaseStatsPattern({ - pattern: blocks.weight, - unit: Unit.wu, - }), - ], - }, - { - name: "Fullness", - title: "Block Fullness", - bottom: fromBaseStatsPattern({ - pattern: blocks.fullness, - unit: Unit.percentage, - }), - }, - ], - }, - - // Transaction - { - name: "Transaction", - tree: [ - { - name: "Count", - title: "Transaction Count", - bottom: fromFullStatsPattern({ - pattern: transactions.count.txCount, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "Speed", - title: "Transactions Per Second", - bottom: [ - dots({ - metric: transactions.volume.txPerSec, - name: "Transactions", - unit: Unit.perSec, - }), - ], - }, - { - name: "Volume", - title: "Transaction Volume", - bottom: [ - ...satsBtcUsd({ - pattern: transactions.volume.sentSum, - name: "Sent", - }), - ...satsBtcUsd({ - pattern: transactions.volume.receivedSum, - name: "Received", - color: colors.cyan, - defaultActive: false, - }), - ...satsBtcUsd({ - pattern: transactions.volume.annualizedVolume, - name: "Annualized", - color: colors.red, - defaultActive: false, - }), - ], - }, - { - name: "Size", - title: "Transaction Size", - bottom: [ - ...fromStatsPattern({ - pattern: transactions.size.weight, - unit: Unit.wu, - }), - ...fromStatsPattern({ - pattern: transactions.size.vsize, - unit: Unit.vb, - }), - ], - }, - { - name: "Fee Rate", - title: "Fee Rate", - bottom: fromStatsPattern({ - pattern: transactions.fees.feeRate, - unit: Unit.feeRate, - }), - }, - { - name: "Versions", - title: "Transaction Versions", - bottom: [ - ...fromCountPattern({ - pattern: transactions.versions.v1, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - title: "v1", - color: colors.orange, - }), - ...fromCountPattern({ - pattern: transactions.versions.v2, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - title: "v2", - color: colors.cyan, - }), - ...fromCountPattern({ - pattern: transactions.versions.v3, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - title: "v3", - color: colors.lime, - }), - ], - }, - { - 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({ - pattern: inputs.count, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - ], - }, - { - name: "Output Count", - title: "Output Count", - bottom: [ - ...fromSumStatsPattern({ - pattern: outputs.count.totalCount, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - ], - }, - { - 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({ - pattern: scripts.count.p2pkh, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "P2PK33", - title: "P2PK33 Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2pk33, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "P2PK65", - title: "P2PK65 Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2pk65, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - ], - }, - // Script Hash - { - name: "Script Hash", - tree: [ - { - name: "P2SH", - title: "P2SH Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2sh, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "P2MS", - title: "P2MS Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2ms, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - ], - }, - // SegWit scripts - { - name: "SegWit", - tree: [ - { - name: "All SegWit", - title: "SegWit Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.segwit, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "P2WPKH", - title: "P2WPKH Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2wpkh, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "P2WSH", - title: "P2WSH Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2wsh, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - ], - }, - // Taproot scripts - { - name: "Taproot", - tree: [ - { - name: "P2TR", - title: "P2TR Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2tr, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "P2A", - title: "P2A Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.p2a, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - ], - }, - // Other scripts - { - name: "Other", - tree: [ - { - name: "OP_RETURN", - title: "OP_RETURN Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.opreturn, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "Empty", - title: "Empty Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.emptyoutput, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - { - name: "Unknown", - title: "Unknown Output Count", - bottom: fromFullStatsPattern({ - pattern: scripts.count.unknownoutput, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - }, - ], - }, - ], - }, - { - 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", - unit: Unit.percentage, - }), - line({ - metric: scripts.count.segwitAdoption.cumulative, - name: "Cumulative", - color: colors.red, - unit: Unit.percentage, - }), - ], - }, - { - 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", - unit: Unit.percentage, - }), - line({ - metric: scripts.count.taprootAdoption.cumulative, - name: "Cumulative", - color: colors.red, - unit: Unit.percentage, - }), - ], - }, - ], - }, - { - name: "OP_RETURN Value", - title: "OP_RETURN Value", - bottom: fromCoinbasePattern({ pattern: scripts.value.opreturn }), - }, - ], - }, - - // Supply - { - name: "Supply", - tree: [ - { - name: "Circulating", - title: "Circulating Supply", - bottom: fromSupplyPattern({ - pattern: supply.circulating, - title: "Supply", - }), - }, - { - name: "Inflation", - title: "Inflation Rate", - bottom: [ - dots({ - metric: supply.inflation, - name: "Rate", - unit: Unit.percentage, - }), - ], - }, - { - name: "Unspendable", - title: "Unspendable Supply", - bottom: fromValuePattern({ pattern: supply.burned.unspendable }), - }, - { - name: "OP_RETURN", - title: "OP_RETURN Supply", - bottom: fromValuePattern({ pattern: supply.burned.opreturn }), - }, - ], - }, - - // Rewards - { - name: "Rewards", - tree: [ - { - name: "Coinbase", - title: "Coinbase Rewards", - bottom: [ - ...fromCoinbasePattern({ pattern: blocks.rewards.coinbase }), - ...satsBtcUsd({ - pattern: blocks.rewards._24hCoinbaseSum, - name: "24h sum", - color: colors.pink, - defaultActive: false, - }), - ], - }, - { - name: "Subsidy", - title: "Block Subsidy", - bottom: [ - ...fromCoinbasePattern({ pattern: 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({ - pattern: transactions.fees.fee.bitcoin, - unit: Unit.btc, - cumulativeUnit: Unit.btcCumulative, - }), - ...fromSumStatsPattern({ - pattern: transactions.fees.fee.sats, - unit: Unit.sats, - cumulativeUnit: Unit.satsCumulative, - }), - ...fromSumStatsPattern({ - pattern: transactions.fees.fee.dollars, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - }), - line({ - metric: blocks.rewards.feeDominance, - name: "Dominance", - color: colors.purple, - unit: Unit.percentage, - defaultActive: false, - }), - ], - }, - { - name: "Unclaimed", - title: "Unclaimed Rewards", - bottom: fromValuePattern({ - pattern: blocks.rewards.unclaimedRewards, - }), - }, - ], - }, - - // 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, - }), - dotted({ - metric: blocks.difficulty.asHash, - name: "Difficulty", - color: colors.default, - unit: Unit.hashRate, - }), - ], - }, - - // 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, - }), - dotted({ - metric: blocks.mining.hashPriceThsMin, - name: "TH/s Min", - color: colors.red, - unit: Unit.usdPerThsPerDay, - }), - dotted({ - metric: blocks.mining.hashPricePhsMin, - name: "PH/s Min", - color: colors.red, - unit: Unit.usdPerPhsPerDay, - }), - ], - }, - { - 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, - }), - dotted({ - metric: blocks.mining.hashValueThsMin, - name: "TH/s Min", - color: colors.red, - unit: Unit.satsPerThsPerDay, - }), - dotted({ - metric: blocks.mining.hashValuePhsMin, - name: "PH/s Min", - color: colors.red, - unit: Unit.satsPerPhsPerDay, - }), - ], - }, - ], - }, - - // 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, - ], - }, - ], - }; -} diff --git a/website/scripts/options/context.js b/website/scripts/options/context.js index bfc1ea281..937c001a1 100644 --- a/website/scripts/options/context.js +++ b/website/scripts/options/context.js @@ -1,15 +1,15 @@ import { - fromSumStatsPattern, fromBaseStatsPattern, - fromFullStatsPattern, fromStatsPattern, - fromCoinbasePattern, - fromValuePattern, - fromBitcoinPatternWithUnit, - fromCountPattern, fromSupplyPattern, + chartsFromFull, + chartsFromSum, + chartsFromCount, + chartsFromValue, + chartsFromValueFull, } from "./series.js"; import { colors } from "../chart/colors.js"; +import { Unit } from "../utils/units.js"; /** * @template {(arg: any, ...args: any[]) => any} F @@ -29,6 +29,18 @@ const bind = (fn) => (...args) => fn(colors, ...args) ); +/** + * Create distribution series for btc/sats/usd from a value pattern with stats (average + percentiles) + * @param {FullValuePattern | SumValuePattern} source + * @returns {AnyFetchedSeriesBlueprint[]} + */ +const distributionBtcSatsUsd = (source) => [ + ...fromStatsPattern(colors, { pattern: source.bitcoin, unit: Unit.btc }), + ...fromStatsPattern(colors, { pattern: source.sats, unit: Unit.sats }), + ...fromStatsPattern(colors, { pattern: source.dollars, unit: Unit.usd }), +]; + + /** * Create a context object with all dependencies for building partial options * @param {Object} args @@ -38,14 +50,16 @@ export function createContext({ brk }) { return { colors, brk, - fromSumStatsPattern: bind(fromSumStatsPattern), + // Series helpers (return series arrays for a single chart) fromBaseStatsPattern: bind(fromBaseStatsPattern), - fromFullStatsPattern: bind(fromFullStatsPattern), fromStatsPattern: bind(fromStatsPattern), - fromCoinbasePattern: bind(fromCoinbasePattern), - fromValuePattern, - fromBitcoinPatternWithUnit, - fromCountPattern, fromSupplyPattern, + distributionBtcSatsUsd, + // Chart helpers (return chart trees for Sum/Distribution/Cumulative folders) + chartsFromFull: bind(chartsFromFull), + chartsFromSum: bind(chartsFromSum), + chartsFromCount, + chartsFromValue, + chartsFromValueFull: bind(chartsFromValueFull), }; } diff --git a/website/scripts/options/distribution/address.js b/website/scripts/options/distribution/address.js index 3b8752d77..cf72095bb 100644 --- a/website/scripts/options/distribution/address.js +++ b/website/scripts/options/distribution/address.js @@ -223,7 +223,6 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) { unit: Unit.usd, defaultActive: false, }), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), ] : []), // RealizedPattern (address cohorts) doesn't have realizedCapRelToOwnMarketCap @@ -437,9 +436,25 @@ function createRealizedPnlSection(ctx, args, title) { name: "Peak Regret", title: title("Peak Regret"), bottom: [ - line({ metric: realized.peakRegret.sum, name: "Sum", color: colors.red, unit: Unit.usd }), - line({ metric: realized.peakRegret.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }), - baseline({ metric: realized.peakRegretRelToRealizedCap, name: "Rel. to Realized Cap", color: colors.orange, unit: Unit.pctRcap }), + line({ + metric: realized.peakRegret.sum, + name: "Sum", + color: colors.red, + unit: Unit.usd, + }), + line({ + metric: realized.peakRegret.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.usd, + defaultActive: false, + }), + baseline({ + metric: realized.peakRegretRelToRealizedCap, + name: "Rel. to Realized Cap", + color: colors.orange, + unit: Unit.pctRcap, + }), ], }, { @@ -449,35 +464,109 @@ function createRealizedPnlSection(ctx, args, title) { name: "In Profit", title: title("Sent In Profit"), bottom: [ - line({ metric: realized.sentInProfit.bitcoin.sum, name: "Sum", color: colors.green, unit: Unit.btc }), - line({ metric: realized.sentInProfit.bitcoin.cumulative, name: "Cumulative", color: colors.green, unit: Unit.btc, defaultActive: false }), - line({ metric: realized.sentInProfit.sats.sum, name: "Sum", color: colors.green, unit: Unit.sats }), - line({ metric: realized.sentInProfit.sats.cumulative, name: "Cumulative", color: colors.green, unit: Unit.sats, defaultActive: false }), - line({ metric: realized.sentInProfit.dollars.sum, name: "Sum", color: colors.green, unit: Unit.usd }), - line({ metric: realized.sentInProfit.dollars.cumulative, name: "Cumulative", color: colors.green, unit: Unit.usd, defaultActive: false }), + line({ + metric: realized.sentInProfit.bitcoin.sum, + name: "Sum", + color: colors.green, + unit: Unit.btc, + }), + line({ + metric: realized.sentInProfit.bitcoin.cumulative, + name: "Cumulative", + color: colors.green, + unit: Unit.btc, + defaultActive: false, + }), + line({ + metric: realized.sentInProfit.sats.sum, + name: "Sum", + color: colors.green, + unit: Unit.sats, + }), + line({ + metric: realized.sentInProfit.sats.cumulative, + name: "Cumulative", + color: colors.green, + unit: Unit.sats, + defaultActive: false, + }), + line({ + metric: realized.sentInProfit.dollars.sum, + name: "Sum", + color: colors.green, + unit: Unit.usd, + }), + line({ + metric: realized.sentInProfit.dollars.cumulative, + name: "Cumulative", + color: colors.green, + unit: Unit.usd, + defaultActive: false, + }), ], }, { name: "In Loss", title: title("Sent In Loss"), bottom: [ - line({ metric: realized.sentInLoss.bitcoin.sum, name: "Sum", color: colors.red, unit: Unit.btc }), - line({ metric: realized.sentInLoss.bitcoin.cumulative, name: "Cumulative", color: colors.red, unit: Unit.btc, defaultActive: false }), - line({ metric: realized.sentInLoss.sats.sum, name: "Sum", color: colors.red, unit: Unit.sats }), - line({ metric: realized.sentInLoss.sats.cumulative, name: "Cumulative", color: colors.red, unit: Unit.sats, defaultActive: false }), - line({ metric: realized.sentInLoss.dollars.sum, name: "Sum", color: colors.red, unit: Unit.usd }), - line({ metric: realized.sentInLoss.dollars.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }), + line({ + metric: realized.sentInLoss.bitcoin.sum, + name: "Sum", + color: colors.red, + unit: Unit.btc, + }), + line({ + metric: realized.sentInLoss.bitcoin.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.btc, + defaultActive: false, + }), + line({ + metric: realized.sentInLoss.sats.sum, + name: "Sum", + color: colors.red, + unit: Unit.sats, + }), + line({ + metric: realized.sentInLoss.sats.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.sats, + defaultActive: false, + }), + line({ + metric: realized.sentInLoss.dollars.sum, + name: "Sum", + color: colors.red, + unit: Unit.usd, + }), + line({ + metric: realized.sentInLoss.dollars.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.usd, + defaultActive: false, + }), ], }, { name: "In Profit 14d EMA", title: title("Sent In Profit 14d EMA"), - bottom: satsBtcUsd({ pattern: realized.sentInProfit14dEma, name: "14d EMA", color: colors.green }), + bottom: satsBtcUsd({ + pattern: realized.sentInProfit14dEma, + name: "14d EMA", + color: colors.green, + }), }, { name: "In Loss 14d EMA", title: title("Sent In Loss 14d EMA"), - bottom: satsBtcUsd({ pattern: realized.sentInLoss14dEma, name: "14d EMA", color: colors.red }), + bottom: satsBtcUsd({ + pattern: realized.sentInLoss14dEma, + name: "14d EMA", + color: colors.red, + }), }, ], }, @@ -493,9 +582,27 @@ function createRealizedPnlSection(ctx, args, title) { */ function createGroupedRealizedPnlSection(ctx, list, title) { const pnlConfigs = /** @type {const} */ ([ - { name: "Profit", sum: "realizedProfit", ema: "realizedProfit7dEma", rel: "realizedProfitRelToRealizedCap", isNet: false }, - { name: "Loss", sum: "realizedLoss", ema: "realizedLoss7dEma", rel: "realizedLossRelToRealizedCap", isNet: false }, - { name: "Net P&L", sum: "netRealizedPnl", ema: "netRealizedPnl7dEma", rel: "netRealizedPnlRelToRealizedCap", isNet: true }, + { + name: "Profit", + sum: "realizedProfit", + ema: "realizedProfit7dEma", + rel: "realizedProfitRelToRealizedCap", + isNet: false, + }, + { + name: "Loss", + sum: "realizedLoss", + ema: "realizedLoss7dEma", + rel: "realizedLossRelToRealizedCap", + isNet: false, + }, + { + name: "Net P&L", + sum: "netRealizedPnl", + ema: "netRealizedPnl7dEma", + rel: "netRealizedPnlRelToRealizedCap", + isNet: true, + }, ]); return [ @@ -507,10 +614,19 @@ function createGroupedRealizedPnlSection(ctx, list, title) { title: title(`Realized ${name}`), bottom: [ ...list.flatMap(({ color, name, tree }) => [ - (isNet ? baseline : line)({ metric: tree.realized[sum].sum, name, color, unit: Unit.usd }), - baseline({ metric: tree.realized[rel].sum, name, color, unit: Unit.pctRcap }), + (isNet ? baseline : line)({ + metric: tree.realized[sum].sum, + name, + color, + unit: Unit.usd, + }), + baseline({ + metric: tree.realized[rel].sum, + name, + color, + unit: Unit.pctRcap, + }), ]), - priceLine({ ctx, unit: Unit.usd }), ], }, { @@ -518,9 +634,13 @@ function createGroupedRealizedPnlSection(ctx, list, title) { title: title(`Realized ${name} 7d EMA`), bottom: [ ...list.map(({ color, name, tree }) => - (isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }), + (isNet ? baseline : line)({ + metric: tree.realized[ema], + name, + color, + unit: Unit.usd, + }), ), - priceLine({ ctx, unit: Unit.usd }), ], }, ], @@ -532,21 +652,36 @@ function createGroupedRealizedPnlSection(ctx, list, title) { name: "Sum", title: title("Peak Regret"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.peakRegret.sum, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.peakRegret.sum, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "Cumulative", title: title("Peak Regret Cumulative"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.peakRegret.cumulative, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.peakRegret.cumulative, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "Rel. to Realized Cap", title: title("Peak Regret Rel. to Realized Cap"), bottom: list.flatMap(({ color, name, tree }) => [ - baseline({ metric: tree.realized.peakRegretRelToRealizedCap, name, color, unit: Unit.pctRcap }), + baseline({ + metric: tree.realized.peakRegretRelToRealizedCap, + name, + color, + unit: Unit.pctRcap, + }), ]), }, ], @@ -558,50 +693,118 @@ function createGroupedRealizedPnlSection(ctx, list, title) { name: "In Profit", title: title("Sent In Profit"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInProfit.bitcoin.sum, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInProfit.sats.sum, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInProfit.dollars.sum, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInProfit.bitcoin.sum, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInProfit.sats.sum, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInProfit.dollars.sum, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Profit Cumulative", title: title("Sent In Profit Cumulative"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInProfit.sats.cumulative, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInProfit.dollars.cumulative, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInProfit.bitcoin.cumulative, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInProfit.sats.cumulative, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInProfit.dollars.cumulative, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Loss", title: title("Sent In Loss"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInLoss.bitcoin.sum, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInLoss.sats.sum, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInLoss.dollars.sum, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInLoss.bitcoin.sum, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInLoss.sats.sum, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInLoss.dollars.sum, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Loss Cumulative", title: title("Sent In Loss Cumulative"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInLoss.sats.cumulative, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInLoss.dollars.cumulative, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInLoss.bitcoin.cumulative, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInLoss.sats.cumulative, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInLoss.dollars.cumulative, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Profit 14d EMA", title: title("Sent In Profit 14d EMA"), bottom: list.flatMap(({ color, name, tree }) => - satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name, color }), + satsBtcUsd({ + pattern: tree.realized.sentInProfit14dEma, + name, + color, + }), ), }, { name: "In Loss 14d EMA", title: title("Sent In Loss 14d EMA"), bottom: list.flatMap(({ color, name, tree }) => - satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name, color }), + satsBtcUsd({ + pattern: tree.realized.sentInLoss14dEma, + name, + color, + }), ), }, ], diff --git a/website/scripts/options/distribution/data.js b/website/scripts/options/distribution/data.js index b375fb7d9..1547f2d9a 100644 --- a/website/scripts/options/distribution/data.js +++ b/website/scripts/options/distribution/data.js @@ -12,16 +12,7 @@ import { spendableTypeColors, yearColors, } from "../colors/index.js"; - -/** - * @template {Record} T - * @param {T} obj - * @returns {[keyof T & string, T[keyof T & string]][]} - */ -const entries = (obj) => - /** @type {[keyof T & string, T[keyof T & string]][]} */ ( - Object.entries(obj) - ); +import { entries } from "../../utils/array.js"; /** @type {readonly AddressableType[]} */ const ADDRESSABLE_TYPES = [ diff --git a/website/scripts/options/distribution/utxo.js b/website/scripts/options/distribution/utxo.js index eb761e251..607fe4612 100644 --- a/website/scripts/options/distribution/utxo.js +++ b/website/scripts/options/distribution/utxo.js @@ -854,7 +854,6 @@ function createSingleRealizedPriceChartsBasic(ctx, cohort, title) { color, unit: Unit.ratio, }), - priceLine({ ctx, unit: Unit.ratio, number: 1 }), ], }, ]; @@ -891,7 +890,6 @@ function createSingleRealizedCapSeries(ctx, cohort, { extra = [] } = {}) { unit: Unit.usd, defaultActive: false, }), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), ...extra, ]; } @@ -983,56 +981,89 @@ function createSingleRealizedPnlSection( title, { extra = [] } = {}, ) { - const { colors, fromCountPattern, fromBitcoinPatternWithUnit } = ctx; + const { colors } = ctx; const { tree } = cohort; return [ { name: "P&L", + tree: [ + { + name: "Sum", + title: title("Realized P&L"), + bottom: [ + line({ + metric: tree.realized.realizedProfit.sum, + name: "Profit", + color: colors.green, + unit: Unit.usd, + }), + line({ + metric: tree.realized.realizedProfit7dEma, + name: "Profit 7d EMA", + color: colors.green, + unit: Unit.usd, + }), + line({ + metric: tree.realized.realizedLoss.sum, + name: "Loss", + color: colors.red, + unit: Unit.usd, + }), + line({ + metric: tree.realized.realizedLoss7dEma, + name: "Loss 7d EMA", + color: colors.red, + unit: Unit.usd, + }), + line({ + metric: tree.realized.negRealizedLoss.sum, + name: "Negative Loss", + color: colors.red, + unit: Unit.usd, + defaultActive: false, + }), + ...extra, + line({ + metric: tree.realized.totalRealizedPnl, + name: "Total", + color: colors.default, + unit: Unit.usd, + defaultActive: false, + }), + ], + }, + { + name: "Cumulative", + title: title("Realized P&L (Total)"), + bottom: [ + line({ + metric: tree.realized.realizedProfit.cumulative, + name: "Profit", + color: colors.green, + unit: Unit.usd, + }), + line({ + metric: tree.realized.realizedLoss.cumulative, + name: "Loss", + color: colors.red, + unit: Unit.usd, + }), + line({ + metric: tree.realized.negRealizedLoss.cumulative, + name: "Negative Loss", + color: colors.red, + unit: Unit.usd, + defaultActive: false, + }), + ], + }, + ], + }, + { + name: "P&L Relative", title: title("Realized P&L"), bottom: [ - ...fromCountPattern({ - pattern: tree.realized.realizedProfit, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - title: "Profit", - color: colors.green, - }), - line({ - metric: tree.realized.realizedProfit7dEma, - name: "Profit 7d EMA", - color: colors.green, - unit: Unit.usd, - }), - ...fromCountPattern({ - pattern: tree.realized.realizedLoss, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - title: "Loss", - color: colors.red, - }), - line({ - metric: tree.realized.realizedLoss7dEma, - name: "Loss 7d EMA", - color: colors.red, - unit: Unit.usd, - }), - ...fromBitcoinPatternWithUnit({ - pattern: tree.realized.negRealizedLoss, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - title: "Negative Loss", - color: colors.red, - defaultActive: false, - }), - ...extra, - line({ - metric: tree.realized.totalRealizedPnl, - name: "Total", - color: colors.default, - unit: Unit.usd, - defaultActive: false, - }), baseline({ metric: tree.realized.realizedProfitRelToRealizedCap.sum, name: "Profit", @@ -1059,57 +1090,67 @@ function createSingleRealizedPnlSection( unit: Unit.pctRcap, defaultActive: false, }), - priceLine({ ctx, unit: Unit.pctRcap }), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), ], }, { name: "Net P&L", - title: title("Net Realized P&L"), - bottom: [ - ...fromCountPattern({ - pattern: tree.realized.netRealizedPnl, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - title: "Net", - }), - baseline({ - metric: tree.realized.netRealizedPnl7dEma, - name: "Net 7d EMA", - unit: Unit.usd, - }), - baseline({ - metric: tree.realized.netRealizedPnlCumulative30dDelta, - name: "Cumulative 30d Change", - unit: Unit.usd, - defaultActive: false, - }), - baseline({ - metric: tree.realized.netRealizedPnlRelToRealizedCap.sum, - name: "Net", - unit: Unit.pctRcap, - }), - baseline({ - metric: tree.realized.netRealizedPnlRelToRealizedCap.cumulative, - name: "Net Cumulative", - unit: Unit.pctRcap, - defaultActive: false, - }), - baseline({ - metric: - tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap, - name: "Cumulative 30d Change", - unit: Unit.pctRcap, - defaultActive: false, - }), - baseline({ - metric: tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap, - name: "Cumulative 30d Change", - unit: Unit.pctMcap, - }), - priceLine({ ctx, unit: Unit.pctMcap }), - priceLine({ ctx, unit: Unit.pctRcap }), - priceLine({ ctx, unit: Unit.usd }), + tree: [ + { + name: "Sum", + title: title("Net Realized P&L"), + bottom: [ + baseline({ + metric: tree.realized.netRealizedPnl.sum, + name: "Net", + unit: Unit.usd, + }), + baseline({ + metric: tree.realized.netRealizedPnl7dEma, + name: "Net 7d EMA", + unit: Unit.usd, + }), + baseline({ + metric: tree.realized.netRealizedPnlRelToRealizedCap.sum, + name: "Net", + unit: Unit.pctRcap, + }), + baseline({ + metric: + tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap, + name: "30d Change", + unit: Unit.pctMcap, + }), + ], + }, + { + name: "Cumulative", + title: title("Net Realized P&L (Total)"), + bottom: [ + baseline({ + metric: tree.realized.netRealizedPnl.cumulative, + name: "Net", + unit: Unit.usd, + }), + baseline({ + metric: tree.realized.netRealizedPnlCumulative30dDelta, + name: "30d Change", + unit: Unit.usd, + defaultActive: false, + }), + baseline({ + metric: tree.realized.netRealizedPnlRelToRealizedCap.cumulative, + name: "Net", + unit: Unit.pctRcap, + }), + baseline({ + metric: + tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap, + name: "30d Change", + unit: Unit.pctRcap, + defaultActive: false, + }), + ], + }, ], }, { @@ -1119,35 +1160,109 @@ function createSingleRealizedPnlSection( name: "In Profit", title: title("Sent In Profit"), bottom: [ - line({ metric: tree.realized.sentInProfit.bitcoin.sum, name: "Sum", color: colors.green, unit: Unit.btc }), - line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name: "Cumulative", color: colors.green, unit: Unit.btc, defaultActive: false }), - line({ metric: tree.realized.sentInProfit.sats.sum, name: "Sum", color: colors.green, unit: Unit.sats }), - line({ metric: tree.realized.sentInProfit.sats.cumulative, name: "Cumulative", color: colors.green, unit: Unit.sats, defaultActive: false }), - line({ metric: tree.realized.sentInProfit.dollars.sum, name: "Sum", color: colors.green, unit: Unit.usd }), - line({ metric: tree.realized.sentInProfit.dollars.cumulative, name: "Cumulative", color: colors.green, unit: Unit.usd, defaultActive: false }), + line({ + metric: tree.realized.sentInProfit.bitcoin.sum, + name: "Sum", + color: colors.green, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInProfit.bitcoin.cumulative, + name: "Cumulative", + color: colors.green, + unit: Unit.btc, + defaultActive: false, + }), + line({ + metric: tree.realized.sentInProfit.sats.sum, + name: "Sum", + color: colors.green, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInProfit.sats.cumulative, + name: "Cumulative", + color: colors.green, + unit: Unit.sats, + defaultActive: false, + }), + line({ + metric: tree.realized.sentInProfit.dollars.sum, + name: "Sum", + color: colors.green, + unit: Unit.usd, + }), + line({ + metric: tree.realized.sentInProfit.dollars.cumulative, + name: "Cumulative", + color: colors.green, + unit: Unit.usd, + defaultActive: false, + }), ], }, { name: "In Loss", title: title("Sent In Loss"), bottom: [ - line({ metric: tree.realized.sentInLoss.bitcoin.sum, name: "Sum", color: colors.red, unit: Unit.btc }), - line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name: "Cumulative", color: colors.red, unit: Unit.btc, defaultActive: false }), - line({ metric: tree.realized.sentInLoss.sats.sum, name: "Sum", color: colors.red, unit: Unit.sats }), - line({ metric: tree.realized.sentInLoss.sats.cumulative, name: "Cumulative", color: colors.red, unit: Unit.sats, defaultActive: false }), - line({ metric: tree.realized.sentInLoss.dollars.sum, name: "Sum", color: colors.red, unit: Unit.usd }), - line({ metric: tree.realized.sentInLoss.dollars.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }), + line({ + metric: tree.realized.sentInLoss.bitcoin.sum, + name: "Sum", + color: colors.red, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInLoss.bitcoin.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.btc, + defaultActive: false, + }), + line({ + metric: tree.realized.sentInLoss.sats.sum, + name: "Sum", + color: colors.red, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInLoss.sats.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.sats, + defaultActive: false, + }), + line({ + metric: tree.realized.sentInLoss.dollars.sum, + name: "Sum", + color: colors.red, + unit: Unit.usd, + }), + line({ + metric: tree.realized.sentInLoss.dollars.cumulative, + name: "Cumulative", + color: colors.red, + unit: Unit.usd, + defaultActive: false, + }), ], }, { name: "In Profit 14d EMA", title: title("Sent In Profit 14d EMA"), - bottom: satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name: "14d EMA", color: colors.green }), + bottom: satsBtcUsd({ + pattern: tree.realized.sentInProfit14dEma, + name: "14d EMA", + color: colors.green, + }), }, { name: "In Loss 14d EMA", title: title("Sent In Loss 14d EMA"), - bottom: satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name: "14d EMA", color: colors.red }), + bottom: satsBtcUsd({ + pattern: tree.realized.sentInLoss14dEma, + name: "14d EMA", + color: colors.red, + }), }, ], }, @@ -1171,9 +1286,27 @@ function createGroupedRealizedPnlSections( { ratioMetrics } = {}, ) { const pnlConfigs = /** @type {const} */ ([ - { name: "Profit", sum: "realizedProfit", ema: "realizedProfit7dEma", rel: "realizedProfitRelToRealizedCap", isNet: false }, - { name: "Loss", sum: "realizedLoss", ema: "realizedLoss7dEma", rel: "realizedLossRelToRealizedCap", isNet: false }, - { name: "Net P&L", sum: "netRealizedPnl", ema: "netRealizedPnl7dEma", rel: "netRealizedPnlRelToRealizedCap", isNet: true }, + { + name: "Profit", + sum: "realizedProfit", + ema: "realizedProfit7dEma", + rel: "realizedProfitRelToRealizedCap", + isNet: false, + }, + { + name: "Loss", + sum: "realizedLoss", + ema: "realizedLoss7dEma", + rel: "realizedLossRelToRealizedCap", + isNet: false, + }, + { + name: "Net P&L", + sum: "netRealizedPnl", + ema: "netRealizedPnl7dEma", + rel: "netRealizedPnlRelToRealizedCap", + isNet: true, + }, ]); return [ @@ -1185,10 +1318,19 @@ function createGroupedRealizedPnlSections( title: title(`Realized ${name}`), bottom: [ ...list.flatMap(({ color, name, tree }) => [ - (isNet ? baseline : line)({ metric: tree.realized[sum].sum, name, color, unit: Unit.usd }), - baseline({ metric: tree.realized[rel].sum, name, color, unit: Unit.pctRcap }), + (isNet ? baseline : line)({ + metric: tree.realized[sum].sum, + name, + color, + unit: Unit.usd, + }), + baseline({ + metric: tree.realized[rel].sum, + name, + color, + unit: Unit.pctRcap, + }), ]), - priceLine({ ctx, unit: Unit.usd }), ], }, { @@ -1196,9 +1338,13 @@ function createGroupedRealizedPnlSections( title: title(`Realized ${name} 7d EMA`), bottom: [ ...list.map(({ color, name, tree }) => - (isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }), + (isNet ? baseline : line)({ + metric: tree.realized[ema], + name, + color, + unit: Unit.usd, + }), ), - priceLine({ ctx, unit: Unit.usd }), ], }, ], @@ -1207,7 +1353,12 @@ function createGroupedRealizedPnlSections( name: "Total P&L", title: title("Total Realized P&L"), bottom: list.flatMap((cohort) => [ - line({ metric: cohort.tree.realized.totalRealizedPnl, name: cohort.name, color: cohort.color, unit: Unit.usd }), + line({ + metric: cohort.tree.realized.totalRealizedPnl, + name: cohort.name, + color: cohort.color, + unit: Unit.usd, + }), ...(ratioMetrics ? ratioMetrics(cohort) : []), ]), }, @@ -1250,7 +1401,6 @@ function createGroupedRealizedPnlSections( unit: Unit.usd, }), ]), - priceLine({ ctx, unit: Unit.usd }), ], }, { @@ -1280,9 +1430,6 @@ function createGroupedRealizedPnlSections( unit: Unit.pctMcap, }), ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - priceLine({ ctx, unit: Unit.pctRcap }), ], }, ], @@ -1294,50 +1441,118 @@ function createGroupedRealizedPnlSections( name: "In Profit", title: title("Sent In Profit"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInProfit.bitcoin.sum, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInProfit.sats.sum, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInProfit.dollars.sum, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInProfit.bitcoin.sum, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInProfit.sats.sum, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInProfit.dollars.sum, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Profit Cumulative", title: title("Sent In Profit Cumulative"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInProfit.sats.cumulative, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInProfit.dollars.cumulative, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInProfit.bitcoin.cumulative, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInProfit.sats.cumulative, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInProfit.dollars.cumulative, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Loss", title: title("Sent In Loss"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInLoss.bitcoin.sum, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInLoss.sats.sum, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInLoss.dollars.sum, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInLoss.bitcoin.sum, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInLoss.sats.sum, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInLoss.dollars.sum, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Loss Cumulative", title: title("Sent In Loss Cumulative"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name, color, unit: Unit.btc }), - line({ metric: tree.realized.sentInLoss.sats.cumulative, name, color, unit: Unit.sats }), - line({ metric: tree.realized.sentInLoss.dollars.cumulative, name, color, unit: Unit.usd }), + line({ + metric: tree.realized.sentInLoss.bitcoin.cumulative, + name, + color, + unit: Unit.btc, + }), + line({ + metric: tree.realized.sentInLoss.sats.cumulative, + name, + color, + unit: Unit.sats, + }), + line({ + metric: tree.realized.sentInLoss.dollars.cumulative, + name, + color, + unit: Unit.usd, + }), ]), }, { name: "In Profit 14d EMA", title: title("Sent In Profit 14d EMA"), bottom: list.flatMap(({ color, name, tree }) => - satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name, color }), + satsBtcUsd({ + pattern: tree.realized.sentInProfit14dEma, + name, + color, + }), ), }, { name: "In Loss 14d EMA", title: title("Sent In Loss 14d EMA"), bottom: list.flatMap(({ color, name, tree }) => - satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name, color }), + satsBtcUsd({ + pattern: tree.realized.sentInLoss14dEma, + name, + color, + }), ), }, ], @@ -1687,7 +1902,6 @@ function createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) { name: "Net", unit: Unit.pctOwnPnl, }), - priceLine({ ctx, unit: Unit.pctOwnPnl }), ]; } @@ -1785,7 +1999,6 @@ function createNuplChart(ctx, rel, title) { name: "NUPL", unit: Unit.ratio, }), - priceLine({ ctx, unit: Unit.ratio }), ], }; } @@ -1880,10 +2093,7 @@ function createSingleInvestedCapitalRelativeChart(ctx, rel, title) { return { name: "Relative", title: title("Invested Capital In Profit & Loss %"), - bottom: [ - ...createInvestedCapitalRelMetrics(ctx, rel), - priceLine({ ctx, unit: Unit.pctRcap }), - ], + bottom: [...createInvestedCapitalRelMetrics(ctx, rel)], }; } @@ -1938,7 +2148,6 @@ function createGroupedNuplChart(ctx, list, title) { unit: Unit.ratio, }), ), - priceLine({ ctx, unit: Unit.ratio }), ], }; } @@ -2023,20 +2232,12 @@ function createUnrealizedSection({ { name: "P&L", title: title("Unrealized P&L"), - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...pnl, - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ], + bottom: [...createUnrealizedPnlBaseMetrics(ctx, tree), ...pnl], }, { name: "Net P&L", title: title("Net Unrealized P&L"), - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...netPnl, - priceLine({ ctx, unit: Unit.usd }), - ], + bottom: [createNetUnrealizedPnlBaseMetric(tree), ...netPnl], }, investedCapitalFolder, { @@ -2256,14 +2457,16 @@ function createSingleUnrealizedSectionAll(ctx, cohort, title) { pnl: [ ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), ], netPnl: [ ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap }), ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [ createNuplChart(ctx, tree.relative, title), createPeakRegretChartWithMarketCap(ctx, tree, title), @@ -2287,15 +2490,17 @@ function createSingleUnrealizedSectionFull(ctx, cohort, title) { ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), ], netPnl: [ ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap }), ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [ createNuplChart(ctx, tree.relative, title), createPeakRegretChartWithMarketCap(ctx, tree, title), @@ -2315,15 +2520,13 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) { ctx, tree, title, - pnl: [ - ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), - ], - netPnl: [ - ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + pnl: [...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative)], + netPnl: [...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative)], + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [ createNuplChart(ctx, tree.relative, title), createPeakRegretChartWithMarketCap(ctx, tree, title), @@ -2343,15 +2546,13 @@ function createSingleUnrealizedSectionWithMarketCapOnly(ctx, cohort, title) { ctx, tree, title, - pnl: [ - ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), - ], - netPnl: [ - ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + pnl: [...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative)], + netPnl: [...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative)], + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [createNuplChart(ctx, tree.relative, title)], }); } @@ -2368,15 +2569,13 @@ function createSingleUnrealizedSectionMinAge(ctx, cohort, title) { ctx, tree, title, - pnl: [ - ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), - ], - netPnl: [ - ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + pnl: [...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative)], + netPnl: [...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative)], + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [ createNuplChart(ctx, tree.relative, title), createPeakRegretChartWithMarketCap(ctx, tree, title), @@ -2603,7 +2802,11 @@ function createSingleUnrealizedSectionWithNupl({ ctx, cohort, title }) { ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [ createNuplChart(ctx, tree.relative, title), createPeakRegretChartWithMarketCap(ctx, tree, title), @@ -2669,7 +2872,11 @@ function createSingleUnrealizedSectionAgeRange(ctx, cohort, title) { ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), ], - investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title), + investedCapitalFolder: createSingleInvestedCapitalFolderFull( + ctx, + tree, + title, + ), charts: [createPeakRegretChart(ctx, tree, title)], }); } @@ -2895,7 +3102,7 @@ function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) { * @returns {PartialOptionsGroup} */ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) { - const { colors, fromCountPattern, fromBitcoinPatternWithUnit } = ctx; + const { colors } = ctx; const { tree, color } = cohort; return { @@ -2903,41 +3110,70 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) { tree: [ { name: "Sent", - title: title("Sent"), - bottom: [ - ...fromCountPattern({ - pattern: tree.activity.sent.sats, - unit: Unit.sats, - cumulativeUnit: Unit.satsCumulative, - color: color, - }), - ...fromBitcoinPatternWithUnit({ - pattern: tree.activity.sent.bitcoin, - unit: Unit.btc, - cumulativeUnit: Unit.btcCumulative, - color: color, - }), - ...fromCountPattern({ - pattern: tree.activity.sent.dollars, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - color: color, - }), - line({ - metric: tree.activity.sent14dEma.sats, - name: "14d EMA", - unit: Unit.sats, - }), - line({ - metric: tree.activity.sent14dEma.bitcoin, - name: "14d EMA", - unit: Unit.btc, - }), - line({ - metric: tree.activity.sent14dEma.dollars, - name: "14d EMA", - unit: Unit.usd, - }), + tree: [ + { + name: "Sum", + title: title("Sent"), + bottom: [ + line({ + metric: tree.activity.sent.sats.sum, + name: "sum", + color, + unit: Unit.sats, + }), + line({ + metric: tree.activity.sent.bitcoin.sum, + name: "sum", + color, + unit: Unit.btc, + }), + line({ + metric: tree.activity.sent.dollars.sum, + name: "sum", + color, + unit: Unit.usd, + }), + line({ + metric: tree.activity.sent14dEma.sats, + name: "14d EMA", + unit: Unit.sats, + }), + line({ + metric: tree.activity.sent14dEma.bitcoin, + name: "14d EMA", + unit: Unit.btc, + }), + line({ + metric: tree.activity.sent14dEma.dollars, + name: "14d EMA", + unit: Unit.usd, + }), + ], + }, + { + name: "Cumulative", + title: title("Sent (Total)"), + bottom: [ + line({ + metric: tree.activity.sent.sats.cumulative, + name: "all-time", + color, + unit: Unit.sats, + }), + line({ + metric: tree.activity.sent.bitcoin.cumulative, + name: "all-time", + color, + unit: Unit.btc, + }), + line({ + metric: tree.activity.sent.dollars.cumulative, + name: "all-time", + color, + unit: Unit.usd, + }), + ], + }, ], }, { diff --git a/website/scripts/options/market/momentum.js b/website/scripts/options/market/momentum.js index f35179e44..49bb3ce57 100644 --- a/website/scripts/options/market/momentum.js +++ b/website/scripts/options/market/momentum.js @@ -104,7 +104,6 @@ export function createMomentumSection(ctx, indicators) { name: "Histogram", unit: Unit.usd, }), - priceLine({ ctx, unit: Unit.usd }), ], }, ], diff --git a/website/scripts/options/market/performance.js b/website/scripts/options/market/performance.js index 5ce122444..9dcb8ed05 100644 --- a/website/scripts/options/market/performance.js +++ b/website/scripts/options/market/performance.js @@ -64,7 +64,6 @@ export function createReturnsSection(ctx, returns) { }), ] : []), - priceLine({ ctx, unit: Unit.percentage }), ], }; }; @@ -121,7 +120,6 @@ export function createReturnsSection(ctx, returns) { color: colors.blue, unit: Unit.percentage, }), - priceLine({ ctx, unit: Unit.percentage }), ], }, // Short-term (1d, 1w, 1m) diff --git a/website/scripts/options/market/volatility.js b/website/scripts/options/market/volatility.js index a8e7fd434..694f0d3e2 100644 --- a/website/scripts/options/market/volatility.js +++ b/website/scripts/options/market/volatility.js @@ -88,7 +88,6 @@ export function createVolatilitySection(ctx, { volatility, range }) { color: colors.lime, unit: Unit.ratio, }), - priceLine({ ctx, unit: Unit.ratio }), ], }, { @@ -113,7 +112,6 @@ export function createVolatilitySection(ctx, { volatility, range }) { color: colors.lime, unit: Unit.ratio, }), - priceLine({ ctx, unit: Unit.ratio }), ], }, ], diff --git a/website/scripts/options/mining.js b/website/scripts/options/mining.js index e1034c992..445f86a1d 100644 --- a/website/scripts/options/mining.js +++ b/website/scripts/options/mining.js @@ -1,13 +1,18 @@ /** Mining section - Network security and miner economics */ import { Unit } from "../utils/units.js"; -import { priceLine } from "./constants.js"; +import { entries, includes } from "../utils/array.js"; +import { colorAt } from "../chart/colors.js"; import { line, baseline, dots, dotted } from "./series.js"; -import { satsBtcUsd } from "./shared.js"; -import { fromCountPattern } from "./series.js"; +import { + satsBtcUsd, + satsBtcUsdFrom, + satsBtcUsdFromFull, + revenueBtcSatsUsd, +} from "./shared.js"; /** Major pools to show in Compare section (by current hashrate dominance) */ -const MAJOR_POOL_IDS = [ +const MAJOR_POOL_IDS = /** @type {const} */ ([ "foundryusa", // ~32% - largest pool "antpool", // ~18% - Bitmain-owned "viabtc", // ~14% - independent @@ -16,14 +21,14 @@ const MAJOR_POOL_IDS = [ "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 = [ +const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([ "antpool", // Bitmain-owned, template source "poolin", // shares AntPool templates "btccom", // CloverPool (formerly BTC.com) @@ -34,7 +39,7 @@ const ANTPOOL_AND_FRIENDS_IDS = [ "sigmapoolcom", // SigmaPool "rawpool", // shares AntPool templates "luxor", // shares AntPool templates -]; +]); /** * Create Mining section @@ -42,143 +47,164 @@ const ANTPOOL_AND_FRIENDS_IDS = [ * @returns {PartialOptionsGroup} */ export function createMiningSection(ctx) { - const { - colors, - brk, - fromSumStatsPattern, - fromCoinbasePattern, - fromValuePattern, - } = ctx; + const { colors, brk, distributionBtcSatsUsd } = ctx; const { blocks, transactions, pools } = brk.metrics; - // 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: `Dominance: ${poolName}`, - bottom: [ - dots({ - metric: pool._24hDominance, - name: "24h", - color: colors.pink, - unit: Unit.percentage, - defaultActive: false, + // Pre-compute pool entries with resolved names + const poolData = entries(pools.vecs).map(([id, pool]) => ({ + id, + name: brk.POOL_ID_TO_POOL_NAME[id], + pool, + })); + + // Filtered pool groups for comparisons + const majorPools = poolData.filter((p) => includes(MAJOR_POOL_IDS, p.id)); + const antpoolFriends = poolData.filter((p) => + includes(ANTPOOL_AND_FRIENDS_IDS, p.id), + ); + + // Build individual pool trees + const poolsTree = poolData.map(({ name, pool }) => ({ + name, + tree: [ + { + name: "Dominance", + title: `Dominance: ${name}`, + bottom: [ + dots({ + metric: pool._24hDominance, + name: "24h", + color: colors.time._24h, + unit: Unit.percentage, + defaultActive: false, + }), + line({ + metric: pool._1wDominance, + name: "1w", + color: colors.time._1w, + unit: Unit.percentage, + defaultActive: false, + }), + line({ + metric: pool._1mDominance, + name: "1m", + color: colors.time._1m, + unit: Unit.percentage, + }), + line({ + metric: pool._1yDominance, + name: "1y", + color: colors.time._1y, + unit: Unit.percentage, + defaultActive: false, + }), + line({ + metric: pool.dominance, + name: "All Time", + color: colors.time.all, + unit: Unit.percentage, + defaultActive: false, + }), + ], + }, + { + name: "Blocks Mined", + tree: [ + { + name: "Sum", + title: `Blocks Mined: ${name}`, + bottom: [ + line({ + metric: pool.blocksMined.sum, + name: "sum", + unit: Unit.count, + }), + line({ + metric: pool._24hBlocksMined, + name: "24h", + color: colors.time._24h, + unit: Unit.count, + defaultActive: false, + }), + line({ + metric: pool._1wBlocksMined, + name: "1w", + color: colors.time._1w, + unit: Unit.count, + defaultActive: false, + }), + line({ + metric: pool._1mBlocksMined, + name: "1m", + color: colors.time._1m, + unit: Unit.count, + defaultActive: false, + }), + line({ + metric: pool._1yBlocksMined, + name: "1y", + color: colors.time._1y, + unit: Unit.count, + defaultActive: false, + }), + ], + }, + { + name: "Cumulative", + title: `Blocks Mined: ${name} (Total)`, + bottom: [ + line({ + metric: pool.blocksMined.cumulative, + name: "all-time", + unit: Unit.count, + }), + ], + }, + ], + }, + { + name: "Rewards", + tree: [ + { + name: "Sum", + title: `Rewards: ${name}`, + bottom: revenueBtcSatsUsd(colors, { + coinbase: pool.coinbase, + subsidy: pool.subsidy, + fee: pool.fee, + key: "sum", }), - line({ - metric: pool._1wDominance, - name: "1w", - color: colors.red, - unit: Unit.percentage, - defaultActive: false, + }, + { + name: "Cumulative", + title: `Rewards: ${name} (Total)`, + bottom: revenueBtcSatsUsd(colors, { + coinbase: pool.coinbase, + subsidy: pool.subsidy, + fee: pool.fee, + key: "cumulative", }), - 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: `Blocks Mined: ${poolName}`, - bottom: [ - ...fromCountPattern({ - pattern: pool.blocksMined, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - }), - line({ - metric: pool._24hBlocksMined, - name: "24h", - color: colors.pink, - unit: Unit.count, - defaultActive: false, - }), - line({ - metric: pool._1wBlocksMined, - name: "1w", - color: colors.red, - unit: Unit.count, - defaultActive: false, - }), - line({ - metric: pool._1mBlocksMined, - name: "1m", - color: colors.orange, - unit: Unit.count, - defaultActive: false, - }), - line({ - metric: pool._1yBlocksMined, - name: "1y", - color: colors.purple, - unit: Unit.count, - defaultActive: false, - }), - ], - }, - { - name: "Rewards", - title: `Rewards: ${poolName}`, - bottom: [ - ...fromValuePattern({ - pattern: pool.coinbase, - title: "coinbase", - color: colors.orange, - }), - ...fromValuePattern({ - pattern: pool.subsidy, - title: "subsidy", - color: colors.lime, - }), - ...fromValuePattern({ - pattern: pool.fee, - title: "fee", - color: colors.cyan, - }), - ], - }, - { - name: "Since Last Block", - title: `Since Last Block: ${poolName}`, - bottom: [ - line({ - metric: pool.blocksSinceBlock, - name: "Elapsed", - unit: Unit.blocks, - }), - line({ - metric: pool.daysSinceBlock, - name: "Elapsed", - unit: Unit.days, - }), - ], - }, - ], - }; - }); + }, + ], + }, + { + name: "Since Last Block", + title: `Since Last Block: ${name}`, + bottom: [ + line({ + metric: pool.blocksSinceBlock, + name: "Elapsed", + unit: Unit.blocks, + }), + line({ + metric: pool.daysSinceBlock, + name: "Elapsed", + unit: Unit.days, + }), + ], + }, + ], + })); return { name: "Mining", @@ -196,28 +222,28 @@ export function createMiningSection(ctx) { line({ metric: blocks.mining.hashRate1wSma, name: "1w SMA", - color: colors.red, + color: colors.time._1w, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.mining.hashRate1mSma, name: "1m SMA", - color: colors.orange, + color: colors.time._1m, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.mining.hashRate2mSma, name: "2m SMA", - color: colors.yellow, + color: colors.orange, unit: Unit.hashRate, defaultActive: false, }), line({ metric: blocks.mining.hashRate1ySma, name: "1y SMA", - color: colors.lime, + color: colors.time._1y, unit: Unit.hashRate, defaultActive: false, }), @@ -265,7 +291,6 @@ export function createMiningSection(ctx) { name: "Change", unit: Unit.percentage, }), - priceLine({ ctx, number: 0, unit: Unit.percentage }), ], }, { @@ -293,72 +318,182 @@ export function createMiningSection(ctx) { { name: "Revenue", tree: [ + { + name: "Compare", + tree: [ + { + name: "Sum", + title: "Revenue Comparison", + bottom: revenueBtcSatsUsd(colors, { + coinbase: blocks.rewards.coinbase, + subsidy: blocks.rewards.subsidy, + fee: transactions.fees.fee, + key: "sum", + }), + }, + { + name: "Cumulative", + title: "Revenue Comparison (Total)", + bottom: revenueBtcSatsUsd(colors, { + coinbase: blocks.rewards.coinbase, + subsidy: blocks.rewards.subsidy, + fee: transactions.fees.fee, + key: "cumulative", + }), + }, + ], + }, { name: "Coinbase", - title: "Coinbase Rewards", - bottom: [ - ...fromCoinbasePattern({ pattern: blocks.rewards.coinbase }), - ...satsBtcUsd({ - pattern: blocks.rewards._24hCoinbaseSum, - name: "24h sum", - color: colors.pink, - defaultActive: false, - }), + tree: [ + { + name: "Sum", + title: "Coinbase Rewards", + bottom: [ + ...satsBtcUsdFromFull({ + source: blocks.rewards.coinbase, + key: "base", + name: "sum", + }), + ...satsBtcUsdFrom({ + source: blocks.rewards.coinbase, + key: "sum", + name: "sum", + }), + ...satsBtcUsd({ + pattern: blocks.rewards._24hCoinbaseSum, + name: "24h", + color: colors.time._24h, + defaultActive: false, + }), + ], + }, + { + name: "Distribution", + title: "Coinbase Rewards Distribution", + bottom: distributionBtcSatsUsd(blocks.rewards.coinbase), + }, + { + name: "Cumulative", + title: "Coinbase Rewards (Total)", + bottom: satsBtcUsdFrom({ + source: blocks.rewards.coinbase, + key: "cumulative", + name: "all-time", + }), + }, ], }, { name: "Subsidy", - title: "Block Subsidy", - bottom: [ - ...fromCoinbasePattern({ pattern: blocks.rewards.subsidy }), - line({ - metric: blocks.rewards.subsidyDominance, - name: "Dominance", - color: colors.purple, - unit: Unit.percentage, - }), - line({ - metric: blocks.rewards.subsidyUsd1ySma, - name: "1y SMA", - color: colors.lime, - unit: Unit.usd, - defaultActive: false, - }), + tree: [ + { + name: "Sum", + title: "Block Subsidy", + bottom: [ + ...satsBtcUsdFromFull({ + source: blocks.rewards.subsidy, + key: "base", + name: "sum", + }), + ...satsBtcUsdFrom({ + source: blocks.rewards.subsidy, + key: "sum", + name: "sum", + }), + line({ + metric: blocks.rewards.subsidyUsd1ySma, + name: "1y SMA", + color: colors.time._1y, + unit: Unit.usd, + defaultActive: false, + }), + ], + }, + { + name: "Distribution", + title: "Block Subsidy Distribution", + bottom: distributionBtcSatsUsd(blocks.rewards.subsidy), + }, + { + name: "Cumulative", + title: "Block Subsidy (Total)", + bottom: satsBtcUsdFrom({ + source: blocks.rewards.subsidy, + key: "cumulative", + name: "all-time", + }), + }, ], }, { name: "Fees", - title: "Transaction Fee Revenue", + tree: [ + { + name: "Sum", + title: "Transaction Fee Revenue", + bottom: satsBtcUsdFrom({ + source: transactions.fees.fee, + key: "sum", + name: "sum", + }), + }, + { + name: "Distribution", + title: "Transaction Fee Revenue Distribution", + bottom: distributionBtcSatsUsd(transactions.fees.fee), + }, + { + name: "Cumulative", + title: "Transaction Fee Revenue (Total)", + bottom: satsBtcUsdFrom({ + source: transactions.fees.fee, + key: "cumulative", + name: "all-time", + }), + }, + ], + }, + { + name: "Dominance", + title: "Revenue Dominance", bottom: [ - ...fromSumStatsPattern({ - pattern: transactions.fees.fee.bitcoin, - unit: Unit.btc, - cumulativeUnit: Unit.btcCumulative, - }), - ...fromSumStatsPattern({ - pattern: transactions.fees.fee.sats, - unit: Unit.sats, - cumulativeUnit: Unit.satsCumulative, - }), - ...fromSumStatsPattern({ - pattern: transactions.fees.fee.dollars, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, + line({ + metric: blocks.rewards.subsidyDominance, + name: "Subsidy", + color: colors.lime, + unit: Unit.percentage, }), line({ metric: blocks.rewards.feeDominance, - name: "Dominance", - color: colors.purple, + name: "Fees", + color: colors.cyan, unit: Unit.percentage, }), ], }, { name: "Unclaimed", - title: "Unclaimed Rewards", - bottom: fromValuePattern({ - pattern: blocks.rewards.unclaimedRewards, - }), + tree: [ + { + name: "Sum", + title: "Unclaimed Rewards", + bottom: satsBtcUsdFrom({ + source: blocks.rewards.unclaimedRewards, + key: "sum", + name: "sum", + }), + }, + { + name: "Cumulative", + title: "Unclaimed Rewards (Total)", + bottom: satsBtcUsdFrom({ + source: blocks.rewards.unclaimedRewards, + key: "cumulative", + name: "all-time", + }), + }, + ], }, ], }, @@ -383,12 +518,6 @@ export function createMiningSection(ctx) { color: colors.emerald, unit: Unit.usdPerPhsPerDay, }), - line({ - metric: blocks.mining.hashPriceRebound, - name: "Rebound", - color: colors.yellow, - unit: Unit.percentage, - }), dotted({ metric: blocks.mining.hashPriceThsMin, name: "TH/s Min", @@ -419,12 +548,6 @@ export function createMiningSection(ctx) { color: colors.orange, unit: Unit.satsPerPhsPerDay, }), - line({ - metric: blocks.mining.hashValueRebound, - name: "Rebound", - color: colors.yellow, - unit: Unit.percentage, - }), dotted({ metric: blocks.mining.hashValueThsMin, name: "TH/s Min", @@ -439,6 +562,24 @@ export function createMiningSection(ctx) { }), ], }, + { + name: "Recovery", + title: "Recovery", + bottom: [ + line({ + metric: blocks.mining.hashPriceRebound, + name: "Hash Price", + color: colors.emerald, + unit: Unit.percentage, + }), + line({ + metric: blocks.mining.hashValueRebound, + name: "Hash Value", + color: colors.orange, + unit: Unit.percentage, + }), + ], + }, ], }, @@ -458,7 +599,6 @@ export function createMiningSection(ctx) { line({ metric: blocks.halving.daysBeforeNextHalving, name: "Remaining", - color: colors.blue, unit: Unit.days, }), ], @@ -487,41 +627,39 @@ export function createMiningSection(ctx) { tree: [ { name: "Dominance", - title: "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, - }); + title: "Dominance: Major Pools (1m)", + bottom: majorPools.map((p, i) => + line({ + metric: p.pool._1mDominance, + name: p.name, + color: colorAt(i), + unit: Unit.percentage, }), + ), }, { name: "Blocks Mined", title: "Blocks Mined: Major Pools (1m)", - 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, - }); + bottom: majorPools.map((p, i) => + line({ + metric: p.pool._1mBlocksMined, + name: p.name, + color: colorAt(i), + unit: Unit.count, }), + ), + }, + { + name: "Total Rewards", + title: "Total Rewards: Major Pools", + bottom: majorPools.flatMap((p, i) => + satsBtcUsdFrom({ + source: p.pool.coinbase, + key: "sum", + name: p.name, + color: colorAt(i), + }), + ), }, ], }, @@ -531,51 +669,45 @@ export function createMiningSection(ctx) { tree: [ { name: "Dominance", - title: "Dominance: AntPool & Friends", - 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, - }); + title: "Dominance: AntPool & Friends (1m)", + bottom: antpoolFriends.map((p, i) => + line({ + metric: p.pool._1mDominance, + name: p.name, + color: colorAt(i), + unit: Unit.percentage, }), + ), }, { name: "Blocks Mined", title: "Blocks Mined: AntPool & Friends (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, - }); + bottom: antpoolFriends.map((p, i) => + line({ + metric: p.pool._1mBlocksMined, + name: p.name, + color: colorAt(i), + unit: Unit.count, }), + ), + }, + { + name: "Total Rewards", + title: "Total Rewards: AntPool & Friends", + bottom: antpoolFriends.flatMap((p, i) => + satsBtcUsdFrom({ + source: p.pool.coinbase, + key: "sum", + name: p.name, + color: colorAt(i), + }), + ), }, ], }, - // Individual pools + // All pools { - name: "Individual", + name: "All Pools", tree: poolsTree, }, ], diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index 26a50394b..a4b4d6afb 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -3,7 +3,7 @@ import { Unit } from "../utils/units.js"; import { priceLine } from "./constants.js"; import { line, dots } from "./series.js"; -import { satsBtcUsd } from "./shared.js"; +import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js"; import { spendableTypeColors } from "./colors/index.js"; /** @@ -15,14 +15,12 @@ export function createNetworkSection(ctx) { const { colors, brk, - fromSumStatsPattern, fromBaseStatsPattern, - fromFullStatsPattern, fromStatsPattern, - fromCoinbasePattern, - fromValuePattern, - fromCountPattern, fromSupplyPattern, + chartsFromFull, + chartsFromSum, + chartsFromValueFull, } = ctx; const { blocks, @@ -34,54 +32,214 @@ export function createNetworkSection(ctx) { 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 }, - ]; + // Address types for mapping (newest to oldest) + const addressTypes = /** @type {const} */ ([ + { + key: "p2a", + name: "P2A", + color: colors[spendableTypeColors.p2a], + defaultActive: false, + }, + { + key: "p2tr", + name: "P2TR", + color: colors[spendableTypeColors.p2tr], + defaultActive: true, + }, + { + key: "p2wsh", + name: "P2WSH", + color: colors[spendableTypeColors.p2wsh], + defaultActive: true, + }, + { + key: "p2wpkh", + name: "P2WPKH", + color: colors[spendableTypeColors.p2wpkh], + defaultActive: true, + }, + { + key: "p2sh", + name: "P2SH", + color: colors[spendableTypeColors.p2sh], + defaultActive: true, + }, + { + key: "p2pkh", + name: "P2PKH", + color: colors[spendableTypeColors.p2pkh], + defaultActive: true, + }, + { + key: "p2pk33", + name: "P2PK33", + color: colors[spendableTypeColors.p2pk33], + defaultActive: false, + }, + { + key: "p2pk65", + name: "P2PK65", + color: colors[spendableTypeColors.p2pk65], + defaultActive: false, + }, + ]); - // Script types for output count comparisons (address types + non-addressable scripts) - /** @type {ReadonlyArray<{key: AddressableType | "p2ms" | "opreturn" | "emptyoutput" | "unknownoutput", name: string, color: Color, defaultActive?: boolean}>} */ - const scriptTypes = [ - { key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] }, + // Address type groups (newest to oldest) + const legacyAddresses = /** @type {const} */ ([ { key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] }, - { key: "p2wpkh", name: "P2WPKH", color: colors[spendableTypeColors.p2wpkh] }, + { key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] }, + { + key: "p2pk33", + name: "P2PK33", + color: colors[spendableTypeColors.p2pk33], + }, + { + key: "p2pk65", + name: "P2PK65", + color: colors[spendableTypeColors.p2pk65], + }, + ]); + const segwitAddresses = /** @type {const} */ ([ { key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] }, + { + key: "p2wpkh", + name: "P2WPKH", + color: colors[spendableTypeColors.p2wpkh], + }, + ]); + const taprootAddresses = /** @type {const} */ ([ + { key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a] }, { 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 }, - { key: "p2ms", name: "P2MS", color: colors[spendableTypeColors.p2ms], defaultActive: false }, - { key: "opreturn", name: "OP_RETURN", color: colors[spendableTypeColors.opreturn], defaultActive: false }, - { key: "emptyoutput", name: "Empty", color: colors[spendableTypeColors.empty], defaultActive: false }, - { key: "unknownoutput", name: "Unknown", color: colors[spendableTypeColors.unknown], 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" }, - ]; + // Script types for output count comparisons (newest to oldest, then non-addressable) + const scriptTypes = /** @type {const} */ ([ + { + key: "p2a", + name: "P2A", + color: colors[spendableTypeColors.p2a], + defaultActive: false, + }, + { + key: "p2tr", + name: "P2TR", + color: colors[spendableTypeColors.p2tr], + defaultActive: true, + }, + { + key: "p2wsh", + name: "P2WSH", + color: colors[spendableTypeColors.p2wsh], + defaultActive: true, + }, + { + key: "p2wpkh", + name: "P2WPKH", + color: colors[spendableTypeColors.p2wpkh], + defaultActive: true, + }, + { + key: "p2sh", + name: "P2SH", + color: colors[spendableTypeColors.p2sh], + defaultActive: true, + }, + { + key: "p2pkh", + name: "P2PKH", + color: colors[spendableTypeColors.p2pkh], + defaultActive: true, + }, + { + key: "p2pk33", + name: "P2PK33", + color: colors[spendableTypeColors.p2pk33], + defaultActive: false, + }, + { + key: "p2pk65", + name: "P2PK65", + color: colors[spendableTypeColors.p2pk65], + defaultActive: false, + }, + { + key: "p2ms", + name: "P2MS", + color: colors[spendableTypeColors.p2ms], + defaultActive: false, + }, + { + key: "opreturn", + name: "OP_RETURN", + color: colors[spendableTypeColors.opreturn], + defaultActive: false, + }, + { + key: "emptyoutput", + name: "Empty", + color: colors[spendableTypeColors.empty], + defaultActive: false, + }, + { + key: "unknownoutput", + name: "Unknown", + color: colors[spendableTypeColors.unknown], + defaultActive: false, + }, + ]); + + // Transacting types (transaction participation) + const transactingTypes = /** @type {const} */ ([ + { + 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: "Sending & Receiving", + title: "Addresses Sending & Receiving", + compareTitle: "Addresses Sending & Receiving by Type", + }, + ]); + + // Balance change types + const balanceTypes = /** @type {const} */ ([ + { + key: "balanceIncreased", + name: "Increased", + title: "Addresses with Increased Balance", + compareTitle: "Addresses with Increased Balance by Type", + }, + { + key: "balanceDecreased", + name: "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" }, - ]; + const countTypes = /** @type {const} */ ([ + { key: "addrCount", name: "Funded", 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 @@ -95,7 +253,7 @@ export function createNetworkSection(ctx) { bottom: [ line({ metric: distribution.addrCount[key], - name: "Loaded", + name: "Funded", unit: Unit.count, }), line({ @@ -116,27 +274,256 @@ export function createNetworkSection(ctx) { }, { name: "New", - title: `${titlePrefix}New Address Count`, - bottom: fromFullStatsPattern({ pattern: distribution.newAddrCount[key], unit: Unit.count, cumulativeUnit: Unit.countCumulative }), + tree: chartsFromFull({ + pattern: distribution.newAddrCount[key], + title: `${titlePrefix}New Address Count`, + unit: Unit.count, + }), + }, + { + name: "Reactivated", + title: `${titlePrefix}Reactivated Address Count`, + bottom: fromBaseStatsPattern({ + pattern: distribution.addressActivity[key].reactivated, + unit: Unit.count, + }), }, { name: "Growth Rate", title: `${titlePrefix}Address Growth Rate`, - bottom: fromBaseStatsPattern({ pattern: distribution.growthRate[key], unit: Unit.ratio }), + bottom: fromBaseStatsPattern({ + pattern: distribution.growthRate[key], + unit: Unit.ratio, + }), }, { - name: "Activity", - tree: activityTypes.map((a) => ({ - name: a.name, - title: `${titlePrefix}${a.name} Address Count`, + name: "Transacting", + tree: transactingTypes.map((t) => ({ + name: t.name, + title: `${titlePrefix}${t.title}`, bottom: fromBaseStatsPattern({ - pattern: distribution.addressActivity[key][a.key], + pattern: distribution.addressActivity[key][t.key], + unit: Unit.count, + }), + })), + }, + { + name: "Balance", + tree: balanceTypes.map((b) => ({ + name: b.name, + title: `${titlePrefix}${b.title}`, + bottom: fromBaseStatsPattern({ + pattern: distribution.addressActivity[key][b.key], 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: distribution[c.key][t.key], + name: t.name, + color: t.color, + unit: Unit.count, + }), + ), + })), + }, + { + name: "New", + title: `${groupName} New Address Count`, + bottom: types.flatMap((t) => [ + line({ + metric: distribution.newAddrCount[t.key].base, + name: t.name, + color: t.color, + unit: Unit.count, + }), + line({ + metric: distribution.newAddrCount[t.key].sum, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ]), + }, + { + name: "Reactivated", + title: `${groupName} Reactivated Address Count`, + bottom: types.flatMap((t) => [ + line({ + metric: distribution.addressActivity[t.key].reactivated.base, + name: t.name, + color: t.color, + unit: Unit.count, + }), + line({ + metric: distribution.addressActivity[t.key].reactivated.average, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ]), + }, + { + name: "Growth Rate", + title: `${groupName} Address Growth Rate`, + bottom: types.flatMap((t) => [ + dots({ + metric: distribution.growthRate[t.key].base, + name: t.name, + color: t.color, + unit: Unit.ratio, + }), + dots({ + metric: distribution.growthRate[t.key].average, + name: t.name, + color: t.color, + unit: Unit.ratio, + }), + ]), + }, + { + name: "Transacting", + tree: transactingTypes.map((tr) => ({ + name: tr.name, + title: `${groupName} ${tr.compareTitle}`, + bottom: types.flatMap((t) => [ + line({ + metric: distribution.addressActivity[t.key][tr.key].base, + name: t.name, + color: t.color, + unit: Unit.count, + }), + line({ + metric: distribution.addressActivity[t.key][tr.key].average, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ]), + })), + }, + { + name: "Balance", + tree: balanceTypes.map((b) => ({ + name: b.name, + title: `${groupName} ${b.compareTitle}`, + bottom: types.flatMap((t) => [ + line({ + metric: distribution.addressActivity[t.key][b.key].base, + name: t.name, + color: t.color, + unit: Unit.count, + }), + line({ + metric: distribution.addressActivity[t.key][b.key].average, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ]), + })), + }, + ], + }); + + // Script type groups for Output Counts (newest to oldest) + const legacyScripts = /** @type {const} */ ([ + { key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] }, + { + key: "p2pk33", + name: "P2PK33", + color: colors[spendableTypeColors.p2pk33], + }, + { + key: "p2pk65", + name: "P2PK65", + color: colors[spendableTypeColors.p2pk65], + }, + ]); + const scriptHashScripts = /** @type {const} */ ([ + { key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] }, + { key: "p2ms", name: "P2MS", color: colors[spendableTypeColors.p2ms] }, + ]); + const segwitScripts = /** @type {const} */ ([ + { key: "segwit", name: "All SegWit", color: colors.cyan }, + { key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] }, + { + key: "p2wpkh", + name: "P2WPKH", + color: colors[spendableTypeColors.p2wpkh], + }, + ]); + const otherScripts = /** @type {const} */ ([ + { + key: "opreturn", + name: "OP_RETURN", + color: colors[spendableTypeColors.opreturn], + }, + { + key: "emptyoutput", + name: "Empty", + color: colors[spendableTypeColors.empty], + }, + { + key: "unknownoutput", + name: "Unknown", + color: colors[spendableTypeColors.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: scripts.count[t.key].sum, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ), + }, + { + name: "Cumulative", + title: `${groupName} Output Count (Total)`, + bottom: types.map((t) => + line({ + metric: scripts.count[t.key].cumulative, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ), + }, + ], + }); + return { name: "Network", tree: [ @@ -147,7 +534,10 @@ export function createNetworkSection(ctx) { { name: "Circulating", title: "Circulating Supply", - bottom: fromSupplyPattern({ pattern: supply.circulating, title: "Supply" }), + bottom: fromSupplyPattern({ + pattern: supply.circulating, + title: "Supply", + }), }, { name: "Inflation", @@ -162,13 +552,33 @@ export function createNetworkSection(ctx) { }, { name: "Unspendable", - title: "Unspendable Supply", - bottom: fromValuePattern({ pattern: supply.burned.unspendable }), + tree: [ + { + name: "Sum", + title: "Unspendable Supply", + bottom: satsBtcUsdFrom({ + source: supply.burned.unspendable, + key: "sum", + name: "sum", + }), + }, + { + name: "Cumulative", + title: "Unspendable Supply (Total)", + bottom: satsBtcUsdFrom({ + source: supply.burned.unspendable, + key: "cumulative", + name: "all-time", + }), + }, + ], }, { name: "OP_RETURN", - title: "OP_RETURN Burned", - bottom: fromCoinbasePattern({ pattern: scripts.value.opreturn }), + tree: chartsFromValueFull({ + pattern: scripts.value.opreturn, + title: "OP_RETURN Burned", + }), }, ], }, @@ -179,77 +589,114 @@ export function createNetworkSection(ctx) { tree: [ { name: "Count", - title: "Transaction Count", - bottom: fromFullStatsPattern({ pattern: transactions.count.txCount, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "Per Second", - title: "Transactions Per Second", - bottom: [ - dots({ - metric: transactions.volume.txPerSec, - name: "TPS", - unit: Unit.perSec, - }), - ], + tree: chartsFromFull({ + pattern: transactions.count.txCount, + title: "Transaction Count", + unit: Unit.count, + }), }, { name: "Fee Rate", title: "Fee Rate", - bottom: fromStatsPattern({ pattern: transactions.fees.feeRate, unit: Unit.feeRate }), + bottom: fromStatsPattern({ + pattern: transactions.fees.feeRate, + unit: Unit.feeRate, + }), }, { name: "Volume", - title: "Transaction Volume", - bottom: [ - ...satsBtcUsd({ pattern: transactions.volume.sentSum, name: "Sent" }), - ...satsBtcUsd({ - pattern: transactions.volume.receivedSum, - name: "Received", - color: colors.cyan, - defaultActive: false, - }), - ...satsBtcUsd({ - pattern: transactions.volume.annualizedVolume, + tree: [ + { + name: "Transferred", + title: "Transaction Volume", + bottom: [ + ...satsBtcUsd({ + pattern: transactions.volume.sentSum, + name: "Sent", + }), + ...satsBtcUsd({ + pattern: transactions.volume.receivedSum, + name: "Received", + color: colors.cyan, + defaultActive: false, + }), + ], + }, + { name: "Annualized", - color: colors.red, - defaultActive: false, - }), + title: "Annualized Transaction Volume", + bottom: satsBtcUsd({ + pattern: transactions.volume.annualizedVolume, + name: "Annualized", + }), + }, ], }, { name: "Size", title: "Transaction Size", bottom: [ - ...fromStatsPattern({ pattern: transactions.size.weight, unit: Unit.wu }), - ...fromStatsPattern({ pattern: transactions.size.vsize, unit: Unit.vb }), + ...fromStatsPattern({ + pattern: transactions.size.weight, + unit: Unit.wu, + }), + ...fromStatsPattern({ + pattern: transactions.size.vsize, + unit: Unit.vb, + }), ], }, { name: "Versions", - title: "Transaction Versions", - bottom: [ - ...fromCountPattern({ - pattern: transactions.versions.v1, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - title: "v1", - color: colors.orange, - }), - ...fromCountPattern({ - pattern: transactions.versions.v2, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - title: "v2", - color: colors.cyan, - }), - ...fromCountPattern({ - pattern: transactions.versions.v3, - unit: Unit.count, - cumulativeUnit: Unit.countCumulative, - title: "v3", - color: colors.lime, - }), + tree: [ + { + name: "Sum", + title: "Transaction Versions", + bottom: [ + line({ + metric: transactions.versions.v1.sum, + name: "v1", + color: colors.orange, + unit: Unit.count, + }), + line({ + metric: transactions.versions.v2.sum, + name: "v2", + color: colors.cyan, + unit: Unit.count, + }), + line({ + metric: transactions.versions.v3.sum, + name: "v3", + color: colors.lime, + unit: Unit.count, + }), + ], + }, + { + name: "Cumulative", + title: "Transaction Versions (Total)", + bottom: [ + line({ + metric: transactions.versions.v1.cumulative, + name: "v1", + color: colors.orange, + unit: Unit.count, + }), + line({ + metric: transactions.versions.v2.cumulative, + name: "v2", + color: colors.cyan, + unit: Unit.count, + }), + line({ + metric: transactions.versions.v3.cumulative, + name: "v3", + color: colors.lime, + unit: Unit.count, + }), + ], + }, ], }, { @@ -258,12 +705,12 @@ export function createNetworkSection(ctx) { bottom: [ line({ metric: supply.velocity.btc, - name: "Bitcoin", + name: "BTC", unit: Unit.ratio, }), line({ metric: supply.velocity.usd, - name: "Dollars", + name: "USD", color: colors.emerald, unit: Unit.ratio, }), @@ -278,74 +725,139 @@ export function createNetworkSection(ctx) { tree: [ { name: "Count", - title: "Block Count", - bottom: [ - ...fromCountPattern({ pattern: blocks.count.blockCount, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - 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, - }), + tree: [ + { + name: "Sum", + title: "Block Count", + bottom: [ + line({ + metric: blocks.count.blockCount.sum, + name: "sum", + unit: Unit.count, + }), + line({ + metric: blocks.count.blockCountTarget, + name: "Target", + color: colors.gray, + unit: Unit.count, + options: { lineStyle: 4 }, + }), + ], + }, + { + name: "Rolling", + title: "Block Count (Rolling)", + bottom: [ + line({ + metric: blocks.count._24hBlockCount, + name: "24h", + color: colors.pink, + unit: Unit.count, + }), + line({ + metric: blocks.count._1wBlockCount, + name: "1w", + color: colors.red, + unit: Unit.count, + }), + line({ + metric: blocks.count._1mBlockCount, + name: "1m", + color: colors.orange, + unit: Unit.count, + }), + line({ + metric: blocks.count._1yBlockCount, + name: "1y", + color: colors.purple, + unit: Unit.count, + }), + ], + }, + { + name: "Cumulative", + title: "Block Count (Total)", + bottom: [ + line({ + metric: blocks.count.blockCount.cumulative, + name: "all-time", + unit: Unit.count, + }), + ], + }, ], }, { name: "Interval", title: "Block Interval", bottom: [ - ...fromBaseStatsPattern({ pattern: blocks.interval, unit: Unit.secs, avgActive: false }), + ...fromBaseStatsPattern({ + pattern: blocks.interval, + unit: Unit.secs, + avgActive: false, + }), priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }), ], }, { name: "Size", - title: "Block Size", - bottom: [ - ...fromSumStatsPattern({ pattern: blocks.size, unit: Unit.bytes, cumulativeUnit: Unit.bytesCumulative }), - line({ - metric: blocks.totalSize, - name: "Total", - color: colors.purple, - unit: Unit.bytes, - defaultActive: false, - }), - ...fromBaseStatsPattern({ pattern: blocks.vbytes, unit: Unit.vb }), - ...fromBaseStatsPattern({ pattern: blocks.weight, unit: Unit.wu }), + tree: [ + { + name: "Sum", + title: "Block Size", + bottom: [ + line({ + metric: blocks.size.sum, + name: "sum", + unit: Unit.bytes, + }), + line({ + metric: blocks.totalSize, + name: "Blockchain", + color: colors.purple, + unit: Unit.bytes, + defaultActive: false, + }), + ], + }, + { + name: "Distribution", + title: "Block Size Distribution", + bottom: [ + ...fromStatsPattern({ + pattern: blocks.size, + unit: Unit.bytes, + }), + ...fromStatsPattern({ + pattern: blocks.vbytes, + unit: Unit.vb, + }), + ...fromStatsPattern({ + pattern: blocks.weight, + unit: Unit.wu, + }), + ], + }, + { + name: "Cumulative", + title: "Block Size (Total)", + bottom: [ + line({ + metric: blocks.size.cumulative, + name: "all-time", + unit: Unit.bytes, + }), + ], + }, ], }, { name: "Fullness", title: "Block Fullness", - bottom: fromBaseStatsPattern({ pattern: blocks.fullness, unit: Unit.percentage }), + bottom: fromBaseStatsPattern({ + pattern: blocks.fullness, + unit: Unit.percentage, + }), }, ], }, @@ -353,60 +865,52 @@ export function createNetworkSection(ctx) { // UTXOs { name: "UTXOs", - tree: [ - { - name: "UTXO Count", - title: "UTXO Count", - bottom: [ - line({ - metric: outputs.count.utxoCount, - name: "Count", - unit: Unit.count, - }), - ], - }, - { - name: "Inputs", - tree: [ - { - name: "Count", - title: "Input Count", - bottom: [...fromSumStatsPattern({ pattern: inputs.count, unit: Unit.count, cumulativeUnit: Unit.countCumulative })], - }, - { - name: "Rate", - title: "Inputs Per Second", - bottom: [ - dots({ - metric: transactions.volume.inputsPerSec, - name: "Inputs/sec", - unit: Unit.perSec, - }), - ], - }, - ], - }, - { - name: "Outputs", - tree: [ - { - name: "Count", - title: "Output Count", - bottom: [...fromSumStatsPattern({ pattern: outputs.count.totalCount, unit: Unit.count, cumulativeUnit: Unit.countCumulative })], - }, - { - name: "Rate", - title: "Outputs Per Second", - bottom: [ - dots({ - metric: transactions.volume.outputsPerSec, - name: "Outputs/sec", - unit: Unit.perSec, - }), - ], - }, - ], - }, + title: "UTXO Count", + bottom: [ + line({ + metric: outputs.count.utxoCount, + name: "Count", + unit: Unit.count, + }), + ], + }, + { + name: "Inputs", + tree: chartsFromSum({ + pattern: inputs.count, + title: "Input Count", + unit: Unit.count, + }), + }, + { + name: "Outputs", + tree: chartsFromSum({ + pattern: outputs.count.totalCount, + title: "Output Count", + unit: Unit.count, + }), + }, + { + name: "Throughput", + title: "Throughput", + bottom: [ + dots({ + metric: transactions.volume.txPerSec, + name: "TX/sec", + color: colors.red, + unit: Unit.perSec, + }), + dots({ + metric: transactions.volume.inputsPerSec, + name: "Inputs/sec", + unit: Unit.perSec, + }), + dots({ + metric: transactions.volume.outputsPerSec, + name: "Outputs/sec", + color: colors.cyan, + unit: Unit.perSec, + }), ], }, @@ -417,7 +921,7 @@ export function createNetworkSection(ctx) { // Overview - global metrics for all addresses { name: "Overview", tree: createAddressMetricsTree("all", "") }, - // Compare - cross-type comparisons + // Top-level Compare - all types { name: "Compare", tree: [ @@ -441,7 +945,7 @@ export function createNetworkSection(ctx) { name: "New", title: "New Address Count by Type", bottom: addressTypes.flatMap((t) => [ - dots({ + line({ metric: distribution.newAddrCount[t.key].base, name: t.name, color: t.color, @@ -449,11 +953,33 @@ export function createNetworkSection(ctx) { defaultActive: t.defaultActive, }), line({ - metric: distribution.newAddrCount[t.key].average, - name: `${t.name} Avg`, + metric: distribution.newAddrCount[t.key].sum, + name: t.name, color: t.color, unit: Unit.count, - defaultActive: false, + defaultActive: t.defaultActive, + }), + ]), + }, + { + name: "Reactivated", + title: "Reactivated Address Count by Type", + bottom: addressTypes.flatMap((t) => [ + line({ + metric: + distribution.addressActivity[t.key].reactivated.base, + name: t.name, + color: t.color, + unit: Unit.count, + defaultActive: t.defaultActive, + }), + line({ + metric: + distribution.addressActivity[t.key].reactivated.average, + name: t.name, + color: t.color, + unit: Unit.count, + defaultActive: t.defaultActive, }), ]), }, @@ -468,34 +994,59 @@ export function createNetworkSection(ctx) { unit: Unit.ratio, defaultActive: t.defaultActive, }), - line({ + dots({ metric: distribution.growthRate[t.key].average, - name: `${t.name} Avg`, + name: t.name, color: t.color, unit: Unit.ratio, - defaultActive: false, + defaultActive: t.defaultActive, }), ]), }, { - name: "Activity", - tree: activityTypes.map((a) => ({ - name: a.name, - title: a.compareTitle, + name: "Transacting", + tree: transactingTypes.map((tr) => ({ + name: tr.name, + title: tr.compareTitle, bottom: addressTypes.flatMap((t) => [ - dots({ - metric: distribution.addressActivity[t.key][a.key].base, + line({ + metric: distribution.addressActivity[t.key][tr.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} Avg`, + metric: + distribution.addressActivity[t.key][tr.key].average, + name: t.name, color: t.color, unit: Unit.count, - defaultActive: false, + defaultActive: t.defaultActive, + }), + ]), + })), + }, + { + name: "Balance", + tree: balanceTypes.map((b) => ({ + name: b.name, + title: b.compareTitle, + bottom: addressTypes.flatMap((t) => [ + line({ + metric: distribution.addressActivity[t.key][b.key].base, + name: t.name, + color: t.color, + unit: Unit.count, + defaultActive: t.defaultActive, + }), + line({ + metric: + distribution.addressActivity[t.key][b.key].average, + name: t.name, + color: t.color, + unit: Unit.count, + defaultActive: t.defaultActive, }), ]), })), @@ -503,11 +1054,41 @@ export function createNetworkSection(ctx) { ], }, - // Individual address types - ...addressTypes.map((t) => ({ - name: t.name, - tree: createAddressMetricsTree(t.key, `${t.name} `), - })), + // 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} `), + })), + ], + }, ], }, @@ -521,110 +1102,103 @@ export function createNetworkSection(ctx) { // Compare section { name: "Compare", - title: "Output Count by Script Type", - bottom: scriptTypes.map((t) => - line({ - metric: scripts.count[t.key].cumulative, - name: t.name, - color: t.color, - unit: Unit.countCumulative, - defaultActive: t.defaultActive, - }), - ), + tree: [ + { + name: "Sum", + title: "Output Count by Script Type", + bottom: scriptTypes.map((t) => + line({ + metric: scripts.count[t.key].sum, + 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, + }), + ), + }, + ], }, - // Legacy scripts { name: "Legacy", tree: [ - { - name: "P2PKH", - title: "P2PKH Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2pkh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "P2PK33", - title: "P2PK33 Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk33, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "P2PK65", - title: "P2PK65 Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk65, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, + createScriptCompare("Legacy", legacyScripts), + ...legacyScripts.map((t) => ({ + name: t.name, + tree: chartsFromFull({ + pattern: scripts.count[t.key], + title: `${t.name} Output Count`, + unit: Unit.count, + }), + })), ], }, - // Script Hash { name: "Script Hash", tree: [ - { - name: "P2SH", - title: "P2SH Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2sh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "P2MS", - title: "P2MS Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2ms, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, + createScriptCompare("Script Hash", scriptHashScripts), + ...scriptHashScripts.map((t) => ({ + name: t.name, + tree: chartsFromFull({ + pattern: scripts.count[t.key], + title: `${t.name} Output Count`, + unit: Unit.count, + }), + })), ], }, - // SegWit scripts { name: "SegWit", tree: [ - { - name: "All SegWit", - title: "SegWit Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.segwit, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "P2WPKH", - title: "P2WPKH Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2wpkh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "P2WSH", - title: "P2WSH Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2wsh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, + createScriptCompare("SegWit", segwitScripts), + ...segwitScripts.map((t) => ({ + name: t.name, + tree: chartsFromFull({ + pattern: scripts.count[t.key], + title: `${t.name} Output Count`, + unit: Unit.count, + }), + })), ], }, - // Taproot scripts { name: "Taproot", tree: [ - { - name: "P2TR", - title: "P2TR Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2tr, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "P2A", - title: "P2A Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.p2a, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, + createScriptCompare("Taproot", taprootAddresses), + ...taprootAddresses.map((t) => ({ + name: t.name, + tree: chartsFromFull({ + pattern: scripts.count[t.key], + title: `${t.name} Output Count`, + unit: Unit.count, + }), + })), ], }, - // Other scripts { name: "Other", tree: [ - { - name: "OP_RETURN", - title: "OP_RETURN Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.opreturn, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "Empty", - title: "Empty Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.emptyoutput, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, - { - name: "Unknown", - title: "Unknown Output Count", - bottom: fromFullStatsPattern({ pattern: scripts.count.unknownoutput, unit: Unit.count, cumulativeUnit: Unit.countCumulative }), - }, + createScriptCompare("Other", otherScripts), + ...otherScripts.map((t) => ({ + name: t.name, + tree: chartsFromFull({ + pattern: scripts.count[t.key], + title: `${t.name} Output Count`, + unit: Unit.count, + }), + })), ], }, ], @@ -632,6 +1206,24 @@ export function createNetworkSection(ctx) { { name: "Adoption", tree: [ + { + name: "Compare", + title: "Script Adoption", + bottom: [ + line({ + metric: scripts.count.segwitAdoption.cumulative, + name: "SegWit", + color: colors.cyan, + unit: Unit.percentage, + }), + line({ + metric: scripts.count.taprootAdoption.cumulative, + name: "Taproot", + color: colors.orange, + unit: Unit.percentage, + }), + ], + }, { name: "SegWit", title: "SegWit Adoption", @@ -648,7 +1240,7 @@ export function createNetworkSection(ctx) { }), line({ metric: scripts.count.segwitAdoption.cumulative, - name: "Cumulative", + name: "All-Time", color: colors.red, unit: Unit.percentage, }), @@ -670,7 +1262,7 @@ export function createNetworkSection(ctx) { }), line({ metric: scripts.count.taprootAdoption.cumulative, - name: "Cumulative", + name: "All-Time", color: colors.red, unit: Unit.percentage, }), @@ -680,7 +1272,6 @@ export function createNetworkSection(ctx) { }, ], }, - ], }; } diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 546466146..6590a078c 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -50,62 +50,18 @@ export function price({ * @param {StatsPattern | BaseStatsPattern | FullStatsPattern | AnyStatsPattern} pattern * @param {Unit} unit * @param {string} title - * @param {{ type?: "Dots" }} [options] * @returns {AnyFetchedSeriesBlueprint[]} */ -function percentileSeries(colors, pattern, unit, title, { type } = {}) { +function percentileSeries(colors, pattern, unit, title) { const { stat } = colors; - const base = { unit, defaultActive: false }; return [ - { - type, - metric: pattern.max, - title: `${title} max`.trim(), - color: stat.max, - ...base, - }, - { - type, - metric: pattern.min, - title: `${title} min`.trim(), - color: stat.min, - ...base, - }, - { - type, - metric: pattern.median, - title: `${title} median`.trim(), - color: stat.median, - ...base, - }, - { - type, - metric: pattern.pct75, - title: `${title} pct75`.trim(), - color: stat.pct75, - ...base, - }, - { - type, - metric: pattern.pct25, - title: `${title} pct25`.trim(), - color: stat.pct25, - ...base, - }, - { - type, - metric: pattern.pct90, - title: `${title} pct90`.trim(), - color: stat.pct90, - ...base, - }, - { - type, - metric: pattern.pct10, - title: `${title} pct10`.trim(), - color: stat.pct10, - ...base, - }, + dots({ metric: pattern.max, name: `${title} max`.trim(), color: stat.max, unit, defaultActive: false }), + dots({ metric: pattern.min, name: `${title} min`.trim(), color: stat.min, unit, defaultActive: false }), + dots({ metric: pattern.median, name: `${title} median`.trim(), color: stat.median, unit, defaultActive: false }), + dots({ metric: pattern.pct75, name: `${title} pct75`.trim(), color: stat.pct75, unit, defaultActive: false }), + dots({ metric: pattern.pct25, name: `${title} pct25`.trim(), color: stat.pct25, unit, defaultActive: false }), + dots({ metric: pattern.pct90, name: `${title} pct90`.trim(), color: stat.pct90, unit, defaultActive: false }), + dots({ metric: pattern.pct10, name: `${title} pct10`.trim(), color: stat.pct10, unit, defaultActive: false }), ]; } @@ -305,36 +261,6 @@ export function histogram({ }; } -/** - * Create series from patterns with sum + cumulative + percentiles (NO base) - * @param {Colors} colors - * @param {Object} args - * @param {AnyStatsPattern} args.pattern - * @param {Unit} args.unit - * @param {Unit} args.cumulativeUnit - * @param {string} [args.title] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromSumStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) { - const { stat } = colors; - return [ - { metric: pattern.average, title: `${title} avg`.trim(), unit }, - { - metric: pattern.sum, - title: title || "sum", - color: stat.sum, - unit, - defaultActive: false, - }, - { - metric: pattern.cumulative, - title: title || "cumulative", - unit: cumulativeUnit, - }, - ...percentileSeries(colors, pattern, unit, title), - ]; -} - /** * Create series from a BaseStatsPattern (base + avg + percentiles, NO sum) * @param {Colors} colors @@ -352,59 +278,23 @@ export function fromBaseStatsPattern( ) { const { stat } = colors; return [ - { metric: pattern.base, title: title || "base", color: baseColor, unit }, - { + dots({ metric: pattern.base, name: title || "base", color: baseColor, unit }), + dots({ metric: pattern.average, - title: `${title} avg`.trim(), + name: `${title} avg`.trim(), color: stat.avg, unit, defaultActive: avgActive, - }, + }), ...percentileSeries(colors, pattern, unit, title), ]; } /** - * Create series from a FullStatsPattern (base + sum + cumulative + avg + percentiles) + * Create series from any pattern with avg + percentiles (works with StatsPattern, SumStatsPattern, etc.) * @param {Colors} colors * @param {Object} args - * @param {FullStatsPattern} args.pattern - * @param {Unit} args.unit - * @param {Unit} args.cumulativeUnit - * @param {string} [args.title] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromFullStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) { - const { stat } = colors; - return [ - { metric: pattern.base, title: title || "base", unit }, - { - metric: pattern.sum, - title: title || "sum", - color: stat.sum, - unit, - }, - { - metric: pattern.cumulative, - title: title || "cumulative", - unit: cumulativeUnit, - }, - { - metric: pattern.average, - title: `${title} avg`.trim(), - color: stat.avg, - unit, - defaultActive: false, - }, - ...percentileSeries(colors, pattern, unit, title), - ]; -} - -/** - * Create series from a StatsPattern (avg + percentiles, NO base) - * @param {Colors} colors - * @param {Object} args - * @param {StatsPattern} args.pattern + * @param {StatsPattern | BaseStatsPattern | FullStatsPattern | AnyStatsPattern} args.pattern * @param {Unit} args.unit * @param {string} [args.title] * @returns {AnyFetchedSeriesBlueprint[]} @@ -417,178 +307,7 @@ export function fromStatsPattern(colors, { pattern, unit, title = "" }) { title: `${title} avg`.trim(), unit, }, - ...percentileSeries(colors, pattern, unit, title, { type: "Dots" }), - ]; -} - -/** - * Create series from AnyFullStatsPattern (base + sum + cumulative + avg + percentiles) - * @param {Colors} colors - * @param {Object} args - * @param {AnyFullStatsPattern} args.pattern - * @param {Unit} args.unit - * @param {Unit} args.cumulativeUnit - * @param {string} [args.title] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromAnyFullStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) { - const { stat } = colors; - return [ - ...fromBaseStatsPattern(colors, { pattern, unit, title }), - { - metric: pattern.sum, - title: title || "sum", - color: stat.sum, - unit, - }, - { - metric: pattern.cumulative, - title: title || "cumulative", - unit: cumulativeUnit, - }, - ]; -} - -/** - * Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each with stats + sum + cumulative) - * @param {Colors} colors - * @param {Object} args - * @param {CoinbasePattern} args.pattern - * @param {string} [args.title] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromCoinbasePattern(colors, { pattern, title = "" }) { - return [ - ...fromAnyFullStatsPattern(colors, { - pattern: pattern.bitcoin, - unit: Unit.btc, - cumulativeUnit: Unit.btcCumulative, - title, - }), - ...fromAnyFullStatsPattern(colors, { - pattern: pattern.sats, - unit: Unit.sats, - cumulativeUnit: Unit.satsCumulative, - title, - }), - ...fromAnyFullStatsPattern(colors, { - pattern: pattern.dollars, - unit: Unit.usd, - cumulativeUnit: Unit.usdCumulative, - title, - }), - ]; -} - -/** - * Create series from a ValuePattern ({ sats, bitcoin, dollars } each as CountPattern with sum + cumulative) - * @param {Object} args - * @param {ValuePattern} args.pattern - * @param {string} [args.title] - * @param {Color} [args.color] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromValuePattern({ pattern, title = "", color }) { - return [ - { - metric: pattern.bitcoin.sum, - title: title || "sum", - color, - unit: Unit.btc, - }, - { - metric: pattern.bitcoin.cumulative, - title: title || "cumulative", - color, - unit: Unit.btcCumulative, - }, - { - metric: pattern.sats.sum, - title: title || "sum", - color, - unit: Unit.sats, - }, - { - metric: pattern.sats.cumulative, - title: title || "cumulative", - color, - unit: Unit.satsCumulative, - }, - { - metric: pattern.dollars.sum, - title: title || "sum", - color, - unit: Unit.usd, - }, - { - metric: pattern.dollars.cumulative, - title: title || "cumulative", - color, - unit: Unit.usdCumulative, - }, - ]; -} - -/** - * Create sum/cumulative series from a BitcoinPattern ({ sum, cumulative }) with explicit unit and colors - * @param {Object} args - * @param {{ sum: AnyMetricPattern, cumulative: AnyMetricPattern }} args.pattern - * @param {Unit} args.unit - * @param {Unit} args.cumulativeUnit - * @param {string} [args.title] - * @param {Color} [args.color] - * @param {boolean} [args.defaultActive] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromBitcoinPatternWithUnit({ - pattern, - unit, - cumulativeUnit, - title = "", - color, - defaultActive, -}) { - return [ - { - metric: pattern.sum, - title: title || "sum", - color, - unit, - defaultActive, - }, - { - metric: pattern.cumulative, - title: title || "cumulative", - color, - unit: cumulativeUnit, - }, - ]; -} - -/** - * Create sum/cumulative series from a CountPattern with explicit unit and colors - * @param {Object} args - * @param {CountPattern} args.pattern - * @param {Unit} args.unit - * @param {Unit} args.cumulativeUnit - * @param {string} [args.title] - * @param {Color} [args.color] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromCountPattern({ pattern, unit, cumulativeUnit, title = "", color }) { - return [ - { - metric: pattern.sum, - title: title || "sum", - color, - unit, - }, - { - metric: pattern.cumulative, - title: title || "cumulative", - color, - unit: cumulativeUnit, - }, + ...percentileSeries(colors, pattern, unit, title), ]; } @@ -622,3 +341,207 @@ export function fromSupplyPattern({ pattern, title, color }) { }, ]; } + +// ============================================================================ +// Chart-generating helpers (return PartialOptionsTree for folder structures) +// ============================================================================ +// These split patterns into separate Sum/Distribution/Cumulative charts + +/** + * Create distribution series (avg + percentiles) + * @param {Colors} colors + * @param {StatsPattern | BaseStatsPattern | FullStatsPattern | AnyStatsPattern} pattern + * @param {Unit} unit + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function distributionSeries(colors, pattern, unit) { + const { stat } = colors; + return [ + dots({ metric: pattern.average, name: "avg", color: stat.avg, unit }), + dots({ metric: pattern.median, name: "median", color: stat.median, unit, defaultActive: false }), + dots({ metric: pattern.max, name: "max", color: stat.max, unit, defaultActive: false }), + dots({ metric: pattern.min, name: "min", color: stat.min, unit, defaultActive: false }), + dots({ metric: pattern.pct75, name: "pct75", color: stat.pct75, unit, defaultActive: false }), + dots({ metric: pattern.pct25, name: "pct25", color: stat.pct25, unit, defaultActive: false }), + dots({ metric: pattern.pct90, name: "pct90", color: stat.pct90, unit, defaultActive: false }), + dots({ metric: pattern.pct10, name: "pct10", color: stat.pct10, unit, defaultActive: false }), + ]; +} + +/** + * Create btc/sats/usd series from metrics + * @param {Object} args + * @param {{ bitcoin: AnyMetricPattern, sats: AnyMetricPattern, dollars: AnyMetricPattern }} args.metrics + * @param {string} args.name + * @param {Color} [args.color] + * @param {boolean} [args.defaultActive] + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function btcSatsUsdSeries({ metrics, name, color, defaultActive }) { + return [ + { metric: metrics.bitcoin, title: name, color, unit: Unit.btc, defaultActive }, + { metric: metrics.sats, title: name, color, unit: Unit.sats, defaultActive }, + { metric: metrics.dollars, title: name, color, unit: Unit.usd, defaultActive }, + ]; +} + +/** + * Split pattern with base + sum + distribution + cumulative into 3 charts + * @param {Colors} colors + * @param {Object} args + * @param {FullStatsPattern} args.pattern + * @param {string} args.title + * @param {Unit} args.unit + * @returns {PartialOptionsTree} + */ +export function chartsFromFull(colors, { pattern, title, unit }) { + return [ + { + name: "Sum", + title, + bottom: [ + { metric: pattern.base, title: "sum", unit }, + { metric: pattern.sum, title: "sum", unit }, + ], + }, + { + name: "Distribution", + title: `${title} Distribution`, + bottom: distributionSeries(colors, pattern, unit), + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: [{ metric: pattern.cumulative, title: "all-time", unit }], + }, + ]; +} + +/** + * Split pattern with sum + distribution + cumulative into 3 charts (no base) + * @param {Colors} colors + * @param {Object} args + * @param {AnyStatsPattern} args.pattern + * @param {string} args.title + * @param {Unit} args.unit + * @returns {PartialOptionsTree} + */ +export function chartsFromSum(colors, { pattern, title, unit }) { + const { stat } = colors; + return [ + { + name: "Sum", + title, + bottom: [{ metric: pattern.sum, title: "sum", color: stat.sum, unit }], + }, + { + name: "Distribution", + title: `${title} Distribution`, + bottom: distributionSeries(colors, pattern, unit), + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: [{ metric: pattern.cumulative, title: "all-time", unit }], + }, + ]; +} + +/** + * Split pattern with sum + cumulative into 2 charts + * @param {Object} args + * @param {CountPattern} args.pattern + * @param {string} args.title + * @param {Unit} args.unit + * @param {Color} [args.color] + * @returns {PartialOptionsTree} + */ +export function chartsFromCount({ pattern, title, unit, color }) { + return [ + { + name: "Sum", + title, + bottom: [{ metric: pattern.sum, title: "sum", color, unit }], + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: [{ metric: pattern.cumulative, title: "all-time", color, unit }], + }, + ]; +} + +/** + * Split value pattern (btc/sats/usd with sum + cumulative) into 2 charts + * @param {Object} args + * @param {ValuePattern} args.pattern + * @param {string} args.title + * @param {Color} [args.color] + * @returns {PartialOptionsTree} + */ +export function chartsFromValue({ pattern, title, color }) { + return [ + { + name: "Sum", + title, + bottom: btcSatsUsdSeries({ + metrics: { bitcoin: pattern.bitcoin.sum, sats: pattern.sats.sum, dollars: pattern.dollars.sum }, + name: "sum", + color, + }), + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: btcSatsUsdSeries({ + metrics: { bitcoin: pattern.bitcoin.cumulative, sats: pattern.sats.cumulative, dollars: pattern.dollars.cumulative }, + name: "all-time", + color, + }), + }, + ]; +} + +/** + * Split btc/sats/usd pattern with full stats into 3 charts + * @param {Colors} colors + * @param {Object} args + * @param {CoinbasePattern} args.pattern + * @param {string} args.title + * @returns {PartialOptionsTree} + */ +export function chartsFromValueFull(colors, { pattern, title }) { + return [ + { + name: "Sum", + title, + bottom: [ + ...btcSatsUsdSeries({ + metrics: { bitcoin: pattern.bitcoin.base, sats: pattern.sats.base, dollars: pattern.dollars.base }, + name: "sum", + }), + ...btcSatsUsdSeries({ + metrics: { bitcoin: pattern.bitcoin.sum, sats: pattern.sats.sum, dollars: pattern.dollars.sum }, + name: "sum", + }), + ], + }, + { + name: "Distribution", + title: `${title} Distribution`, + bottom: [ + ...distributionSeries(colors, pattern.bitcoin, Unit.btc), + ...distributionSeries(colors, pattern.sats, Unit.sats), + ...distributionSeries(colors, pattern.dollars, Unit.usd), + ], + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: btcSatsUsdSeries({ + metrics: { bitcoin: pattern.bitcoin.cumulative, sats: pattern.sats.cumulative, dollars: pattern.dollars.cumulative }, + name: "all-time", + }), + }, + ]; +} diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index db5cc0ba4..c16ac2540 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -23,21 +23,65 @@ export const formatCohortTitle = (cohortTitle) => */ export function satsBtcUsd({ pattern, name, color, defaultActive }) { return [ - line({ - metric: pattern.bitcoin, - name, - color, - unit: Unit.btc, - defaultActive, - }), + line({ metric: pattern.bitcoin, name, color, unit: Unit.btc, defaultActive }), line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }), - line({ - metric: pattern.dollars, - name, - color, - unit: Unit.usd, - defaultActive, - }), + line({ metric: pattern.dollars, name, color, unit: Unit.usd, defaultActive }), + ]; +} + +/** + * Create sats/btc/usd series from any value pattern using sum or cumulative key + * @param {Object} args + * @param {AnyValuePatternType} args.source + * @param {'sum' | 'cumulative'} args.key + * @param {string} args.name + * @param {Color} [args.color] + * @param {boolean} [args.defaultActive] + * @returns {FetchedLineSeriesBlueprint[]} + */ +export function satsBtcUsdFrom({ source, key, name, color, defaultActive }) { + return satsBtcUsd({ + pattern: { bitcoin: source.bitcoin[key], sats: source.sats[key], dollars: source.dollars[key] }, + name, + color, + defaultActive, + }); +} + +/** + * Create sats/btc/usd series from a full value pattern using base or average key + * @param {Object} args + * @param {FullValuePattern} args.source + * @param {'base' | 'average'} args.key + * @param {string} args.name + * @param {Color} [args.color] + * @param {boolean} [args.defaultActive] + * @returns {FetchedLineSeriesBlueprint[]} + */ +export function satsBtcUsdFromFull({ source, key, name, color, defaultActive }) { + return satsBtcUsd({ + pattern: { bitcoin: source.bitcoin[key], sats: source.sats[key], dollars: source.dollars[key] }, + name, + color, + defaultActive, + }); +} + +/** + * Create coinbase/subsidy/fee series from separate sources + * @param {Colors} colors + * @param {Object} args + * @param {AnyValuePatternType} args.coinbase + * @param {AnyValuePatternType} args.subsidy + * @param {AnyValuePatternType} args.fee + * @param {'sum' | 'cumulative'} args.key + * @returns {FetchedLineSeriesBlueprint[]} + */ +export function revenueBtcSatsUsd(colors, { coinbase, subsidy, fee, key }) { + return [ + ...satsBtcUsdFrom({ source: coinbase, key, name: "Coinbase", color: colors.orange }), + ...satsBtcUsdFrom({ source: subsidy, key, name: "Subsidy", color: colors.lime }), + ...satsBtcUsdFrom({ source: fee, key, name: "Fees", color: colors.cyan }), ]; } diff --git a/website/scripts/types.js b/website/scripts/types.js index 0cb303d07..c277c3f13 100644 --- a/website/scripts/types.js +++ b/website/scripts/types.js @@ -61,8 +61,14 @@ * @typedef {Brk.PriceRatioPattern} ActivePriceRatioPattern * AnyRatioPattern: full ratio patterns (with or without price) - has ratio, percentiles, z-scores * @typedef {Brk.RatioPattern | Brk.PriceRatioPattern} AnyRatioPattern - * ValuePattern: patterns with {bitcoin.sum, bitcoin.cumulative, sats.sum, sats.cumulative, dollars.sum, dollars.cumulative} - * @typedef {Brk.BitcoinDollarsSatsPattern6 | Brk.BitcoinDollarsSatsPattern3 | Brk.BitcoinDollarsSatsPattern2} ValuePattern + * ValuePattern: patterns with minimal stats (sum, cumulative only) for bitcoin/sats/dollars + * @typedef {Brk.BitcoinDollarsSatsPattern6 | Brk.BitcoinDollarsSatsPattern3} ValuePattern + * FullValuePattern: patterns with full stats (base, sum, cumulative, average, percentiles) for bitcoin/sats/dollars + * @typedef {Brk.BitcoinDollarsSatsPattern2} FullValuePattern + * SumValuePattern: patterns with sum stats (sum, cumulative, average, percentiles - no base) for bitcoin/sats/dollars + * @typedef {{bitcoin: SumStatsPattern, sats: SumStatsPattern, dollars: SumStatsPattern}} SumValuePattern + * AnyValuePatternType: union of all value pattern types + * @typedef {ValuePattern | FullValuePattern} AnyValuePatternType * @typedef {Brk.AnyMetricPattern} AnyMetricPattern * @typedef {Brk.DollarsSatsPattern} ActivePricePattern * @typedef {Brk.AnyMetricEndpointBuilder} AnyMetricEndpoint diff --git a/website/scripts/utils/array.js b/website/scripts/utils/array.js index e867443a1..a7502e968 100644 --- a/website/scripts/utils/array.js +++ b/website/scripts/utils/array.js @@ -18,3 +18,20 @@ export function range(start, end) { export function randomFromArray(array) { return array[Math.floor(Math.random() * array.length)]; } + +/** + * Typed Object.entries that preserves key types + * @template {Record} T + * @param {T} obj + * @returns {[keyof T & string, T[keyof T & string]][]} + */ +export const entries = (obj) => /** @type {[keyof T & string, T[keyof T & string]][]} */ (Object.entries(obj)); + +/** + * Type-safe includes that narrows the value type + * @template T + * @param {readonly T[]} arr + * @param {unknown} value + * @returns {value is T} + */ +export const includes = (arr, value) => arr.includes(/** @type {T} */ (value)); diff --git a/website/scripts/utils/units.js b/website/scripts/utils/units.js index 87833a048..f8ecced85 100644 --- a/website/scripts/utils/units.js +++ b/website/scripts/utils/units.js @@ -9,11 +9,6 @@ export const Unit = /** @type {const} */ ({ btc: { id: "btc", name: "Bitcoin" }, usd: { id: "usd", name: "US Dollars" }, - // Cumulative value units (running totals) - satsCumulative: { id: "sats-total", name: "Satoshis (Total)" }, - btcCumulative: { id: "btc-total", name: "Bitcoin (Total)" }, - usdCumulative: { id: "usd-total", name: "US Dollars (Total)" }, - // Ratios & percentages percentage: { id: "percentage", name: "Percentage" }, cagr: { id: "cagr", name: "CAGR (%/year)" }, @@ -36,15 +31,12 @@ export const Unit = /** @type {const} */ ({ // Counts count: { id: "count", name: "Count" }, - countCumulative: { id: "count-total", name: "Count (Total)" }, blocks: { id: "blocks", name: "Blocks" }, // Size bytes: { id: "bytes", name: "Bytes" }, - bytesCumulative: { id: "bytes-total", name: "Bytes (Total)" }, vb: { id: "vb", name: "Virtual Bytes" }, wu: { id: "wu", name: "Weight Units" }, - wuCumulative: { id: "wu-total", name: "Weight Units (Total)" }, // Mining hashRate: { id: "hashrate", name: "Hash Rate" },