Compare commits

..

24 Commits

Author SHA1 Message Date
nym21 65685c23e1 release: v0.0.56 2025-06-13 18:03:28 +02:00
nym21 2f74748cea computer: stateful: reset when reorg detected 2025-06-13 18:03:09 +02:00
nym21 f477bd66f3 release: v0.0.55 2025-06-13 10:23:38 +02:00
nym21 d7d77ae8f0 global: multiple fixes 2025-06-13 10:22:03 +02:00
nym21 31110a740d release: v0.0.54 2025-06-12 22:18:36 +02:00
nym21 b64d8b1d7f release: v0.0.53 2025-06-12 22:16:33 +02:00
nym21 c46006aacc web: filter possible index choices in charts 2025-06-12 22:09:33 +02:00
nym21 92f81b1493 web: fix css 2025-06-12 20:23:23 +02:00
nym21 70213cfc8f websites: default: add auto price series type 2025-06-12 18:41:56 +02:00
nym21 8a82bf5c50 websites: default: add live price 2025-06-12 18:10:24 +02:00
nym21 37405384a2 vec: fixed compressed, still slow par read, cli: made raw the default 2025-06-12 16:31:54 +02:00
nym21 54ea6cc53b indexer: only raw format + global: fixes 2025-06-12 12:33:43 +02:00
nym21 339c00d815 release: v0.0.52 2025-06-11 21:19:41 +02:00
nym21 ea6b4dcde2 websites: default: remove scrollToSelected 2025-06-11 21:19:22 +02:00
nym21 2b84623d1e release: v0.0.51 2025-06-11 21:09:07 +02:00
nym21 c8b3afa56b websites: default: fix sw adn co 2025-06-11 21:08:42 +02:00
nym21 1348f3c24c release: v0.0.50 2025-06-11 18:11:22 +02:00
nym21 62208ce3e1 websites: default: fix minBarSpacing 2025-06-11 18:11:11 +02:00
nym21 813b2481de release: v0.0.49 2025-06-11 17:51:31 +02:00
nym21 27b924ba61 cargo: set full version of crates 2025-06-11 17:51:11 +02:00
nym21 b40170b8ce websites: default: snapshot 2025-06-11 17:45:17 +02:00
nym21 8bfa9d2734 websites: default: snapshot 2025-06-11 11:25:25 +02:00
nym21 c7cf76d4a8 websites: default: snapshot 2025-06-10 18:54:18 +02:00
nym21 dfd2969b3e websites: default: snapshot 2025-06-09 17:58:26 +02:00
41 changed files with 2325 additions and 1875 deletions
Generated
+68 -68
View File
@@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "adler2" name = "adler2"
version = "2.0.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "aes" name = "aes"
@@ -146,9 +146,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.23" version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" checksum = "d615619615a650c571269c00dca41db04b9210037fa76ed8239f70404ab56985"
dependencies = [ dependencies = [
"brotli", "brotli",
"flate2", "flate2",
@@ -388,7 +388,7 @@ dependencies = [
[[package]] [[package]]
name = "brk" name = "brk"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"brk_cli", "brk_cli",
"brk_computer", "brk_computer",
@@ -407,7 +407,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_cli" name = "brk_cli"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"bitcoincore-rpc", "bitcoincore-rpc",
"brk_computer", "brk_computer",
@@ -432,7 +432,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_computer" name = "brk_computer"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -453,7 +453,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_core" name = "brk_core"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"bincode", "bincode",
"bitcoin", "bitcoin",
@@ -474,7 +474,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_exit" name = "brk_exit"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"brk_logger", "brk_logger",
"ctrlc", "ctrlc",
@@ -483,7 +483,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_fetcher" name = "brk_fetcher"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"brk_core", "brk_core",
"brk_logger", "brk_logger",
@@ -496,7 +496,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_indexer" name = "brk_indexer"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -514,7 +514,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_logger" name = "brk_logger"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"color-eyre", "color-eyre",
"env_logger", "env_logger",
@@ -524,7 +524,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_parser" name = "brk_parser"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -539,7 +539,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_query" name = "brk_query"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"brk_computer", "brk_computer",
"brk_core", "brk_core",
@@ -557,7 +557,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_server" name = "brk_server"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"axum", "axum",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -586,7 +586,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_state" name = "brk_state"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"bincode", "bincode",
"brk_core", "brk_core",
@@ -600,7 +600,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_store" name = "brk_store"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"brk_core", "brk_core",
@@ -610,7 +610,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_vec" name = "brk_vec"
version = "0.0.48" version = "0.0.56"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"brk_core", "brk_core",
@@ -722,9 +722,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
@@ -757,18 +757,18 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.39" version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.39" version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -778,21 +778,21 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.32" version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]] [[package]]
name = "color-eyre" name = "color-eyre"
@@ -990,7 +990,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -1001,7 +1001,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -1042,7 +1042,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -1088,7 +1088,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -1258,7 +1258,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@@ -1547,7 +1547,7 @@ checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -1698,9 +1698,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
@@ -1719,9 +1719,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.8" version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
@@ -1747,7 +1747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [ dependencies = [
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -1876,7 +1876,7 @@ checksum = "3bd3da01a295024fa79e3b4aba14b590d91256a274ff29cc5ee8f55183b2df24"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -1918,7 +1918,7 @@ dependencies = [
"phf", "phf",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2280,7 +2280,7 @@ dependencies = [
"phf_shared", "phf_shared",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2359,7 +2359,7 @@ dependencies = [
"proc-macro-error-attr2", "proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2515,9 +2515,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
@@ -2653,7 +2653,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2726,7 +2726,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2826,9 +2826,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.101" version = "2.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2862,7 +2862,7 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2915,7 +2915,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -2982,7 +2982,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -3107,7 +3107,7 @@ checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -3167,9 +3167,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
@@ -3238,9 +3238,9 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
@@ -3273,7 +3273,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -3295,7 +3295,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -3336,7 +3336,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -3347,14 +3347,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b"
[[package]] [[package]]
name = "windows-result" name = "windows-result"
@@ -3458,9 +3458,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.10" version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -3497,7 +3497,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
@@ -3517,7 +3517,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.102",
] ]
[[package]] [[package]]
+16 -16
View File
@@ -4,7 +4,7 @@ members = ["crates/*"]
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node" package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
package.license = "MIT" package.license = "MIT"
package.edition = "2024" package.edition = "2024"
package.version = "0.0.48" package.version = "0.0.56"
package.homepage = "https://bitcoinresearchkit.org" package.homepage = "https://bitcoinresearchkit.org"
package.repository = "https://github.com/bitcoinresearchkit/brk" package.repository = "https://github.com/bitcoinresearchkit/brk"
@@ -22,22 +22,22 @@ axum = "0.8.4"
bincode = { version = "2.0.1", features = ["serde"] } bincode = { version = "2.0.1", features = ["serde"] }
bitcoin = { version = "0.32.6", features = ["serde"] } bitcoin = { version = "0.32.6", features = ["serde"] }
bitcoincore-rpc = "0.19.0" bitcoincore-rpc = "0.19.0"
brk_cli = { version = "0", path = "crates/brk_cli" } brk_cli = { version = "0.0.56", path = "crates/brk_cli" }
brk_computer = { version = "0", path = "crates/brk_computer" } brk_computer = { version = "0.0.56", path = "crates/brk_computer" }
brk_core = { version = "0", path = "crates/brk_core" } brk_core = { version = "0.0.56", path = "crates/brk_core" }
brk_exit = { version = "0", path = "crates/brk_exit" } brk_exit = { version = "0.0.56", path = "crates/brk_exit" }
brk_fetcher = { version = "0", path = "crates/brk_fetcher" } brk_fetcher = { version = "0.0.56", path = "crates/brk_fetcher" }
brk_indexer = { version = "0", path = "crates/brk_indexer" } brk_indexer = { version = "0.0.56", path = "crates/brk_indexer" }
brk_logger = { version = "0", path = "crates/brk_logger" } brk_logger = { version = "0.0.56", path = "crates/brk_logger" }
brk_parser = { version = "0", path = "crates/brk_parser" } brk_parser = { version = "0.0.56", path = "crates/brk_parser" }
brk_query = { version = "0", path = "crates/brk_query" } brk_query = { version = "0.0.56", path = "crates/brk_query" }
brk_server = { version = "0", path = "crates/brk_server" } brk_server = { version = "0.0.56", path = "crates/brk_server" }
brk_state = { version = "0", path = "crates/brk_state" } brk_state = { version = "0.0.56", path = "crates/brk_state" }
brk_store = { version = "0", path = "crates/brk_store" } brk_store = { version = "0.0.56", path = "crates/brk_store" }
brk_vec = { version = "0", path = "crates/brk_vec" } brk_vec = { version = "0.0.56", path = "crates/brk_vec" }
byteview = "=0.6.1" byteview = "=0.6.1"
clap = { version = "4.5.39", features = ["string"] } clap = { version = "4.5.40", features = ["string"] }
clap_derive = "4.5.32" clap_derive = "4.5.40"
color-eyre = "0.6.5" color-eyre = "0.6.5"
derive_deref = "1.1.1" derive_deref = "1.1.1"
fjall = "2.11.0" fjall = "2.11.0"
+2 -2
View File
@@ -20,9 +20,9 @@ struct Cli {
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
enum Commands { enum Commands {
/// Run the indexer, computer and server /// Run the indexer, computer and server, use `run -h` for more information
Run(RunConfig), Run(RunConfig),
/// Query generated datasets via the `run` command in a similar fashion as the server's API /// Query generated datasets via the `run` command in a similar fashion as the server's API, use `query -h` for more information
Query(QueryArgs), Query(QueryArgs),
} }
+1 -1
View File
@@ -10,7 +10,7 @@ pub fn query(params: QueryParams) -> color_eyre::Result<()> {
let format = config.format(); let format = config.format();
let mut indexer = Indexer::new(&config.outputsdir(), format, config.check_collisions())?; let mut indexer = Indexer::new(&config.outputsdir(), config.check_collisions())?;
indexer.import_vecs()?; indexer.import_vecs()?;
let mut computer = Computer::new(&config.outputsdir(), config.fetcher(), format); let mut computer = Computer::new(&config.outputsdir(), config.fetcher(), format);
+30 -15
View File
@@ -7,7 +7,7 @@ use std::{
use bitcoincore_rpc::{self, Auth, Client, RpcApi}; use bitcoincore_rpc::{self, Auth, Client, RpcApi};
use brk_computer::Computer; use brk_computer::Computer;
use brk_core::{default_bitcoin_path, default_brk_path, dot_brk_path}; use brk_core::{default_bitcoin_path, default_brk_path, default_on_error, dot_brk_path};
use brk_exit::Exit; use brk_exit::Exit;
use brk_fetcher::Fetcher; use brk_fetcher::Fetcher;
use brk_indexer::Indexer; use brk_indexer::Indexer;
@@ -29,7 +29,7 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
let format = config.format(); let format = config.format();
let mut indexer = Indexer::new(&config.outputsdir(), format, config.check_collisions())?; let mut indexer = Indexer::new(&config.outputsdir(), config.check_collisions())?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
@@ -109,62 +109,77 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
#[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct RunConfig { pub struct RunConfig {
/// Bitcoin main directory path, defaults: ~/.bitcoin, ~/Library/Application\ Support/Bitcoin, saved /// Bitcoin main directory path, defaults: ~/.bitcoin, ~/Library/Application\ Support/Bitcoin, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
bitcoindir: Option<String>, bitcoindir: Option<String>,
/// Bitcoin blocks directory path, default: --bitcoindir/blocks, saved /// Bitcoin blocks directory path, default: --bitcoindir/blocks, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
blocksdir: Option<String>, blocksdir: Option<String>,
/// Bitcoin Research Kit outputs directory path, default: ~/.brk, saved /// Bitcoin Research Kit outputs directory path, default: ~/.brk, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
brkdir: Option<String>, brkdir: Option<String>,
/// Executed by the runner, default: all, saved /// Activated services, default: all, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)] #[arg(short, long)]
mode: Option<Mode>, services: Option<Services>,
/// Computation mode for compatible datasets, `lazy` computes data whenever requested without saving it, `eager` computes the data once and saves it to disk, default: Lazy, saved /// Computation of computed datasets, `lazy` computes data whenever requested without saving it, `eager` computes the data once and saves it to disk, default: `lazy`, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)] #[arg(short, long)]
computation: Option<Computation>, computation: Option<Computation>,
/// Activate compression of datasets, set to true to save disk space or false if prioritize speed, default: true, saved /// Format of computed datasets, `compressed` to save disk space (experimental), `raw` to prioritize speed, default: `raw`, saved
#[arg(short, long, value_name = "FORMAT")] #[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)]
format: Option<Format>, format: Option<Format>,
/// Activate fetching prices from exchanges APIs and the computation of all related datasets, default: true, saved /// Activate fetching prices from exchanges APIs and the computation of all related datasets, default: true, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short = 'F', long, value_name = "BOOL")] #[arg(short = 'F', long, value_name = "BOOL")]
fetch: Option<bool>, fetch: Option<bool>,
/// Website served by the server (if active), default: default, saved /// Website served by the server (if active), default: default, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)] #[arg(short, long)]
website: Option<Website>, website: Option<Website>,
/// Bitcoin RPC ip, default: localhost, saved /// Bitcoin RPC ip, default: localhost, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "IP")] #[arg(long, value_name = "IP")]
rpcconnect: Option<String>, rpcconnect: Option<String>,
/// Bitcoin RPC port, default: 8332, saved /// Bitcoin RPC port, default: 8332, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PORT")] #[arg(long, value_name = "PORT")]
rpcport: Option<u16>, rpcport: Option<u16>,
/// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved /// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
rpccookiefile: Option<String>, rpccookiefile: Option<String>,
/// Bitcoin RPC username, saved /// Bitcoin RPC username, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "USERNAME")] #[arg(long, value_name = "USERNAME")]
rpcuser: Option<String>, rpcuser: Option<String>,
/// Bitcoin RPC password, saved /// Bitcoin RPC password, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PASSWORD")] #[arg(long, value_name = "PASSWORD")]
rpcpassword: Option<String>, rpcpassword: Option<String>,
/// Delay between runs, default: 0, saved /// Delay between runs, default: 0, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "SECONDS")] #[arg(long, value_name = "SECONDS")]
delay: Option<u64>, delay: Option<u64>,
/// DEV: Activate checking address hashes for collisions when indexing, default: false, saved /// DEV: Activate checking address hashes for collisions when indexing, default: false, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "BOOL")] #[arg(long, value_name = "BOOL")]
check_collisions: Option<bool>, check_collisions: Option<bool>,
} }
@@ -192,8 +207,8 @@ impl RunConfig {
config_saved.brkdir = Some(brkdir); config_saved.brkdir = Some(brkdir);
} }
if let Some(mode) = config_args.mode.take() { if let Some(services) = config_args.services.take() {
config_saved.mode = Some(mode); config_saved.services = Some(services);
} }
if let Some(computation) = config_args.computation.take() { if let Some(computation) = config_args.computation.take() {
@@ -255,7 +270,7 @@ impl RunConfig {
// info!("Configuration {{"); // info!("Configuration {{");
// info!(" bitcoindir: {:?}", config.bitcoindir); // info!(" bitcoindir: {:?}", config.bitcoindir);
// info!(" brkdir: {:?}", config.brkdir); // info!(" brkdir: {:?}", config.brkdir);
// info!(" mode: {:?}", config.mode); // info!(" services: {:?}", config.services);
// info!(" website: {:?}", config.website); // info!(" website: {:?}", config.website);
// info!(" rpcconnect: {:?}", config.rpcconnect); // info!(" rpcconnect: {:?}", config.rpcconnect);
// info!(" rpcport: {:?}", config.rpcport); // info!(" rpcport: {:?}", config.rpcport);
@@ -375,13 +390,13 @@ impl RunConfig {
} }
pub fn process(&self) -> bool { pub fn process(&self) -> bool {
self.mode self.services
.is_none_or(|m| m == Mode::All || m == Mode::Processor) .is_none_or(|m| m == Services::All || m == Services::Processor)
} }
pub fn serve(&self) -> bool { pub fn serve(&self) -> bool {
self.mode self.services
.is_none_or(|m| m == Mode::All || m == Mode::Server) .is_none_or(|m| m == Services::All || m == Services::Server)
} }
fn path_cookiefile(&self) -> PathBuf { fn path_cookiefile(&self) -> PathBuf {
@@ -449,7 +464,7 @@ impl RunConfig {
PartialOrd, PartialOrd,
Ord, Ord,
)] )]
pub enum Mode { pub enum Services {
#[default] #[default]
All, All,
Processor, Processor,
+1 -1
View File
@@ -33,7 +33,7 @@ pub fn main() -> color_eyre::Result<()> {
let format = Format::Raw; let format = Format::Raw;
let mut indexer = Indexer::new(outputs_dir, format, true)?; let mut indexer = Indexer::new(outputs_dir, true)?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
@@ -226,7 +226,11 @@ impl ComputedValueVecsFromTxindex {
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> { pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[ [
self.sats.vecs(), self.sats.vecs(),
vec![&self.bitcoin_txindex as &dyn AnyCollectableVec],
self.bitcoin.vecs(), self.bitcoin.vecs(),
self.dollars_txindex
.as_ref()
.map_or(vec![], |v| vec![v as &dyn AnyCollectableVec]),
self.dollars.as_ref().map_or(vec![], |v| v.vecs()), self.dollars.as_ref().map_or(vec![], |v| v.vecs()),
] ]
.into_iter() .into_iter()
+1
View File
@@ -124,6 +124,7 @@ impl Vecs {
fetcher: Option<&mut Fetcher>, fetcher: Option<&mut Fetcher>,
exit: &Exit, exit: &Exit,
) -> color_eyre::Result<()> { ) -> color_eyre::Result<()> {
info!("Computing indexes...");
let starting_indexes = self.indexes.compute(indexer, starting_indexes, exit)?; let starting_indexes = self.indexes.compute(indexer, starting_indexes, exit)?;
info!("Computing constants..."); info!("Computing constants...");
+15 -13
View File
@@ -1289,7 +1289,7 @@ impl Vecs {
base_version + self.height_to_opreturn_supply.inner_version(), base_version + self.height_to_opreturn_supply.inner_version(),
)?; )?;
let mut chain_state: Vec<BlockState>; let mut chain_state: Vec<BlockState> = vec![];
let mut chain_state_starting_height = Height::from(self.chain_state.len()); let mut chain_state_starting_height = Height::from(self.chain_state.len());
let stateful_starting_height = match separate_utxo_vecs let stateful_starting_height = match separate_utxo_vecs
@@ -1322,25 +1322,27 @@ impl Vecs {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
chain_state_starting_height chain_state_starting_height
} }
Ordering::Less => { Ordering::Less => Height::ZERO,
// todo!("rollback instead");
chain_state = vec![];
chain_state_starting_height = Height::ZERO;
Height::ZERO
}
}; };
if stateful_starting_height.is_zero() {
info!("Starting processing utxos from the start");
separate_utxo_vecs
.par_iter_mut()
.try_for_each(|(_, v)| v.state.price_to_amount.reset())?;
}
let starting_height = starting_indexes let starting_height = starting_indexes
.height .height
.min(stateful_starting_height) .min(stateful_starting_height)
.min(Height::from(self.height_to_unspendable_supply.len())) .min(Height::from(self.height_to_unspendable_supply.len()))
.min(Height::from(self.height_to_opreturn_supply.len())); .min(Height::from(self.height_to_opreturn_supply.len()));
if starting_height.is_zero() {
info!("Starting processing utxos from the start");
// todo!("rollback instead");
chain_state = vec![];
chain_state_starting_height = Height::ZERO;
separate_utxo_vecs
.par_iter_mut()
.try_for_each(|(_, v)| v.state.price_to_amount.reset())?;
}
if starting_height == Height::from(height_to_date_fixed.len()) { if starting_height == Height::from(height_to_date_fixed.len()) {
return Ok(()); return Ok(());
} }
+2
View File
@@ -3,9 +3,11 @@ mod checked_sub;
mod paths; mod paths;
mod pause; mod pause;
mod rlimit; mod rlimit;
mod serde;
pub use bytes::*; pub use bytes::*;
pub use checked_sub::*; pub use checked_sub::*;
pub use paths::*; pub use paths::*;
pub use pause::*; pub use pause::*;
pub use rlimit::*; pub use rlimit::*;
pub use serde::*;
+12
View File
@@ -0,0 +1,12 @@
use serde::{Deserialize, Deserializer};
pub fn default_on_error<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
match T::deserialize(deserializer) {
Ok(v) => Ok(v),
Err(_) => Ok(T::default()),
}
}
+14 -1
View File
@@ -1,5 +1,5 @@
use brk_core::{Date, Height}; use brk_core::{Date, Height};
use brk_fetcher::{BRK, Fetcher}; use brk_fetcher::{BRK, Binance, Fetcher, Kraken};
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
@@ -12,6 +12,19 @@ fn main() -> color_eyre::Result<()> {
let mut fetcher = Fetcher::import(None)?; let mut fetcher = Fetcher::import(None)?;
Binance::fetch_1d().map(|b| {
dbg!(b.last_key_value());
})?;
Kraken::fetch_1d().map(|b| {
dbg!(b.last_key_value());
})?;
Binance::fetch_1mn().map(|b| {
dbg!(b.last_key_value());
})?;
Kraken::fetch_1mn().map(|b| {
dbg!(b.last_key_value());
})?;
dbg!(fetcher.get_date(Date::new(2025, 6, 5))?); dbg!(fetcher.get_date(Date::new(2025, 6, 5))?);
dbg!(fetcher.get_height( dbg!(fetcher.get_height(
899911_u32.into(), 899911_u32.into(),
+2 -2
View File
@@ -32,7 +32,7 @@ impl Kraken {
) )
} }
fn fetch_1mn() -> color_eyre::Result<BTreeMap<Timestamp, OHLCCents>> { pub fn fetch_1mn() -> color_eyre::Result<BTreeMap<Timestamp, OHLCCents>> {
info!("Fetching 1mn prices from Kraken..."); info!("Fetching 1mn prices from Kraken...");
retry( retry(
@@ -54,7 +54,7 @@ impl Kraken {
.ok_or(color_eyre::eyre::Error::msg("Couldn't find date")) .ok_or(color_eyre::eyre::Error::msg("Couldn't find date"))
} }
fn fetch_1d() -> color_eyre::Result<BTreeMap<Date, OHLCCents>> { pub fn fetch_1d() -> color_eyre::Result<BTreeMap<Date, OHLCCents>> {
info!("Fetching daily prices from Kraken..."); info!("Fetching daily prices from Kraken...");
retry( retry(
+5 -5
View File
@@ -40,11 +40,11 @@ impl Fetcher {
} }
fn get_date_(&mut self, date: Date, tries: usize) -> color_eyre::Result<OHLCCents> { fn get_date_(&mut self, date: Date, tries: usize) -> color_eyre::Result<OHLCCents> {
self.binance self.kraken
.get_from_1d(&date) .get_from_1d(&date)
.or_else(|_| { .or_else(|_| {
// eprintln!("{e}"); // eprintln!("{e}");
self.kraken.get_from_1d(&date) self.binance.get_from_1d(&date)
}) })
.or_else(|_| { .or_else(|_| {
// eprintln!("{e}"); // eprintln!("{e}");
@@ -90,11 +90,11 @@ impl Fetcher {
let previous_timestamp = previous_timestamp.map(|t| t.floor_seconds()); let previous_timestamp = previous_timestamp.map(|t| t.floor_seconds());
let ohlc = self let ohlc = self
.binance .kraken
.get_from_1mn(timestamp, previous_timestamp) .get_from_1mn(timestamp, previous_timestamp)
.unwrap_or_else(|_report| { .unwrap_or_else(|_report| {
// eprintln!("{_report}"); // eprintln!("{_report}");
self.kraken self.binance
.get_from_1mn(timestamp, previous_timestamp) .get_from_1mn(timestamp, previous_timestamp)
.unwrap_or_else(|_report| { .unwrap_or_else(|_report| {
// // eprintln!("{_report}"); // // eprintln!("{_report}");
@@ -185,8 +185,8 @@ How to fix this:
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.kraken.clear();
self.binance.clear(); self.binance.clear();
self.brk.clear(); self.brk.clear();
self.kraken.clear();
} }
} }
+1 -2
View File
@@ -4,7 +4,6 @@ use brk_core::default_bitcoin_path;
use brk_exit::Exit; use brk_exit::Exit;
use brk_indexer::Indexer; use brk_indexer::Indexer;
use brk_parser::Parser; use brk_parser::Parser;
use brk_vec::Format;
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
@@ -25,7 +24,7 @@ fn main() -> color_eyre::Result<()> {
let outputs = Path::new("../../_outputs"); let outputs = Path::new("../../_outputs");
let mut indexer = Indexer::new(outputs, Format::Raw, false)?; let mut indexer = Indexer::new(outputs, false)?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
+1 -7
View File
@@ -110,13 +110,7 @@ impl TryFrom<(&mut Vecs, &Stores, &Client)> for Indexes {
vecs.height_to_blockhash vecs.height_to_blockhash
.iter() .iter()
.get(*height) .get(*height)
.is_none_or(|saved_blockhash| { .is_none_or(|saved_blockhash| &rpc_blockhash != saved_blockhash.as_ref())
let b = &rpc_blockhash != saved_blockhash.as_ref();
if b {
dbg!(rpc_blockhash, saved_blockhash.as_ref());
}
b
})
}) })
.unwrap_or(starting_height); .unwrap_or(starting_height);
+2 -9
View File
@@ -19,7 +19,7 @@ use brk_core::{
use bitcoin::{Transaction, TxIn, TxOut}; use bitcoin::{Transaction, TxIn, TxOut};
use brk_exit::Exit; use brk_exit::Exit;
use brk_parser::Parser; use brk_parser::Parser;
use brk_vec::{AnyVec, Format, VecIterator}; use brk_vec::{AnyVec, VecIterator};
use color_eyre::eyre::{ContextCompat, eyre}; use color_eyre::eyre::{ContextCompat, eyre};
use fjall::TransactionalKeyspace; use fjall::TransactionalKeyspace;
use log::{error, info}; use log::{error, info};
@@ -42,21 +42,15 @@ pub struct Indexer {
vecs: Option<Vecs>, vecs: Option<Vecs>,
stores: Option<Stores>, stores: Option<Stores>,
check_collisions: bool, check_collisions: bool,
format: Format,
} }
impl Indexer { impl Indexer {
pub fn new( pub fn new(outputs_dir: &Path, check_collisions: bool) -> color_eyre::Result<Self> {
outputs_dir: &Path,
format: Format,
check_collisions: bool,
) -> color_eyre::Result<Self> {
setrlimit()?; setrlimit()?;
Ok(Self { Ok(Self {
path: outputs_dir.to_owned(), path: outputs_dir.to_owned(),
vecs: None, vecs: None,
stores: None, stores: None,
format,
check_collisions, check_collisions,
}) })
} }
@@ -65,7 +59,6 @@ impl Indexer {
self.vecs = Some(Vecs::forced_import( self.vecs = Some(Vecs::forced_import(
&self.path.join("vecs/indexed"), &self.path.join("vecs/indexed"),
VERSION + Version::ZERO, VERSION + Version::ZERO,
self.format,
)?); )?);
Ok(()) Ok(())
} }
+34 -38
View File
@@ -66,11 +66,7 @@ pub struct Vecs {
} }
impl Vecs { impl Vecs {
pub fn forced_import( pub fn forced_import(path: &Path, version: Version) -> color_eyre::Result<Self> {
path: &Path,
version: Version,
format: Format,
) -> color_eyre::Result<Self> {
fs::create_dir_all(path)?; fs::create_dir_all(path)?;
Ok(Self { Ok(Self {
@@ -78,7 +74,7 @@ impl Vecs {
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_blockhash: IndexedVec::forced_import( height_to_blockhash: IndexedVec::forced_import(
path, path,
@@ -90,145 +86,145 @@ impl Vecs {
path, path,
"difficulty", "difficulty",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_emptyoutputindex: IndexedVec::forced_import( height_to_first_emptyoutputindex: IndexedVec::forced_import(
path, path,
"first_emptyoutputindex", "first_emptyoutputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_inputindex: IndexedVec::forced_import( height_to_first_inputindex: IndexedVec::forced_import(
path, path,
"first_inputindex", "first_inputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_opreturnindex: IndexedVec::forced_import( height_to_first_opreturnindex: IndexedVec::forced_import(
path, path,
"first_opreturnindex", "first_opreturnindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_outputindex: IndexedVec::forced_import( height_to_first_outputindex: IndexedVec::forced_import(
path, path,
"first_outputindex", "first_outputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2aindex: IndexedVec::forced_import( height_to_first_p2aindex: IndexedVec::forced_import(
path, path,
"first_p2aindex", "first_p2aindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2msindex: IndexedVec::forced_import( height_to_first_p2msindex: IndexedVec::forced_import(
path, path,
"first_p2msindex", "first_p2msindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2pk33index: IndexedVec::forced_import( height_to_first_p2pk33index: IndexedVec::forced_import(
path, path,
"first_p2pk33index", "first_p2pk33index",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2pk65index: IndexedVec::forced_import( height_to_first_p2pk65index: IndexedVec::forced_import(
path, path,
"first_p2pk65index", "first_p2pk65index",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2pkhindex: IndexedVec::forced_import( height_to_first_p2pkhindex: IndexedVec::forced_import(
path, path,
"first_p2pkhindex", "first_p2pkhindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2shindex: IndexedVec::forced_import( height_to_first_p2shindex: IndexedVec::forced_import(
path, path,
"first_p2shindex", "first_p2shindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2trindex: IndexedVec::forced_import( height_to_first_p2trindex: IndexedVec::forced_import(
path, path,
"first_p2trindex", "first_p2trindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2wpkhindex: IndexedVec::forced_import( height_to_first_p2wpkhindex: IndexedVec::forced_import(
path, path,
"first_p2wpkhindex", "first_p2wpkhindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2wshindex: IndexedVec::forced_import( height_to_first_p2wshindex: IndexedVec::forced_import(
path, path,
"first_p2wshindex", "first_p2wshindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_txindex: IndexedVec::forced_import( height_to_first_txindex: IndexedVec::forced_import(
path, path,
"first_txindex", "first_txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_unknownoutputindex: IndexedVec::forced_import( height_to_first_unknownoutputindex: IndexedVec::forced_import(
path, path,
"first_unknownoutputindex", "first_unknownoutputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_timestamp: IndexedVec::forced_import( height_to_timestamp: IndexedVec::forced_import(
path, path,
"timestamp", "timestamp",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_total_size: IndexedVec::forced_import( height_to_total_size: IndexedVec::forced_import(
path, path,
"total_size", "total_size",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_weight: IndexedVec::forced_import( height_to_weight: IndexedVec::forced_import(
path, path,
"weight", "weight",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
inputindex_to_outputindex: IndexedVec::forced_import( inputindex_to_outputindex: IndexedVec::forced_import(
path, path,
"outputindex", "outputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
opreturnindex_to_txindex: IndexedVec::forced_import( opreturnindex_to_txindex: IndexedVec::forced_import(
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
outputindex_to_outputtype: IndexedVec::forced_import( outputindex_to_outputtype: IndexedVec::forced_import(
path, path,
"outputtype", "outputtype",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
outputindex_to_outputtypeindex: IndexedVec::forced_import( outputindex_to_outputtypeindex: IndexedVec::forced_import(
path, path,
"outputtypeindex", "outputtypeindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
outputindex_to_value: IndexedVec::forced_import( outputindex_to_value: IndexedVec::forced_import(
path, path,
"value", "value",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
p2aindex_to_p2abytes: IndexedVec::forced_import( p2aindex_to_p2abytes: IndexedVec::forced_import(
path, path,
@@ -240,7 +236,7 @@ impl Vecs {
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
p2pk33index_to_p2pk33bytes: IndexedVec::forced_import( p2pk33index_to_p2pk33bytes: IndexedVec::forced_import(
path, path,
@@ -288,13 +284,13 @@ impl Vecs {
path, path,
"base_size", "base_size",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_first_inputindex: IndexedVec::forced_import( txindex_to_first_inputindex: IndexedVec::forced_import(
path, path,
"first_inputindex", "first_inputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_first_outputindex: IndexedVec::forced_import( txindex_to_first_outputindex: IndexedVec::forced_import(
path, path,
@@ -306,19 +302,19 @@ impl Vecs {
path, path,
"is_explicitly_rbf", "is_explicitly_rbf",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_rawlocktime: IndexedVec::forced_import( txindex_to_rawlocktime: IndexedVec::forced_import(
path, path,
"rawlocktime", "rawlocktime",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_total_size: IndexedVec::forced_import( txindex_to_total_size: IndexedVec::forced_import(
path, path,
"total_size", "total_size",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_txid: IndexedVec::forced_import( txindex_to_txid: IndexedVec::forced_import(
path, path,
@@ -330,13 +326,13 @@ impl Vecs {
path, path,
"txversion", "txversion",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
unknownoutputindex_to_txindex: IndexedVec::forced_import( unknownoutputindex_to_txindex: IndexedVec::forced_import(
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
}) })
} }
+1 -1
View File
@@ -12,7 +12,7 @@ pub fn main() -> color_eyre::Result<()> {
let format = Format::Compressed; let format = Format::Compressed;
let mut indexer = Indexer::new(outputs_dir, format, true)?; let mut indexer = Indexer::new(outputs_dir, true)?;
indexer.import_vecs()?; indexer.import_vecs()?;
let mut computer = Computer::new(outputs_dir, None, format); let mut computer = Computer::new(outputs_dir, None, format);
+1 -1
View File
@@ -73,7 +73,7 @@ A list of all possible vec ids and their supported vec indexes
A list of all possible vec indexes and their supported vec ids A list of all possible vec indexes and their supported vec ids
#### `GET /api/{INDEX}-to-{VALUE}` #### `GET /api/{INDEX}-to-{ID}`
This endpoint retrieves data based on the specified vector index and id. This endpoint retrieves data based on the specified vector index and id.
+1 -1
View File
@@ -31,7 +31,7 @@ pub fn main() -> color_eyre::Result<()> {
let format = Format::Compressed; let format = Format::Compressed;
let mut indexer = Indexer::new(outputs_dir, format, true)?; let mut indexer = Indexer::new(outputs_dir, true)?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
+1
View File
@@ -124,6 +124,7 @@ pub async fn variant_handler(
Query(params_opt): Query<ParamsOpt>, Query(params_opt): Query<ParamsOpt>,
state: State<AppState>, state: State<AppState>,
) -> Response { ) -> Response {
let variant = variant.replace("_", "-");
let mut split = variant.split(TO_SEPARATOR); let mut split = variant.split(TO_SEPARATOR);
let params = Params::from(( let params = Params::from((
( (
+1 -1
View File
@@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ValueEnum, Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ValueEnum,
)] )]
pub enum Format { pub enum Format {
#[default]
Compressed, Compressed,
#[default]
Raw, Raw,
} }
+2 -1
View File
@@ -31,7 +31,7 @@ where
} }
#[inline] #[inline]
fn get_or_read_(&self, index: usize, mmap: &Mmap) -> Result<Option<Value<T>>> { fn get_or_read_(&self, index: usize, mmap: &Mmap) -> Result<Option<Value<T>>> {
let stored_len = mmap.len() / Self::SIZE_OF_T; let stored_len = self.stored_len_(mmap);
if index >= stored_len { if index >= stored_len {
let pushed = self.pushed(); let pushed = self.pushed();
@@ -53,6 +53,7 @@ where
fn mmap(&self) -> &ArcSwap<Mmap>; fn mmap(&self) -> &ArcSwap<Mmap>;
fn stored_len(&self) -> usize; fn stored_len(&self) -> usize;
fn stored_len_(&self, mmap: &Mmap) -> usize;
fn pushed(&self) -> &[T]; fn pushed(&self) -> &[T];
#[inline] #[inline]
+13 -5
View File
@@ -21,7 +21,9 @@ use crate::{
const ONE_KIB: usize = 1024; const ONE_KIB: usize = 1024;
const ONE_MIB: usize = ONE_KIB * ONE_KIB; const ONE_MIB: usize = ONE_KIB * ONE_KIB;
pub const MAX_CACHE_SIZE: usize = 100 * ONE_MIB; pub const MAX_CACHE_SIZE: usize = 100 * ONE_MIB;
pub const MAX_PAGE_SIZE: usize = 16 * ONE_KIB; pub const MAX_PAGE_SIZE: usize = 64 * ONE_KIB;
const VERSION: Version = Version::ONE;
#[derive(Debug)] #[derive(Debug)]
pub struct CompressedVec<I, T> { pub struct CompressedVec<I, T> {
@@ -39,7 +41,9 @@ where
pub const CACHE_LENGTH: usize = MAX_CACHE_SIZE / Self::PAGE_SIZE; pub const CACHE_LENGTH: usize = MAX_CACHE_SIZE / Self::PAGE_SIZE;
/// Same as import but will reset the folder under certain errors, so be careful ! /// Same as import but will reset the folder under certain errors, so be careful !
pub fn forced_import(path: &Path, version: Version) -> Result<Self> { pub fn forced_import(path: &Path, mut version: Version) -> Result<Self> {
version = version + VERSION;
let res = Self::import(path, version); let res = Self::import(path, version);
match res { match res {
Err(Error::WrongEndian) Err(Error::WrongEndian)
@@ -129,7 +133,7 @@ where
page_index * Self::PER_PAGE page_index * Self::PER_PAGE
} }
fn stored_len_(pages_meta: &Guard<Arc<CompressedPagesMetadata>>) -> usize { fn stored_len__(pages_meta: &Guard<Arc<CompressedPagesMetadata>>) -> usize {
if let Some(last) = pages_meta.last() { if let Some(last) = pages_meta.last() {
(pages_meta.len() - 1) * Self::PER_PAGE + last.values_len as usize (pages_meta.len() - 1) * Self::PER_PAGE + last.values_len as usize
} else { } else {
@@ -178,7 +182,11 @@ where
#[inline] #[inline]
fn stored_len(&self) -> usize { fn stored_len(&self) -> usize {
Self::stored_len_(&self.pages_meta.load()) Self::stored_len__(&self.pages_meta.load())
}
#[inline]
fn stored_len_(&self, _: &Mmap) -> usize {
self.stored_len()
} }
#[inline] #[inline]
@@ -477,7 +485,7 @@ where
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
let pages_meta = self.pages_meta.load(); let pages_meta = self.pages_meta.load();
let stored_len = CompressedVec::<I, T>::stored_len_(&pages_meta); let stored_len = CompressedVec::<I, T>::stored_len__(&pages_meta);
CompressedVecIterator { CompressedVecIterator {
vec: self, vec: self,
guard: self.mmap().load(), guard: self.mmap().load(),
+2 -2
View File
@@ -1116,12 +1116,12 @@ impl EagerVec<DateIndex, Dollars> {
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.validate_computed_version_or_reset_file( self.validate_computed_version_or_reset_file(
Version::ZERO + self.inner.version() + stacks.version(), Version::ONE + self.inner.version() + stacks.version(),
)?; )?;
let index = max_from.min(DateIndex::from(self.len())); let index = max_from.min(DateIndex::from(self.len()));
let first_price_date = DateIndex::try_from(Date::new(2010, 8, 16)).unwrap(); let first_price_date = DateIndex::try_from(Date::new(2010, 7, 12)).unwrap();
stacks.iter_at(index).try_for_each(|(i, stack)| { stacks.iter_at(index).try_for_each(|(i, stack)| {
let stack = stack.into_inner(); let stack = stack.into_inner();
+5 -1
View File
@@ -104,7 +104,11 @@ where
#[inline] #[inline]
fn stored_len(&self) -> usize { fn stored_len(&self) -> usize {
self.mmap.load().len() / Self::SIZE_OF_T self.stored_len_(&self.mmap.load())
}
#[inline]
fn stored_len_(&self, mmap: &Mmap) -> usize {
mmap.len() / Self::SIZE_OF_T
} }
#[inline] #[inline]
+7
View File
@@ -73,6 +73,13 @@ where
StoredVec::Compressed(v) => v.stored_len(), StoredVec::Compressed(v) => v.stored_len(),
} }
} }
#[inline]
fn stored_len_(&self, mmap: &Mmap) -> usize {
match self {
StoredVec::Raw(v) => v.stored_len_(mmap),
StoredVec::Compressed(v) => v.stored_len_(mmap),
}
}
#[inline] #[inline]
fn pushed(&self) -> &[T] { fn pushed(&self) -> &[T] {
+6 -5
View File
@@ -2,7 +2,6 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<!-- <title>kibo.money</title> -->
<meta <meta
name="description" name="description"
content="An open source Bitcoin Core data extractor and visualizer" content="An open source Bitcoin Core data extractor and visualizer"
@@ -966,7 +965,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1.5rem; gap: 1.5rem;
margin: -0.75rem var(--negative-main-padding); margin: -0.5rem var(--negative-main-padding);
padding: 0.75rem var(--main-padding); padding: 0.75rem var(--main-padding);
overflow-x: auto; overflow-x: auto;
min-width: 0; min-width: 0;
@@ -1123,10 +1122,10 @@
/** @param {boolean} dark */ /** @param {boolean} dark */
function updateThemeColor(dark) { function updateThemeColor(dark) {
const backgroundColor = getComputedStyle( const theme = getComputedStyle(
window.document.documentElement window.document.documentElement
).getPropertyValue(dark ? "--black" : "--white"); ).getPropertyValue(dark ? "--black" : "--white");
themeColor.content = backgroundColor; themeColor.content = theme;
} }
updateThemeColor(preferredColorSchemeMatchMedia.matches); updateThemeColor(preferredColorSchemeMatchMedia.matches);
@@ -1142,7 +1141,9 @@
} }
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service-worker.js"); window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js");
});
} }
</script> </script>
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "kibo.money", "name": "brk",
"short_name": "kibo", "short_name": "brk",
"description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode", "description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode",
"categories": [ "categories": [
"bitcoin", "bitcoin",
File diff suppressed because it is too large Load Diff
@@ -3,10 +3,12 @@
/** /**
* @import { SignalOptions } from "./v0.3.2-treeshaked/types/core/core" * @import { SignalOptions } from "./v0.3.2-treeshaked/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup } from "./v0.3.2-treeshaked/types/core/owner" * @import { getOwner as GetOwner, onCleanup as OnCleanup } from "./v0.3.2-treeshaked/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner } from "./v0.3.2-treeshaked/types/signals"; * @import { createSignal as CreateSignal, createEffect as CreateEffect, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner, Accessor } from "./v0.3.2-treeshaked/types/signals";
* @import { Signal } from "./types"; * @import { Signal } from "./types";
*/ */
let effectCount = 0;
const importSignals = import("./v0.3.2-treeshaked/script.js").then( const importSignals = import("./v0.3.2-treeshaked/script.js").then(
(_signals) => { (_signals) => {
const signals = { const signals = {
@@ -20,16 +22,30 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
// @ts-ignore // @ts-ignore
(compute, effect) => { (compute, effect) => {
let dispose = /** @type {VoidFunction | null} */ (null); let dispose = /** @type {VoidFunction | null} */ (null);
if (_signals.getOwner() === null) {
throw Error("No owner");
}
function cleanup() {
if (dispose) {
dispose();
dispose = null;
// console.log("effectCount = ", --effectCount);
}
}
// @ts-ignore // @ts-ignore
_signals.createEffect(compute, (v, oldV) => { _signals.createEffect(compute, (v, oldV) => {
dispose?.(); // console.log("effectCount = ", ++effectCount);
cleanup();
signals.createRoot((_dispose) => { signals.createRoot((_dispose) => {
dispose = _dispose; dispose = _dispose;
return effect(v, oldV); return effect(v, oldV);
}); });
signals.onCleanup(() => dispose?.()); signals.onCleanup(cleanup);
}); });
signals.onCleanup(() => dispose?.()); signals.onCleanup(cleanup);
} }
), ),
createMemo: /** @type {typeof CreateMemo} */ (_signals.createMemo), createMemo: /** @type {typeof CreateMemo} */ (_signals.createMemo),
@@ -40,7 +56,7 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
/** /**
* @template T * @template T
* @param {T} initialValue * @param {T} initialValue
* @param {SignalOptions<T> & {save?: {keyPrefix: string; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options] * @param {SignalOptions<T> & {save?: {keyPrefix: string | Accessor<string>; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options]
* @returns {Signal<T>} * @returns {Signal<T>}
*/ */
createSignal(initialValue, options) { createSignal(initialValue, options) {
@@ -59,7 +75,14 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
const save = options.save; const save = options.save;
const paramKey = save.key; const paramKey = save.key;
const storageKey = `${save.keyPrefix}-${paramKey}`; const storageKey = this.createMemo(
() =>
`${
typeof save.keyPrefix === "string"
? save.keyPrefix
: save.keyPrefix()
}-${paramKey}`
);
let serialized = /** @type {string | null} */ (null); let serialized = /** @type {string | null} */ (null);
if (options.save.serializeParam !== false) { if (options.save.serializeParam !== false) {
@@ -67,19 +90,31 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
paramKey paramKey
); );
} }
if (serialized === null) { if (serialized === null) {
serialized = localStorage.getItem(storageKey); serialized = localStorage.getItem(storageKey());
} }
if (serialized) { if (serialized) {
set(() => save.deserialize(serialized)); set(() =>
serialized ? save.deserialize(serialized) : initialValue
);
} }
let firstEffect = true; let firstRun1 = true;
this.createEffect(storageKey, (storageKey) => {
if (!firstRun1) {
serialized = localStorage.getItem(storageKey);
set(() =>
serialized ? save.deserialize(serialized) : initialValue
);
}
firstRun1 = false;
});
let firstRun2 = true;
this.createEffect(get, (value) => { this.createEffect(get, (value) => {
if (!save) return; if (!save) return;
if (!firstEffect) { if (!firstRun2) {
if ( if (
value !== undefined && value !== undefined &&
value !== null && value !== null &&
@@ -87,9 +122,9 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
initialValue === null || initialValue === null ||
save.serialize(value) !== save.serialize(initialValue)) save.serialize(value) !== save.serialize(initialValue))
) { ) {
localStorage.setItem(storageKey, save.serialize(value)); localStorage.setItem(storageKey(), save.serialize(value));
} else { } else {
localStorage.removeItem(storageKey); localStorage.removeItem(storageKey());
} }
} }
@@ -105,7 +140,7 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
removeParam(paramKey); removeParam(paramKey);
} }
firstEffect = false; firstRun2 = false;
}); });
} }
+420 -245
View File
@@ -1,10 +1,16 @@
// @ts-check // @ts-check
const keyPrefix = "chart";
const ONE_BTC_IN_SATS = 100_000_000;
const AUTO = "auto";
const LINE = "line";
const CANDLE = "candle";
/** /**
* @param {Object} args * @param {Object} args
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {LightweightCharts} args.lightweightCharts * @param {LightweightCharts} args.lightweightCharts
* @param {Accessor<ChartOption>} args.selected * @param {Accessor<ChartOption>} args.option
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {WebSockets} args.webSockets * @param {WebSockets} args.webSockets
@@ -16,7 +22,7 @@ export function init({
colors, colors,
elements, elements,
lightweightCharts, lightweightCharts,
selected, option,
signals, signals,
utils, utils,
webSockets, webSockets,
@@ -26,10 +32,41 @@ export function init({
elements.charts.append(utils.dom.createShadow("left")); elements.charts.append(utils.dom.createShadow("left"));
elements.charts.append(utils.dom.createShadow("right")); elements.charts.append(utils.dom.createShadow("right"));
const { headerElement, headingElement } = utils.dom.createHeader({}); const { headerElement, headingElement } = utils.dom.createHeader();
elements.charts.append(headerElement); elements.charts.append(headerElement);
const { index, fieldset } = createIndexSelector({
option,
vecIdToIndexes,
signals,
utils,
});
const TIMERANGE_LS_KEY = signals.createMemo(
() => `chart-timerange-${index()}`
);
let firstRun = true;
const from = signals.createSignal(/** @type {number | null} */ (null), {
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "from",
serializeParam: firstRun,
},
});
const to = signals.createSignal(/** @type {number | null} */ (null), {
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "to",
serializeParam: firstRun,
},
});
const chart = lightweightCharts.createChartElement({ const chart = lightweightCharts.createChartElement({
owner: signals.getOwner(),
parent: elements.charts, parent: elements.charts,
signals, signals,
colors, colors,
@@ -37,202 +74,340 @@ export function init({
utils, utils,
vecsResources, vecsResources,
elements, elements,
index,
timeScaleSetCallback: (unknownTimeScaleCallback) => {
// TODO: Although it mostly works in practice, need to make it more robust, there is no guarantee that this runs in order and wait for `from` and `to` to update when `index` and thus `TIMERANGE_LS_KEY` is updated
// Need to have the right values before the update
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
chart.inner.timeScale().setVisibleLogicalRange({
from: from_,
to: to_,
});
} else {
unknownTimeScaleCallback();
}
},
}); });
const index = createIndexSelector({ elements, signals, utils }); chart.inner.timeScale().subscribeVisibleLogicalRangeChange(
utils.debounce((t) => {
if (t) {
from.set(t.from);
to.set(t.to);
}
})
);
let firstRun = true; elements.charts.append(fieldset);
signals.createEffect(selected, (option) => { const { field: seriesTypeField, selected: topSeriesType_ } =
utils.dom.createHorizontalChoiceField({
defaultValue: CANDLE,
keyPrefix,
key: "seriestype-0",
choices: /** @type {const} */ ([AUTO, CANDLE, LINE]),
signals,
});
const topSeriesType = signals.createMemo(() => {
const topSeriesType = topSeriesType_();
if (topSeriesType === AUTO) {
const t = to();
const f = from();
if (!t || !f) return null;
const diff = t - f;
if (diff / chart.inner.paneSize().width <= 0.5) {
return CANDLE;
} else {
return LINE;
}
} else {
return topSeriesType;
}
});
const { field: topUnitField, selected: topUnit } =
utils.dom.createHorizontalChoiceField({
defaultValue: "USD",
keyPrefix,
key: "unit-0",
choices: /** @type {const} */ ([
/** @satisfies {Unit} */ ("USD"),
/** @satisfies {Unit} */ ("Sats"),
]),
signals,
sorted: true,
});
chart.addFieldsetIfNeeded({
id: "charts-unit-0",
paneIndex: 0,
position: "nw",
createChild() {
return topUnitField;
},
});
const seriesListTop = /** @type {Series[]} */ ([]);
const seriesListBottom = /** @type {Series[]} */ ([]);
/**
* @param {Object} params
* @param {ISeriesApi<any, number>} params.iseries
* @param {Unit} params.unit
* @param {Index} params.index
*/
function printLatest({ iseries, unit, index }) {
const _latest = webSockets.kraken1dCandle.latest();
if (!_latest) return;
const latest = { ..._latest };
if (unit === "Sats") {
latest.open = Math.floor(ONE_BTC_IN_SATS / latest.open);
latest.high = Math.floor(ONE_BTC_IN_SATS / latest.high);
latest.low = Math.floor(ONE_BTC_IN_SATS / latest.low);
latest.close = Math.floor(ONE_BTC_IN_SATS / latest.close);
latest.value = Math.floor(ONE_BTC_IN_SATS / latest.value);
}
const last_ = iseries.data().at(-1);
if (!last_) return;
const last = { ...last_ };
if ("close" in last) {
last.close = latest.close;
}
if ("value" in last) {
last.value = latest.value;
}
const date = new Date(latest.time * 1000);
switch (index) {
case /** @satisfies {Height} */ (5): {
if ("close" in last) {
last.low = Math.min(last.low, latest.close);
last.high = Math.max(last.high, latest.close);
}
iseries.update(last);
break;
}
case /** @satisfies {DateIndex} */ (0): {
iseries.update(latest);
break;
}
default: {
if (index === /** @satisfies {WeekIndex} */ (22)) {
date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 6) % 7));
} else if (index === /** @satisfies {MonthIndex} */ (7)) {
date.setUTCDate(1);
} else if (index === /** @satisfies {QuarterIndex} */ (19)) {
const month = date.getUTCMonth();
date.setUTCMonth(month - (month % 3), 1);
} else if (index === /** @satisfies {YearIndex} */ (23)) {
date.setUTCMonth(0, 1);
} else if (index === /** @satisfies {DecadeIndex} */ (1)) {
date.setUTCFullYear(
Math.floor(date.getUTCFullYear() / 10) * 10,
0,
1
);
} else {
throw Error("Unsupported");
}
const time = date.valueOf() / 1000;
if (time === last.time) {
if ("close" in last) {
last.low = Math.min(last.low, latest.low);
last.high = Math.max(last.high, latest.high);
}
iseries.update(last);
} else {
latest.time = time;
iseries.update(latest);
}
}
}
}
signals.createEffect(option, (option) => {
headingElement.innerHTML = option.title; headingElement.innerHTML = option.title;
const bottomUnits = /** @type {readonly Unit[]} */ (
Object.keys(option.bottom)
);
const { field: bottomUnitField, selected: bottomUnit } =
utils.dom.createHorizontalChoiceField({
defaultValue: bottomUnits.at(0) || "",
keyPrefix,
key: "unit-1",
choices: bottomUnits,
signals,
sorted: true,
});
if (bottomUnits.length) {
chart.addFieldsetIfNeeded({
id: "charts-unit-1",
paneIndex: 1,
position: "nw",
createChild() {
return bottomUnitField;
},
});
}
chart.addFieldsetIfNeeded({
id: "charts-seriestype-0",
paneIndex: 0,
position: "ne",
createChild() {
return seriesTypeField;
},
});
signals.createEffect(index, (index) => { signals.createEffect(index, (index) => {
const { field: topUnitField, selected: topUnit } = signals.createEffect(
utils.dom.createHorizontalChoiceField({ () => ({
defaultValue: "USD", topUnit: topUnit(),
keyPrefix: "charts", topSeriesType: topSeriesType(),
key: "unit-0", }),
choices: /** @type {const} */ ([ ({ topUnit, topSeriesType }) => {
/** @satisfies {Unit} */ ("USD"), /** @type {Series | undefined} */
/** @satisfies {Unit} */ ("Sats"), let series;
]),
signals,
sorted: true,
});
signals.createEffect(topUnit, (topUnit) => { switch (topUnit) {
const { field: seriesTypeField, selected: topSeriesType } = case "USD": {
utils.dom.createHorizontalChoiceField({ switch (topSeriesType) {
defaultValue: "Line", case CANDLE: {
keyPrefix: "charts", series = chart.addCandlestickSeries({
key: "seriestype-0", vecId: "ohlc",
choices: /** @type {const} */ (["Candles", "Line"]), name: "Price",
signals, unit: topUnit,
}); setDataCallback: printLatest,
order: 0,
signals.createEffect(topSeriesType, (topSeriesType) => {
const bottomUnits = /** @type {readonly Unit[]} */ (
Object.keys(option.bottom)
);
const { field: bottomUnitField, selected: bottomUnit } =
utils.dom.createHorizontalChoiceField({
defaultValue: bottomUnits.at(0) || "",
keyPrefix: "charts",
key: "unit-1",
choices: bottomUnits,
signals,
sorted: true,
});
signals.createEffect(bottomUnit, (bottomUnit) => {
chart.reset({ owner: signals.getOwner() });
chart.addFieldsetIfNeeded({
id: "charts-unit-0",
paneIndex: 0,
position: "nw",
createChild() {
return topUnitField;
},
});
if (bottomUnits.length) {
chart.addFieldsetIfNeeded({
id: "charts-unit-1",
paneIndex: 1,
position: "nw",
createChild() {
return bottomUnitField;
},
});
}
chart.addFieldsetIfNeeded({
id: "charts-seriestype-0",
paneIndex: 0,
position: "ne",
createChild() {
return seriesTypeField;
},
});
const TIMERANGE_LS_KEY = `chart-timerange-${index}`;
const from = signals.createSignal(
/** @type {number | null} */ (null),
{
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "from",
serializeParam: firstRun,
},
}
);
const to = signals.createSignal(
/** @type {number | null} */ (null),
{
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "to",
serializeParam: firstRun,
},
}
);
chart.create({
index,
timeScaleSetCallback: (unknownTimeScaleCallback) => {
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
chart.inner()?.timeScale().setVisibleLogicalRange({
from: from_,
to: to_,
}); });
} else {
unknownTimeScaleCallback(); break;
} }
}, case LINE: {
series = chart.addLineSeries({
vecId: "close",
name: "Price",
unit: topUnit,
color: colors.default,
setDataCallback: printLatest,
options: {
priceLineVisible: true,
},
order: 0,
});
}
}
break;
}
case "Sats": {
switch (topSeriesType) {
case CANDLE: {
series = chart.addCandlestickSeries({
vecId: "ohlc-in-sats",
name: "Price",
unit: topUnit,
inverse: true,
setDataCallback: printLatest,
order: 0,
});
break;
}
case LINE: {
series = chart.addLineSeries({
vecId: "close-in-sats",
name: "Price",
unit: topUnit,
color: colors.default,
setDataCallback: printLatest,
options: {
priceLineVisible: true,
},
order: 0,
});
}
}
break;
}
}
if (!series) throw Error("Unreachable");
seriesListTop[0]?.remove();
seriesListTop[0] = series;
// setDataCallback insimport("./options").tead of hasData
signals.createEffect(
() => ({
latest: webSockets.kraken1dCandle.latest(),
hasData: series.hasData(),
}),
({ latest, hasData }) => {
if (!series || !latest || !hasData) return;
printLatest({ iseries: series.inner, unit: topUnit, index });
}
);
}
);
[
{
blueprints: option.top,
paneIndex: 0,
unit: topUnit,
seriesList: seriesListTop,
orderStart: 1,
legend: chart.legendTop,
},
{
blueprints: option.bottom,
paneIndex: 1,
unit: bottomUnit,
seriesList: seriesListBottom,
orderStart: 0,
legend: chart.legendBottom,
},
].forEach(
({
blueprints,
paneIndex,
unit,
seriesList: seriesList,
orderStart,
legend,
}) => {
signals.createEffect(unit, (unit) => {
legend.removeFrom(orderStart);
seriesList.splice(orderStart).forEach((series) => {
series.remove();
}); });
switch (topUnit) { blueprints[unit]?.forEach((blueprint, order) => {
case "USD": { order += orderStart;
switch (topSeriesType) {
case "Candles": {
const candles = chart.addCandlestickSeries({
vecId: "ohlc",
name: "Price",
unit: topUnit,
});
break;
}
case "Line": {
const line = chart.addLineSeries({
vecId: "close",
name: "Price",
unit: topUnit,
color: colors.default,
options: {
priceLineVisible: true,
},
});
}
}
// signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
// if (!latest) return;
// const last = /** @type { CandlestickData | undefined} */ (
// candles.data().at(-1)
// );
// if (!last) return;
// candles?.update({ ...last, close: latest.close });
// });
break;
}
case "Sats": {
switch (topSeriesType) {
case "Candles": {
const candles = chart.addCandlestickSeries({
vecId: "ohlc-in-sats",
name: "Price",
unit: topUnit,
inverse: true,
});
break;
}
case "Line": {
const line = chart.addLineSeries({
vecId: "close-in-sats",
name: "Price",
unit: topUnit,
color: colors.default,
options: {
priceLineVisible: true,
},
});
}
}
break;
}
}
[ const indexes = /** @type {readonly number[]} */ (
{ blueprints: option.top, paneIndex: 0 }, vecIdToIndexes[blueprint.key]
{ blueprints: option.bottom, paneIndex: 1 }, );
].forEach(({ blueprints, paneIndex }) => {
const unit = paneIndex ? bottomUnit : topUnit;
blueprints[unit]?.forEach((blueprint) => { if (indexes.includes(index)) {
const indexes = /** @type {readonly number[]} */ ( switch (blueprint.type) {
vecIdToIndexes[blueprint.key] case "Baseline": {
); seriesList.push(
if (indexes.includes(index)) {
switch (blueprint.type) {
case "Baseline": {
chart.addBaselineSeries({ chart.addBaselineSeries({
vecId: blueprint.key, vecId: blueprint.key,
// color: blueprint.color,
name: blueprint.title, name: blueprint.title,
unit, unit,
defaultActive: blueprint.defaultActive, defaultActive: blueprint.defaultActive,
@@ -244,14 +419,16 @@ export function init({
bottomLineColor: bottomLineColor:
blueprint.color?.() ?? blueprint.colors?.[1](), blueprint.color?.() ?? blueprint.colors?.[1](),
}, },
}); order,
break; })
} );
case "Candlestick": { break;
throw Error("TODO"); }
break; case "Candlestick": {
} throw Error("TODO");
default: }
default:
seriesList.push(
chart.addLineSeries({ chart.addLineSeries({
vecId: blueprint.key, vecId: blueprint.key,
color: blueprint.color, color: blueprint.color,
@@ -260,54 +437,70 @@ export function init({
defaultActive: blueprint.defaultActive, defaultActive: blueprint.defaultActive,
paneIndex, paneIndex,
options: blueprint.options, options: blueprint.options,
}); order,
} })
);
} }
}); }
}); });
chart
.inner()
?.timeScale()
.subscribeVisibleLogicalRangeChange(
utils.debounce((t) => {
if (t) {
from.set(t.from);
to.set(t.to);
}
})
);
firstRun = false;
}); });
}); }
}); );
firstRun = false;
}); });
}); });
} }
/** /**
* @param {Object} args * @param {Object} args
* @param {Elements} args.elements * @param {Accessor<ChartOption>} args.option
* @param {VecIdToIndexes} args.vecIdToIndexes
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils
*/ */
function createIndexSelector({ elements, signals, utils }) { function createIndexSelector({ option, vecIdToIndexes, signals, utils }) {
const choices_ = /** @type {const} */ ([
"timestamp",
"date",
"week",
// "difficulty epoch",
"month",
"quarter",
"year",
// "halving epoch",
"decade",
]);
/** @type {Accessor<typeof choices_>} */
const choices = signals.createMemo(() => {
const o = option();
if (!Object.keys(o.top).length && !Object.keys(o.bottom).length) {
return [...choices_];
}
const rawIndexes = new Set(
[Object.values(o.top), Object.values(o.bottom)]
.flat(2)
.map((blueprint) => vecIdToIndexes[blueprint.key])
.flat()
);
const serializedIndexes = [...rawIndexes].flatMap((index) => {
const c = utils.serde.chartableIndex.serialize(index);
return c ? [c] : [];
});
return /** @type {any} */ (
choices_.filter((choice) => serializedIndexes.includes(choice))
);
});
const { field, selected } = utils.dom.createHorizontalChoiceField({ const { field, selected } = utils.dom.createHorizontalChoiceField({
defaultValue: "date", defaultValue: "date",
keyPrefix: "charts", keyPrefix,
key: "index", key: "index",
choices: /**@type {const} */ ([ choices,
"timestamp",
"date",
"week",
// "difficulty epoch",
"month",
"quarter",
"year",
// "halving epoch",
"decade",
]),
id: "index", id: "index",
signals, signals,
}); });
@@ -315,28 +508,10 @@ function createIndexSelector({ elements, signals, utils }) {
const fieldset = window.document.createElement("fieldset"); const fieldset = window.document.createElement("fieldset");
fieldset.append(field); fieldset.append(field);
fieldset.dataset.size = "sm"; fieldset.dataset.size = "sm";
elements.charts.append(fieldset);
const index = signals.createMemo( const index = signals.createMemo(() =>
/** @returns {ChartableIndex} */ () => { utils.serde.chartableIndex.deserialize(selected())
switch (selected()) {
case "timestamp":
return /** @satisfies {Height} */ (5);
case "date":
return /** @satisfies {DateIndex} */ (0);
case "week":
return /** @satisfies {WeekIndex} */ (22);
case "month":
return /** @satisfies {MonthIndex} */ (7);
case "quarter":
return /** @satisfies {QuarterIndex} */ (19);
case "year":
return /** @satisfies {YearIndex} */ (23);
case "decade":
return /** @satisfies {DecadeIndex} */ (1);
}
}
); );
return index; return { fieldset, index };
} }
File diff suppressed because it is too large Load Diff
+15 -17
View File
@@ -1,7 +1,7 @@
// @ts-check // @ts-check
/** /**
* @typedef {Height | DateIndex | WeekIndex | DifficultyEpoch | MonthIndex | QuarterIndex | YearIndex | HalvingEpoch | DecadeIndex} ChartableIndex * @typedef {Height | DateIndex | WeekIndex | MonthIndex | QuarterIndex | YearIndex | DecadeIndex} ChartableIndex
*/ */
/** /**
* @template {readonly unknown[]} T * @template {readonly unknown[]} T
@@ -27,7 +27,7 @@
* @property {Color} [color] * @property {Color} [color]
* @property {[Color, Color]} [colors] * @property {[Color, Color]} [colors]
* @property {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options] * @property {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options]
* @property {Accessor<BaselineData[]>} [data] * @property {Accessor<BaselineData<number>[]>} [data]
* @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint * @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint
* *
* @typedef {Object} CandlestickSeriesBlueprintSpecific * @typedef {Object} CandlestickSeriesBlueprintSpecific
@@ -41,7 +41,7 @@
* @property {"Line"} [type] * @property {"Line"} [type]
* @property {Color} [color] * @property {Color} [color]
* @property {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options] * @property {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options]
* @property {Accessor<LineData[]>} [data] * @property {Accessor<LineData<number>[]>} [data]
* @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint * @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint
* *
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint * @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
@@ -1512,7 +1512,9 @@ function createPartialOptions(colors) {
}), }),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
key: `${fixKey(key)}net-realized-profit-and-loss-relative-to-realized-cap`, key: `${fixKey(
key,
)}net-realized-profit-and-loss-relative-to-realized-cap`,
title: useGroupName ? name : "Net", title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
options: { options: {
@@ -1581,7 +1583,9 @@ function createPartialOptions(colors) {
bottom: list.flatMap(({ color, name, key }) => [ bottom: list.flatMap(({ color, name, key }) => [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
key: `${fixKey(key)}adjusted-spent-output-profit-ratio`, key: `${fixKey(
key,
)}adjusted-spent-output-profit-ratio`,
title: useGroupName ? name : "asopr", title: useGroupName ? name : "asopr",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
options: { options: {
@@ -1691,7 +1695,9 @@ function createPartialOptions(colors) {
}), }),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
key: `${fixKey(key)}net-unrealized-profit-and-loss-relative-to-market-cap`, key: `${fixKey(
key,
)}net-unrealized-profit-and-loss-relative-to-market-cap`,
title: useGroupName ? name : "Net", title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
options: { options: {
@@ -1987,7 +1993,7 @@ function createPartialOptions(colors) {
top: [ top: [
createBaseSeries({ createBaseSeries({
key: `${key}-dca-avg-price`, key: `${key}-dca-avg-price`,
name: `dca avg. price`, name: `dca`,
color: colors.orange, color: colors.orange,
}), }),
createBaseSeries({ createBaseSeries({
@@ -2883,17 +2889,9 @@ function createPartialOptions(colors) {
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Env} args.env * @param {Env} args.env
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {WebSockets} args.webSockets
* @param {Signal<string | null>} args.qrcode * @param {Signal<string | null>} args.qrcode
*/ */
export function initOptions({ export function initOptions({ colors, signals, env, utils, qrcode }) {
colors,
signals,
env,
utils,
webSockets,
qrcode,
}) {
const LS_SELECTED_KEY = `selected-id`; const LS_SELECTED_KEY = `selected-id`;
const urlSelected = utils.url.pathnameToSelectedId(); const urlSelected = utils.url.pathnameToSelectedId();
@@ -3106,7 +3104,7 @@ export function initOptions({
summary.append(supCount); summary.append(supCount);
signals.createEffect(childOptionsCount, (childOptionsCount) => { signals.createEffect(childOptionsCount, (childOptionsCount) => {
supCount.innerHTML = childOptionsCount.toLocaleString(); supCount.innerHTML = childOptionsCount.toLocaleString("en-us");
}); });
details.addEventListener("toggle", () => { details.addEventListener("toggle", () => {
+88 -74
View File
@@ -76,7 +76,7 @@ export function init({
input.value = value; input.value = value;
stateValue = value; stateValue = value;
} }
}, }
); );
input.addEventListener("input", () => { input.addEventListener("input", () => {
@@ -139,7 +139,7 @@ export function init({
input.value = value; input.value = value;
stateValue = value; stateValue = value;
} }
}, }
); );
input.addEventListener("change", () => { input.addEventListener("change", () => {
@@ -328,7 +328,7 @@ export function init({
keyPrefix, keyPrefix,
key: "top-up-freq", key: "top-up-freq",
}, },
}, }
), ),
}, },
}, },
@@ -356,7 +356,7 @@ export function init({
keyPrefix, keyPrefix,
key: "swap-freq", key: "swap-freq",
}, },
}, }
), ),
}, },
}, },
@@ -369,7 +369,7 @@ export function init({
keyPrefix, keyPrefix,
key: "interval-start", key: "interval-start",
}, },
}, }
), ),
end: signals.createSignal(/** @type {Date | null} */ (new Date()), { end: signals.createSignal(/** @type {Date | null} */ (new Date()), {
save: { save: {
@@ -391,9 +391,7 @@ export function init({
}; };
parametersElement.append( parametersElement.append(
utils.dom.createHeader({ utils.dom.createHeader("Save in Bitcoin").headerElement
title: "Save in Bitcoin",
}).headerElement,
); );
/** /**
@@ -412,7 +410,9 @@ export function init({
* @param {string} param0.text * @param {string} param0.text
*/ */
function createColoredSpan({ color, text }) { function createColoredSpan({ color, text }) {
return `<span style="color: ${colors[color]()}; font-weight: 500; text-transform: uppercase; return `<span style="color: ${colors[
color
]()}; font-weight: 500; text-transform: uppercase;
font-size: var(--font-size-sm);">${text}</span>`; font-size: var(--font-size-sm);">${text}</span>`;
} }
@@ -431,9 +431,9 @@ export function init({
title: "Initial Dollar Amount", title: "Initial Dollar Amount",
signal: settings.dollars.initial.amount, signal: settings.dollars.initial.amount,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -451,9 +451,9 @@ export function init({
list: frequencies.list, list: frequencies.list,
signal: settings.dollars.topUp.frenquency, signal: settings.dollars.topUp.frenquency,
deep: true, deep: true,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -471,9 +471,9 @@ export function init({
title: "Top Up Dollar Amount", title: "Top Up Dollar Amount",
signal: settings.dollars.topUp.amount, signal: settings.dollars.topUp.amount,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -491,9 +491,9 @@ export function init({
title: "Initial Swap Amount", title: "Initial Swap Amount",
signal: settings.bitcoin.investment.initial, signal: settings.bitcoin.investment.initial,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -510,9 +510,9 @@ export function init({
list: frequencies.list, list: frequencies.list,
signal: settings.bitcoin.investment.frequency, signal: settings.bitcoin.investment.frequency,
deep: true, deep: true,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -530,9 +530,9 @@ export function init({
title: "Bitcoin Recurrent Investment", title: "Bitcoin Recurrent Investment",
signal: settings.bitcoin.investment.recurrent, signal: settings.bitcoin.investment.recurrent,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -549,9 +549,9 @@ export function init({
title: "First Simulation Date", title: "First Simulation Date",
signal: settings.interval.start, signal: settings.interval.start,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -568,9 +568,9 @@ export function init({
title: "Last Simulation Day", title: "Last Simulation Day",
signal: settings.interval.end, signal: settings.interval.end,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -591,9 +591,9 @@ export function init({
step: 0.01, step: 0.01,
signals, signals,
placeholder: "Fees", placeholder: "Fees",
}), })
), ),
}), })
); );
const p1 = window.document.createElement("p"); const p1 = window.document.createElement("p");
@@ -608,102 +608,106 @@ export function init({
const owner = signals.getOwner(); const owner = signals.getOwner();
const totalInvestedAmountData = signals.createSignal( const totalInvestedAmountData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinValueData = signals.createSignal( const bitcoinValueData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinData = signals.createSignal( const bitcoinData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const resultData = signals.createSignal( const resultData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const dollarsLeftData = signals.createSignal( const dollarsLeftData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const totalValueData = signals.createSignal( const totalValueData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const investmentData = signals.createSignal( const investmentData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinAddedData = signals.createSignal( const bitcoinAddedData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const averagePricePaidData = signals.createSignal( const averagePricePaidData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinPriceData = signals.createSignal( const bitcoinPriceData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const buyCountData = signals.createSignal( const buyCountData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const totalFeesPaidData = signals.createSignal( const totalFeesPaidData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const daysCountData = signals.createSignal( const daysCountData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const profitableDaysRatioData = signals.createSignal( const profitableDaysRatioData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const unprofitableDaysRatioData = signals.createSignal( const unprofitableDaysRatioData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const index = () => /** @type {DateIndex} */ (0);
lightweightCharts.createChartElement({ lightweightCharts.createChartElement({
index,
owner,
parent: resultsElement, parent: resultsElement,
signals, signals,
colors, colors,
id: `result`, id: `result`,
fitContentOnResize: true, fitContent: true,
vecsResources, vecsResources,
utils, utils,
elements, elements,
@@ -743,11 +747,13 @@ export function init({
}); });
lightweightCharts.createChartElement({ lightweightCharts.createChartElement({
index,
owner,
parent: resultsElement, parent: resultsElement,
signals, signals,
colors, colors,
id: `bitcoin`, id: `bitcoin`,
fitContentOnResize: true, fitContent: true,
vecsResources, vecsResources,
elements, elements,
utils, utils,
@@ -767,11 +773,13 @@ export function init({
}); });
lightweightCharts.createChartElement({ lightweightCharts.createChartElement({
index,
owner,
parent: resultsElement, parent: resultsElement,
signals, signals,
colors, colors,
id: `average-price`, id: `average-price`,
fitContentOnResize: true, fitContent: true,
vecsResources, vecsResources,
utils, utils,
elements, elements,
@@ -797,15 +805,16 @@ export function init({
}); });
lightweightCharts.createChartElement({ lightweightCharts.createChartElement({
index,
owner,
parent: resultsElement, parent: resultsElement,
signals, signals,
colors, colors,
vecsResources, vecsResources,
id: `return-ratio`, id: `return-ratio`,
fitContentOnResize: true, fitContent: true,
utils, utils,
elements, elements,
config: [ config: [
{ {
unit: "USD", unit: "USD",
@@ -821,11 +830,12 @@ export function init({
}); });
lightweightCharts.createChartElement({ lightweightCharts.createChartElement({
index,
parent: resultsElement, parent: resultsElement,
signals, signals,
colors, colors,
id: `simulation-profitability-ratios`, id: `simulation-profitability-ratios`,
fitContentOnResize: true, fitContent: true,
vecsResources, vecsResources,
utils, utils,
elements, elements,
@@ -925,8 +935,7 @@ export function init({
let lastSatsAdded = 0; let lastSatsAdded = 0;
range.forEach((date, index) => { range.forEach((date, index) => {
const year = date.getUTCFullYear(); const time = date.valueOf() / 1000;
const time = utils.date.toString(date);
if (topUpFrequency.isTriggerDay(date)) { if (topUpFrequency.isTriggerDay(date)) {
dollars += topUpAmount; dollars += topUpAmount;
@@ -1087,16 +1096,21 @@ export function init({
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`; p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
const dayDiff = Math.floor( const dayDiff = Math.floor(
utils.date.differenceBetween(new Date(), lastInvestDay), utils.date.differenceBetween(new Date(), lastInvestDay)
); );
const serDailyInvestment = c("emerald", fd(dailyInvestment)); const serDailyInvestment = c("emerald", fd(dailyInvestment));
const setLastSatsAdded = c("orange", f(lastSatsAdded)); const setLastSatsAdded = c("orange", f(lastSatsAdded));
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`; p2.innerHTML = `You would've last bought ${c(
"blue",
dayDiff
? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago`
: "today"
)} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio)); const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
const serUnprofitableDaysRatio = c( const serUnprofitableDaysRatio = c(
"red", "red",
fp(unprofitableDaysRatio), fp(unprofitableDaysRatio)
); );
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`; p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
@@ -1106,7 +1120,7 @@ export function init({
(lowestAnnual4YReturn) => { (lowestAnnual4YReturn) => {
const serLowestAnnual4YReturn = c( const serLowestAnnual4YReturn = c(
"cyan", "cyan",
`${fp(lowestAnnual4YReturn)}`, `${fp(lowestAnnual4YReturn)}`
); );
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn; const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
@@ -1122,22 +1136,22 @@ export function init({
const bitcoinValueAfter4y = bitcoinValueReturn(4); const bitcoinValueAfter4y = bitcoinValueReturn(4);
const serBitcoinValueAfter4y = c( const serBitcoinValueAfter4y = c(
"purple", "purple",
fd(bitcoinValueAfter4y), fd(bitcoinValueAfter4y)
); );
const bitcoinValueAfter10y = bitcoinValueReturn(10); const bitcoinValueAfter10y = bitcoinValueReturn(10);
const serBitcoinValueAfter10y = c( const serBitcoinValueAfter10y = c(
"fuchsia", "fuchsia",
fd(bitcoinValueAfter10y), fd(bitcoinValueAfter10y)
); );
const bitcoinValueAfter21y = bitcoinValueReturn(21); const bitcoinValueAfter21y = bitcoinValueReturn(21);
const serBitcoinValueAfter21y = c( const serBitcoinValueAfter21y = c(
"pink", "pink",
fd(bitcoinValueAfter21y), fd(bitcoinValueAfter21y)
); );
/** @param {number} v */ /** @param {number} v */
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`; p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
}, }
); );
totalInvestedAmountData.set((a) => a); totalInvestedAmountData.set((a) => a);
@@ -1155,7 +1169,7 @@ export function init({
daysCountData.set((a) => a); daysCountData.set((a) => a);
profitableDaysRatioData.set((a) => a); profitableDaysRatioData.set((a) => a);
unprofitableDaysRatioData.set((a) => a); unprofitableDaysRatioData.set((a) => a);
}, }
); );
}); });
}); });
+25 -24
View File
@@ -29,11 +29,11 @@ function createTable({
keyPrefix: "table", keyPrefix: "table",
key: "index", key: "index",
}, },
} },
) )
); );
const index = signals.createMemo(() => const index = signals.createMemo(() =>
serializedIndexToIndex(serializedIndex()) serializedIndexToIndex(serializedIndex()),
); );
const table = window.document.createElement("table"); const table = window.document.createElement("table");
@@ -73,7 +73,7 @@ function createTable({
table.append(tbody); table.append(tbody);
const rowElements = signals.createSignal( const rowElements = signals.createSignal(
/** @type {HTMLTableRowElement[]} */ ([]) /** @type {HTMLTableRowElement[]} */ ([]),
); );
/** /**
@@ -197,7 +197,7 @@ function createTable({
}); });
signals.createEffect(vecIdOption, (vecIdOption) => { signals.createEffect(vecIdOption, (vecIdOption) => {
select.style.width = `${30 + 8.5 * vecIdOption.name.length}px`; select.style.width = `${21 + 7.25 * vecIdOption.name.length}px`;
}); });
if (_colIndex === columns().length) { if (_colIndex === columns().length) {
@@ -305,25 +305,28 @@ function createTable({
return l; return l;
}); });
signals.createEffect(vec.fetched[fetchedKey].vec, (vec) => { signals.createEffect(
if (!vec) return; () => vec.fetched().get(fetchedKey)?.vec(),
(vec) => {
if (!vec?.length) return;
const thIndex = colIndex() + 1; const thIndex = colIndex() + 1;
for (let i = 0; i < rowElements.length; i++) { for (let i = 0; i < rowElements.length; i++) {
const iRev = vec.length - 1 - i; const iRev = vec.length - 1 - i;
const value = vec[iRev]; const value = vec[iRev];
// @ts-ignore // @ts-ignore
rowElements[i].childNodes[thIndex].innerHTML = rowElements[i].childNodes[thIndex].innerHTML =
serializeValue({ serializeValue({
value, value,
unit, unit,
}); });
} }
}); },
);
return () => vecId; return () => vecId;
} },
); );
}); });
}); });
@@ -368,9 +371,7 @@ export function init({
vecIdToIndexes, vecIdToIndexes,
}) { }) {
const parent = elements.table; const parent = elements.table;
const { headerElement } = utils.dom.createHeader({ const { headerElement } = utils.dom.createHeader("Table");
title: "Table",
});
parent.append(headerElement); parent.append(headerElement);
const div = window.document.createElement("div"); const div = window.document.createElement("div");
@@ -394,7 +395,7 @@ export function init({
}, },
inside: span, inside: span,
title: "Click or tap to add a column to the table", title: "Click or tap to add a column to the table",
}) }),
); );
} }
@@ -499,7 +500,7 @@ function createIndexToVecIds(vecIdToIndexes) {
}); });
return arr; return arr;
}, },
/** @type {VecId[][]} */ (new Array(24)) /** @type {VecId[][]} */ (new Array(24)),
); );
indexToVecIds.forEach((arr) => { indexToVecIds.forEach((arr) => {
arr.sort(); arr.sort();
+3 -1
View File
@@ -2,7 +2,7 @@
// File auto-generated, any modifications will be overwritten // File auto-generated, any modifications will be overwritten
// //
export const VERSION = "v0.0.47"; export const VERSION = "v0.0.55";
/** @typedef {0} DateIndex */ /** @typedef {0} DateIndex */
/** @typedef {1} DecadeIndex */ /** @typedef {1} DecadeIndex */
@@ -1251,6 +1251,7 @@ export function createVecIdToIndexes() {
"fee-75p": [5], "fee-75p": [5],
"fee-90p": [5], "fee-90p": [5],
"fee-average": [0, 1, 2, 5, 7, 19, 22, 23], "fee-average": [0, 1, 2, 5, 7, 19, 22, 23],
"fee-in-btc": [20],
"fee-in-btc-10p": [5], "fee-in-btc-10p": [5],
"fee-in-btc-25p": [5], "fee-in-btc-25p": [5],
"fee-in-btc-75p": [5], "fee-in-btc-75p": [5],
@@ -1260,6 +1261,7 @@ export function createVecIdToIndexes() {
"fee-in-btc-median": [5], "fee-in-btc-median": [5],
"fee-in-btc-min": [0, 1, 2, 5, 7, 19, 22, 23], "fee-in-btc-min": [0, 1, 2, 5, 7, 19, 22, 23],
"fee-in-btc-sum": [0, 1, 2, 5, 7, 19, 22, 23], "fee-in-btc-sum": [0, 1, 2, 5, 7, 19, 22, 23],
"fee-in-usd": [20],
"fee-in-usd-10p": [5], "fee-in-usd-10p": [5],
"fee-in-usd-25p": [5], "fee-in-usd-25p": [5],
"fee-in-usd-75p": [5], "fee-in-usd-75p": [5],
+10 -12
View File
@@ -10,17 +10,15 @@ sw.addEventListener("install", (event) => {
sw.addEventListener("activate", (event) => { sw.addEventListener("activate", (event) => {
console.log("sw: active"); console.log("sw: active");
event.waitUntil(sw.clients.claim()); sw.clients.claim();
event.waitUntil( event.waitUntil(
caches caches
.keys() .keys()
.then((keys) => .then((keys) =>
Promise.all( Promise.all(
keys keys.filter((key) => key !== "api").map((key) => caches.delete(key)),
.filter((key) => key !== CACHE_NAME && key !== "api") ),
.map((key) => caches.delete(key)) ),
)
)
); );
}); });
@@ -44,6 +42,8 @@ sw.addEventListener("fetch", (event) => {
return; // let the browser handle it return; // let the browser handle it
} }
const cache = caches.open(CACHE_NAME);
// 2) NAVIGATION: networkfirst on your shell // 2) NAVIGATION: networkfirst on your shell
if (req.mode === "navigate") { if (req.mode === "navigate") {
event.respondWith( event.respondWith(
@@ -54,16 +54,14 @@ sw.addEventListener("fetch", (event) => {
if (response.ok || response.status === 304) { if (response.ok || response.status === 304) {
if (response.ok) { if (response.ok) {
const clone = response.clone(); const clone = response.clone();
caches cache.then((cache) => cache.put("/index.html", clone));
.open(CACHE_NAME)
.then((cache) => cache.put("/index.html", clone));
} }
return response; return response;
} }
throw new Error("Non-2xx on shell"); throw new Error("Non-2xx on shell");
}) })
// On any failure, fall back to the cached shell // On any failure, fall back to the cached shell
.catch(indexHTMLOrOffline) .catch(indexHTMLOrOffline),
); );
return; return;
} }
@@ -74,7 +72,7 @@ sw.addEventListener("fetch", (event) => {
.then((response) => { .then((response) => {
if (response.ok) { if (response.ok) {
const clone = response.clone(); const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(req, clone)); cache.then((cache) => cache.put(req, clone));
} }
return response; return response;
}) })
@@ -86,6 +84,6 @@ sw.addEventListener("fetch", (event) => {
}) })
.catch(indexHTMLOrOffline); .catch(indexHTMLOrOffline);
}) })
.catch(indexHTMLOrOffline) .catch(indexHTMLOrOffline),
); );
}); });
+9 -7
View File
@@ -7,7 +7,9 @@
> div { > div {
display: flex; display: flex;
font-size: var(--font-size-sm); font-size: var(--font-size-xs);
line--line-height: var(--line-height-xs);
font-weight: 450;
margin-left: var(--negative-main-padding); margin-left: var(--negative-main-padding);
margin-right: var(--negative-main-padding); margin-right: var(--negative-main-padding);
@@ -36,7 +38,7 @@
border-bottom: 1px; border-bottom: 1px;
border-color: var(--off-color); border-color: var(--off-color);
border-style: dashed !important; border-style: dashed !important;
padding: 0.25rem 1rem; padding: 0.25rem 0.75rem;
} }
td { td {
@@ -84,7 +86,7 @@
> button { > button {
padding: 0 0.25rem; padding: 0 0.25rem;
margin: 0 -0.25rem; margin: 0 -0.25rem;
font-size: 1rem; font-size: 0.75rem;
line-height: 0; line-height: 0;
} }
} }
@@ -109,9 +111,10 @@
} }
} }
/* select { select {
width: 100%; margin-right: -4px;
} */ /* width: 100%; */
}
tbody { tbody {
text-align: right; text-align: right;
@@ -126,7 +129,6 @@
position: relative; position: relative;
border-top-width: 1px; border-top-width: 1px;
width: 100%; width: 100%;
/* border-right-width: 1px; */
border-bottom-width: 1px; border-bottom-width: 1px;
border-style: dashed !important; border-style: dashed !important;
+1
View File
@@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true,
"checkJs": true, "checkJs": true,
"strict": true, "strict": true,
"target": "ESNext", "target": "ESNext",