feat(testnet): L2 sequencer and archiver example (#2001)
Co-authored-by: Antonio Antonino <antonio@status.im> Co-authored-by: Petar Radovic <petar.radovic@gmail.com>
4
.gitignore
vendored
@ -28,3 +28,7 @@ zk/**/bin/*
|
||||
|
||||
# C headers
|
||||
nomos-c/libnomos.h
|
||||
|
||||
# Demo sequencer related ignores
|
||||
node_modules
|
||||
*.database
|
||||
321
Cargo.lock
generated
@ -1147,7 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
@ -1156,7 +1156,7 @@ dependencies = [
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
@ -1174,6 +1174,36 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
dependencies = [
|
||||
"axum-core 0.5.6",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit 0.8.4",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
@ -1195,6 +1225,24 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.4.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.2.1"
|
||||
@ -1263,9 +1311,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.2"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bcder"
|
||||
@ -1639,7 +1687,7 @@ checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"heck 0.5.0",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1647,14 +1695,14 @@ dependencies = [
|
||||
"serde_json",
|
||||
"syn 2.0.114",
|
||||
"tempfile",
|
||||
"toml 0.9.10+spec-1.1.0",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.51"
|
||||
version = "1.2.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
||||
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@ -1724,7 +1772,7 @@ dependencies = [
|
||||
name = "cfgsync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"clap",
|
||||
"nomos-core 0.1.0",
|
||||
"nomos-da-network-core 0.1.0",
|
||||
@ -1748,10 +1796,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cfgsync"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"clap",
|
||||
"groth16 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
"hex",
|
||||
@ -2182,6 +2230,7 @@ name = "common-http-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"broadcast-service 0.1.0",
|
||||
"chain-service 0.1.0",
|
||||
"futures",
|
||||
"nomos-core 0.1.0",
|
||||
"nomos-da-messages 0.1.0",
|
||||
@ -2677,6 +2726,7 @@ dependencies = [
|
||||
"humantime",
|
||||
"inventory",
|
||||
"itertools 0.14.0",
|
||||
"junit-report",
|
||||
"linked-hash-map",
|
||||
"pin-project",
|
||||
"ref-cast",
|
||||
@ -2718,7 +2768,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cucumber_ext"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"cucumber",
|
||||
"testing-framework-core",
|
||||
@ -2842,15 +2892,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding-macro"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d"
|
||||
checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"data-encoding-macro-internal",
|
||||
@ -2858,9 +2908,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding-macro-internal"
|
||||
version = "0.1.16"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
|
||||
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"syn 2.0.114",
|
||||
@ -2901,6 +2951,29 @@ dependencies = [
|
||||
"windows 0.32.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "demo-sequencer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"common-http-client 0.1.0",
|
||||
"hex",
|
||||
"key-management-system-service 0.1.0",
|
||||
"nomos-core 0.1.0",
|
||||
"owo-colors",
|
||||
"rand 0.8.5",
|
||||
"redb 2.6.3",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.6.8",
|
||||
"tracing",
|
||||
"tracing-subscriber 0.3.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@ -2947,6 +3020,17 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-getters"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2c35ab6e03642397cdda1dd58abbc05d418aef8e36297f336d5aba060fe8df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
@ -3675,9 +3759,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
||||
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
|
||||
|
||||
[[package]]
|
||||
name = "findshlibs"
|
||||
@ -3931,9 +4015,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@ -4142,7 +4226,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@ -4161,7 +4245,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.4.0",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@ -4878,9 +4962,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.1"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
@ -4904,7 +4988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"is-terminal",
|
||||
"itoa",
|
||||
"log",
|
||||
@ -5148,6 +5232,18 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "junit-report"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c3a3342e6720a82d7d179f380e9841b73a1dd49344e33959fdfe571ce56b55"
|
||||
dependencies = [
|
||||
"derive-getters",
|
||||
"quick-xml 0.31.0",
|
||||
"strip-ansi-escapes",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jzon"
|
||||
version = "0.12.5"
|
||||
@ -5397,9 +5493,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.179"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@ -5437,7 +5533,7 @@ dependencies = [
|
||||
"either",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libp2p-allow-block-list",
|
||||
"libp2p-autonat",
|
||||
"libp2p-connection-limits",
|
||||
@ -5564,7 +5660,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"hashlink",
|
||||
"hex_fmt",
|
||||
"libp2p-core",
|
||||
@ -5973,6 +6069,32 @@ dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-blockchain-archiver"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"axum 0.8.8",
|
||||
"broadcast-service 0.1.0",
|
||||
"clap",
|
||||
"common-http-client 0.1.0",
|
||||
"demo-sequencer",
|
||||
"futures",
|
||||
"hex",
|
||||
"nomos-core 0.1.0",
|
||||
"owo-colors",
|
||||
"redb 3.1.0",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower-http 0.6.8",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loki-api"
|
||||
version = "0.1.3"
|
||||
@ -6050,6 +6172,12 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.10"
|
||||
@ -6550,7 +6678,7 @@ name = "nomos-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"broadcast-service 0.1.0",
|
||||
"bytes",
|
||||
"chain-service 0.1.0",
|
||||
@ -7092,7 +7220,7 @@ dependencies = [
|
||||
"cached",
|
||||
"fixed",
|
||||
"futures",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"key-management-system-keys 0.1.0",
|
||||
"kzgrs 0.1.0",
|
||||
"kzgrs-backend 0.1.0",
|
||||
@ -7123,7 +7251,7 @@ dependencies = [
|
||||
"cached",
|
||||
"fixed",
|
||||
"futures",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"kzgrs-backend 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
"libp2p",
|
||||
"libp2p-stream",
|
||||
@ -7330,7 +7458,7 @@ name = "nomos-executor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"broadcast-service 0.1.0",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
@ -7369,7 +7497,7 @@ version = "0.1.0"
|
||||
source = "git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917#1fce2dc3f482c16361316eb2a1b6ccd1206aa917"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"broadcast-service 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
@ -7406,7 +7534,7 @@ dependencies = [
|
||||
name = "nomos-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"governor",
|
||||
"key-management-system-keys 0.1.0",
|
||||
"nomos-core 0.1.0",
|
||||
@ -7424,7 +7552,7 @@ name = "nomos-http-api-common"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917#1fce2dc3f482c16361316eb2a1b6ccd1206aa917"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"governor",
|
||||
"key-management-system-keys 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
"nomos-core 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
@ -7586,7 +7714,7 @@ name = "nomos-node"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"broadcast-service 0.1.0",
|
||||
"chain-leader 0.1.0",
|
||||
"chain-network 0.1.0",
|
||||
@ -7652,7 +7780,7 @@ version = "0.1.0"
|
||||
source = "git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917#1fce2dc3f482c16361316eb2a1b6ccd1206aa917"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"broadcast-service 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
"chain-leader 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
"chain-network 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
@ -8222,7 +8350,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"memchr",
|
||||
"ruzstd",
|
||||
]
|
||||
@ -8492,7 +8620,7 @@ dependencies = [
|
||||
"cbc",
|
||||
"cipher",
|
||||
"des",
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"hmac",
|
||||
"lazy_static",
|
||||
"rc2",
|
||||
@ -8919,7 +9047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"quick-xml 0.38.4",
|
||||
"serde",
|
||||
"time",
|
||||
@ -9400,7 +9528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"log",
|
||||
"protobuf",
|
||||
"protobuf-support",
|
||||
@ -9488,6 +9616,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.4"
|
||||
@ -9647,7 +9784,7 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -9823,6 +9960,24 @@ dependencies = [
|
||||
"yasna",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redb"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eca1e9d98d5a7e9002d0013e18d5a9b000aee942eb134883a82f06ebffb6c01"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redb"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@ -9847,7 +10002,7 @@ version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@ -9858,7 +10013,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@ -10044,7 +10199,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
@ -10794,7 +10949,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.2.0",
|
||||
"serde_core",
|
||||
@ -10821,7 +10976,7 @@ version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@ -11223,6 +11378,15 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
|
||||
dependencies = [
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@ -11577,7 +11741,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.9.10+spec-1.1.0",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"url",
|
||||
"urlpattern",
|
||||
"uuid",
|
||||
@ -11662,7 +11826,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "testing-framework-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"blst",
|
||||
"chain-leader 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
@ -11704,7 +11868,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "testing-framework-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -11743,12 +11907,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "testing-framework-env"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
|
||||
[[package]]
|
||||
name = "testing-framework-runner-compose"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -11771,7 +11935,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "testing-framework-runner-local"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"testing-framework-core",
|
||||
@ -11782,7 +11946,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "testing-framework-workflows"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#0576f58a19c0629d49124d91d0589bf69ba24910"
|
||||
source = "git+https://github.com/logos-blockchain/logos-blockchain-testing.git?branch=master#1b336c2c080715b0873bdb5b36bd526dd0e74baf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chain-service 0.1.0 (git+https://github.com/logos-co/nomos-node.git?rev=1fce2dc3f482c16361316eb2a1b6ccd1206aa917)",
|
||||
@ -12114,11 +12278,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.10+spec-1.1.0"
|
||||
version = "0.9.11+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
|
||||
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.4",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
@ -12151,7 +12315,7 @@ version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
@ -12165,7 +12329,7 @@ version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
@ -12200,7 +12364,7 @@ checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"h2 0.4.13",
|
||||
@ -12312,7 +12476,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3790eac6ad3fb8d9d96c2b040ae06e2517aa24b067545d1078b96ae72f7bb9a7"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"forwarded-header-value",
|
||||
"governor",
|
||||
"http 1.4.0",
|
||||
@ -12872,7 +13036,7 @@ version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
|
||||
dependencies = [
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa-gen",
|
||||
@ -12896,7 +13060,7 @@ version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943e0ff606c6d57d410fd5663a4d7c074ab2c5f14ab903b9514565e59fa1189e"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"mime_guess",
|
||||
"regex",
|
||||
"reqwest 0.12.28",
|
||||
@ -12993,6 +13157,15 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@ -13955,18 +14128,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.32"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56"
|
||||
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.32"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90"
|
||||
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -14070,7 +14243,7 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"num_enum",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@ -14084,7 +14257,7 @@ dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"indexmap 2.12.1",
|
||||
"indexmap 2.13.0",
|
||||
"memchr",
|
||||
"zopfli",
|
||||
]
|
||||
@ -14140,9 +14313,9 @@ checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.12"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
|
||||
checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
|
||||
@ -62,6 +62,8 @@ members = [
|
||||
"nomos-tracing",
|
||||
"nomos-utils",
|
||||
"testnet/cfgsync",
|
||||
"testnet/l2-sequencer-archival-demo/archiver",
|
||||
"testnet/l2-sequencer-archival-demo/sequencer",
|
||||
"tests",
|
||||
"utxotree",
|
||||
"wallet",
|
||||
@ -81,6 +83,7 @@ resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Internal
|
||||
archiver = { default-features = false, path = "./testnet/l2-sequencer-archival-demo/archiver" }
|
||||
broadcast-service = { default-features = false, path = "./nomos-services/chain/broadcast-service" }
|
||||
bundler = { default-features = false, path = "./nomos-bundler" }
|
||||
cfgsync = { default-features = false, path = "./testnet/cfgsync" }
|
||||
@ -94,6 +97,7 @@ circuits-verifier = { default-features = false, path = "./zk/circuit
|
||||
common-http-client = { default-features = false, path = "./nodes/nomos-node/http-client" }
|
||||
cryptarchia-engine = { default-features = false, path = "./consensus/cryptarchia-engine" }
|
||||
cryptarchia-sync = { default-features = false, path = "./consensus/cryptarchia-sync" }
|
||||
demo-sequencer = { default-features = false, path = "./testnet/l2-sequencer-archival-demo/sequencer" }
|
||||
executor-http-client = { default-features = false, path = "./nodes/nomos-executor/http-client" }
|
||||
groth16 = { default-features = false, path = "./zk/groth16" }
|
||||
key-management-system-keys = { default-features = false, path = "./nomos-kms/keys" }
|
||||
|
||||
@ -125,3 +125,40 @@ services:
|
||||
- "4317:4317" # otlp grpc
|
||||
depends_on:
|
||||
- tempo-init
|
||||
|
||||
# L2 Sequencer Demo Services
|
||||
l2-nginx:
|
||||
container_name: l2_nginx
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain:testnet
|
||||
ports:
|
||||
- "8200:80"
|
||||
depends_on:
|
||||
- l2-sequencer
|
||||
- l2-archiver
|
||||
entrypoint: ["nginx", "-g", "daemon off;"]
|
||||
|
||||
l2-sequencer:
|
||||
container_name: l2_sequencer
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain:testnet
|
||||
environment:
|
||||
- SEQUENCER_LISTEN_ADDR=0.0.0.0:8080
|
||||
- SEQUENCER_NODE_ENDPOINT=http://nomos-node-0:18080
|
||||
- SEQUENCER_DB_PATH=/data/sequencer.db
|
||||
- SEQUENCER_SIGNING_KEY_PATH=/data/sequencer.key
|
||||
- SEQUENCER_CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000001
|
||||
- SEQUENCER_INITIAL_BALANCE=1000
|
||||
volumes:
|
||||
- l2-sequencer-data:/data
|
||||
entrypoint: ["/usr/bin/demo-sequencer"]
|
||||
|
||||
l2-archiver:
|
||||
container_name: l2_archiver
|
||||
image: ghcr.io/logos-blockchain/logos-blockchain:testnet
|
||||
environment:
|
||||
- TESTNET_ENDPOINT=http://nomos-node-1:18080
|
||||
- CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000001
|
||||
- TOKEN_NAME=MEM
|
||||
entrypoint: ["/usr/bin/logos-blockchain-archiver"]
|
||||
|
||||
volumes:
|
||||
l2-sequencer-data:
|
||||
|
||||
@ -14,11 +14,12 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
broadcast-service = { workspace = true }
|
||||
chain-service = { workspace = true }
|
||||
futures = { default-features = false, version = "0.3.31" }
|
||||
nomos-core = { workspace = true }
|
||||
nomos-da-messages = { workspace = true }
|
||||
nomos-http-api-common = { workspace = true }
|
||||
reqwest = { features = ["json", "stream"], workspace = true }
|
||||
reqwest = { features = ["json", "rustls-tls", "stream"], workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { default-features = false, version = "1.0.140" }
|
||||
thiserror = "1.0"
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
use std::{collections::HashSet, fmt::Debug, hash::Hash, sync::Arc};
|
||||
|
||||
use broadcast_service::BlockInfo;
|
||||
use chain_service::CryptarchiaInfo;
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use nomos_core::da::blob::Share;
|
||||
use nomos_core::{block::Block, da::blob::Share, header::HeaderId, mantle::SignedMantleTx};
|
||||
use nomos_da_messages::http::da::{
|
||||
DASharesCommitmentsRequest, DaSamplingRequest, GetSharesRequest,
|
||||
};
|
||||
use nomos_http_api_common::paths::{
|
||||
CRYPTARCHIA_LIB_STREAM, DA_GET_LIGHT_SHARE, DA_GET_SHARES, DA_GET_STORAGE_SHARES_COMMITMENTS,
|
||||
MEMPOOL_ADD_TX,
|
||||
CRYPTARCHIA_INFO, CRYPTARCHIA_LIB_STREAM, DA_GET_LIGHT_SHARE, DA_GET_SHARES,
|
||||
DA_GET_STORAGE_SHARES_COMMITMENTS, MEMPOOL_ADD_TX, STORAGE_BLOCK,
|
||||
};
|
||||
use reqwest::{Client, ClientBuilder, RequestBuilder, StatusCode, Url};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
@ -138,6 +139,20 @@ impl CommonHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_block_by_id<HeaderId>(
|
||||
&self,
|
||||
base_url: Url,
|
||||
header_id: HeaderId,
|
||||
) -> Result<Option<Block<SignedMantleTx>>, Error>
|
||||
where
|
||||
HeaderId: Serialize + Send + Sync,
|
||||
{
|
||||
let request_url = base_url
|
||||
.join(STORAGE_BLOCK.trim_start_matches('/'))
|
||||
.map_err(Error::Url)?;
|
||||
self.post(request_url, &header_id).await
|
||||
}
|
||||
|
||||
/// Get the commitments for a Blob
|
||||
pub async fn get_storage_commitments<S>(
|
||||
&self,
|
||||
@ -226,4 +241,24 @@ impl CommonHttpClient {
|
||||
.map_err(Error::Url)?;
|
||||
self.post(request_url, &transaction).await
|
||||
}
|
||||
|
||||
/// Get consensus info (tip, height, etc.)
|
||||
pub async fn consensus_info(&self, base_url: Url) -> Result<CryptarchiaInfo, Error> {
|
||||
let request_url = base_url
|
||||
.join(CRYPTARCHIA_INFO.trim_start_matches('/'))
|
||||
.map_err(Error::Url)?;
|
||||
self.get::<(), CryptarchiaInfo>(request_url, None).await
|
||||
}
|
||||
|
||||
/// Get a block by its header ID
|
||||
pub async fn get_block(
|
||||
&self,
|
||||
base_url: Url,
|
||||
header_id: HeaderId,
|
||||
) -> Result<Option<Block<SignedMantleTx>>, Error> {
|
||||
let request_url = base_url
|
||||
.join(STORAGE_BLOCK.trim_start_matches('/'))
|
||||
.map_err(Error::Url)?;
|
||||
self.post(request_url, &header_id).await
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,6 +153,12 @@ impl TryFrom<&[u8]> for HeaderId {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for HeaderId {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for ContentId {
|
||||
fn from(id: [u8; 32]) -> Self {
|
||||
Self(id)
|
||||
|
||||
BIN
restore.dat
Normal file
@ -7,57 +7,63 @@ ARG VERSION=v0.3.1
|
||||
# ===========================
|
||||
# BUILD IMAGE
|
||||
# ===========================
|
||||
|
||||
FROM rust:1.92.0-slim-bookworm AS builder
|
||||
|
||||
ARG VERSION
|
||||
|
||||
LABEL maintainer="augustinas@status.im" \
|
||||
source="https://github.com/logos-co/nomos-node" \
|
||||
description="Nomos testnet build image"
|
||||
|
||||
WORKDIR /nomos
|
||||
COPY . .
|
||||
|
||||
# Install dependencies needed for building RocksDB.
|
||||
RUN apt-get update && apt-get install -yq \
|
||||
git gcc g++ clang libssl-dev pkg-config ca-certificates curl
|
||||
git gcc g++ clang libssl-dev pkg-config ca-certificates curl unzip
|
||||
|
||||
RUN chmod +x scripts/setup-nomos-circuits.sh && \
|
||||
scripts/setup-nomos-circuits.sh "$VERSION" "/opt/circuits"
|
||||
# Install Bun for L2 demo frontend.
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
ENV PATH="/root/.bun/bin:$PATH"
|
||||
|
||||
# Build Frontend Webapp
|
||||
WORKDIR /nomos/testnet/l2-sequencer-archival-demo/webapp
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN VITE_SEQUENCER_URL=/api/sequencer VITE_ARCHIVER_URL=/api/archiver bun run build
|
||||
|
||||
# Build Rust Binaries & Circuits
|
||||
WORKDIR /nomos
|
||||
ENV NOMOS_CIRCUITS=/opt/circuits
|
||||
|
||||
RUN scripts/setup-nomos-circuits.sh "$VERSION" "$NOMOS_CIRCUITS"
|
||||
RUN cargo build --locked --release --all-features
|
||||
|
||||
# ===========================
|
||||
# NODE IMAGE
|
||||
# ===========================
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG VERSION
|
||||
|
||||
LABEL maintainer="augustinas@status.im" \
|
||||
source="https://github.com/logos-co/nomos-node" \
|
||||
description="Nomos node image"
|
||||
description="Logos Blockchain node and other binaries image"
|
||||
|
||||
RUN apt-get update && apt-get install -yq \
|
||||
libstdc++6 \
|
||||
libssl3 \
|
||||
ca-certificates \
|
||||
libstdc++6 libssl3 ca-certificates nginx \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Circuits
|
||||
COPY --from=builder /opt/circuits /opt/circuits
|
||||
|
||||
COPY --from=builder /nomos/target/release/nomos-node /usr/bin/nomos-node
|
||||
COPY --from=builder /nomos/target/release/nomos-executor /usr/bin/nomos-executor
|
||||
COPY --from=builder /nomos/target/release/nomos-cli /usr/bin/nomos-cli
|
||||
COPY --from=builder /nomos/target/release/cfgsync-server /usr/bin/cfgsync-server
|
||||
COPY --from=builder /nomos/target/release/cfgsync-client /usr/bin/cfgsync-client
|
||||
|
||||
ENV NOMOS_CIRCUITS=/opt/circuits
|
||||
|
||||
EXPOSE 3000 8080 9000 60000
|
||||
# Copy Binaries
|
||||
COPY --from=builder /nomos/target/release/nomos-node /usr/bin/
|
||||
COPY --from=builder /nomos/target/release/nomos-executor /usr/bin/
|
||||
COPY --from=builder /nomos/target/release/nomos-cli /usr/bin/
|
||||
COPY --from=builder /nomos/target/release/cfgsync-server /usr/bin/
|
||||
COPY --from=builder /nomos/target/release/cfgsync-client /usr/bin/
|
||||
COPY --from=builder /nomos/target/release/demo-sequencer /usr/bin/
|
||||
COPY --from=builder /nomos/target/release/logos-blockchain-archiver /usr/bin/
|
||||
|
||||
# Copy Frontend & Nginx config
|
||||
COPY --from=builder /nomos/testnet/l2-sequencer-archival-demo/webapp/dist /var/www/l2-demo
|
||||
COPY --from=builder /nomos/testnet/l2-sequencer-archival-demo/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
EXPOSE 80 3000 8080 8090 9000 18080 60000
|
||||
ENTRYPOINT ["/usr/bin/nomos-node"]
|
||||
|
||||
15
testnet/l2-sequencer-archival-demo/.env.example
Normal file
@ -0,0 +1,15 @@
|
||||
# Sequencer node endpoint (for submitting transactions)
|
||||
SEQUENCER_NODE_ENDPOINT=https://testnet.nomos.tech/node/1/
|
||||
SEQUENCER_NODE_USERNAME=
|
||||
SEQUENCER_NODE_PASSWORD=
|
||||
|
||||
# Archiver node endpoint (for reading blocks - can be different node)
|
||||
ARCHIVER_NODE_ENDPOINT=https://testnet.nomos.tech/node/2/
|
||||
ARCHIVER_NODE_USERNAME=
|
||||
ARCHIVER_NODE_PASSWORD=
|
||||
|
||||
# Channel ID (64 hex chars) - must be same for both
|
||||
CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000011
|
||||
|
||||
# Token name for display
|
||||
TOKEN_NAME=MEM
|
||||
9
testnet/l2-sequencer-archival-demo/.env.example-local
Normal file
@ -0,0 +1,9 @@
|
||||
# L2 Demo - Local Development Environment
|
||||
# Usage: ./run-local.sh --env-file ~/Eng/offsite-sequencer-env/.env-local
|
||||
|
||||
SEQUENCER_NODE_ENDPOINT=https://testnet.nomos.tech/node/2/
|
||||
ARCHIVER_NODE_ENDPOINT=https://testnet.nomos.tech/node/2/
|
||||
|
||||
TOKEN_NAME=MEM
|
||||
TESTNET_USERNAME=
|
||||
TESTNET_PASSWORD=
|
||||
102
testnet/l2-sequencer-archival-demo/README.md
Normal file
@ -0,0 +1,102 @@
|
||||
# Logos L2 Sequencer & Archival Demo
|
||||
|
||||
This directory contains a reference implementation of a l2 solution using the Logos Blockchain as a Settlement layer. It consists of three primary components working in tandem to provide fast L2 transactions with L1 security.
|
||||
|
||||
## System Architecture
|
||||
|
||||
The demo follows a classic rollup-style architecture where the Sequencer handles execution, and the Archiver handles state derivation from L1 data.
|
||||
|
||||
1. **L2 Sequencer**: The entry point for users. It accepts transactions, maintains a local mempool, batches them into L2 blocks, and "inscribes" them to a specific **Channel ID** on the Logos L1.
|
||||
2. **Logos L1**: Acts as the immutable ledger. It doesn't know the "rules" of the L2; it simply stores the L2 data in a verifiable sequence.
|
||||
3. **Archiver**: Watches the Logos L1 stream. It pulls L2 data from the designated channel, validates the transactions against L2 state rules (e.g., balance checks), and provides a verified API for the frontend.
|
||||
4. **Frontend**: A simple dashboard to visualize transfers, account balances, and real-time block production.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
Each component is a standalone service that can be run independently or via Docker.
|
||||
|
||||
| Component | Directory | Responsibility |
|
||||
| --- | --- | --- |
|
||||
| **Sequencer** | `sequencer/` | Transaction ingestion, batching, and L1 inscription. |
|
||||
| **Archiver** | `archiver/` | L1 monitoring, L2 block validation, and data serving. |
|
||||
| **Frontend** | `webapp/` | UI for monitoring L2 state and sending transactions. |
|
||||
|
||||
---
|
||||
|
||||
## Getting Started (Local Run)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* **Docker & Docker Compose**
|
||||
* **Logos Testnet Credentials**: If connecting to the public testnet, you need basic auth credentials (Username/Password). Contact the team via Discord to obtain these.
|
||||
|
||||
### 1. Configuration
|
||||
|
||||
Copy the example environment file and fill in your credentials.
|
||||
|
||||
```bash
|
||||
cp testnet/l2-sequencer-archival-demo/.env.example testnet/l2-sequencer-archival-demo/.env
|
||||
|
||||
```
|
||||
|
||||
### 2. Launch with Docker Compose
|
||||
|
||||
The simplest way to run the entire stack is using our prebuilt images.
|
||||
|
||||
```bash
|
||||
# Navigate to the demo directory
|
||||
cd testnet/l2-sequencer-archival-demo
|
||||
|
||||
# Start all services
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Once running, the web application will be available at `http://localhost:8200`.
|
||||
|
||||
---
|
||||
|
||||
## Manual Development Setup
|
||||
|
||||
For developers on macOS (including ARM/M1/M2) or Linux who wish to run components outside of Docker, we provide a unified helper script: `run-local.sh`.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* **Rust**: For building the Sequencer and Archiver binaries.
|
||||
* **Bun**: For running the frontend development server.
|
||||
* **OpenSSL**: For generating unique Channel IDs.
|
||||
|
||||
### Using the Local Runner
|
||||
|
||||
The script automates building binaries, managing data directories, and linking environment variables between services.
|
||||
|
||||
```bash
|
||||
# Usage
|
||||
./run-local.sh <service> --env-file <path-to-env> [--clean]
|
||||
|
||||
# 1. Run the entire stack (Sequencer + Archiver + Frontend)
|
||||
./run-local.sh all --env-file .env-local
|
||||
|
||||
# 2. Run only a specific component
|
||||
./run-local.sh sequencer --env-file .env-local
|
||||
|
||||
# 3. Start fresh (deletes local databases/keys)
|
||||
./run-local.sh all --env-file .env-local --clean
|
||||
|
||||
```
|
||||
|
||||
### Environment Setup
|
||||
|
||||
You will need a running **Nomos Node** (L1). If you are running one locally, ensure the `TESTNET_ENDPOINT` in your `.env` points to your local node.
|
||||
|
||||
---
|
||||
|
||||
## Component READMEs
|
||||
|
||||
For detailed configuration flags, API documentation, and internal logic for each component, please refer to their individual documentation:
|
||||
|
||||
* **[Sequencer In-Depth](./sequencer/README.md)** - Transaction batching and L1 submission logic.
|
||||
* **[Archiver In-Depth](./archiver/README.md)** - Validation engine and the SSE block stream.
|
||||
* **[Webapp Setup](./webapp/README.md)** - UI development and customization instructions.
|
||||
|
||||
35
testnet/l2-sequencer-archival-demo/archiver/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
categories = { workspace = true }
|
||||
description = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
keywords = { workspace = true }
|
||||
license = { workspace = true }
|
||||
name = "logos-blockchain-archiver"
|
||||
readme = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
version = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-stream = { default-features = false, version = "0.3.6" }
|
||||
axum = { default-features = false, version = "0.8.7", features = ["http2", "json", "tokio"] }
|
||||
broadcast-service = { workspace = true }
|
||||
clap = { default-features = false, features = ["derive", "env", "std"], version = "4.5.13" }
|
||||
common-http-client = { workspace = true }
|
||||
demo-sequencer = { workspace = true }
|
||||
futures = { default-features = false, version = "0.3" }
|
||||
hex = { default-features = false, features = ["alloc"], version = "0.4" }
|
||||
nomos-core = { workspace = true }
|
||||
owo-colors = { default-features = false, version = "4.2.3" }
|
||||
redb = { default-features = false, version = "3.1.0" }
|
||||
reqwest = { features = ["json", "rustls-tls"], workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { default-features = false, version = "0.1" }
|
||||
tokio-util = { default-features = false, version = "0.7" }
|
||||
tower-http = { version = "0.6.8", features = ["cors"] }
|
||||
url = { default-features = false, version = "2.5.4" }
|
||||
203
testnet/l2-sequencer-archival-demo/archiver/README.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Archiver Demo
|
||||
|
||||
A real-time block archiver that subscribes to a Logos Blockchain node's Last Immutable Block (LIB) stream, extracts L2 sequencer inscriptions from a specified channel, validates transactions, and exposes them via HTTP endpoints.
|
||||
|
||||
## What It Does
|
||||
|
||||
1. **Connects to a Logos Blockchain node** via HTTP to subscribe to the LIB stream
|
||||
2. **Filters inscriptions** by channel ID to extract L2 sequencer block data
|
||||
3. **Validates blocks** — a block is invalid if:
|
||||
- Its parent block (except genesis block 0) was previously marked as invalid, or
|
||||
- It contains a transaction where the sender has insufficient balance
|
||||
4. **Persists valid blocks** in a redb database and tracks invalid block IDs
|
||||
5. **Re-validates blocks** — previously invalid blocks are automatically marked as valid when they appear again with valid ancestry
|
||||
6. **Broadcasts blocks** to connected clients via an SSE endpoint at `/block_stream`
|
||||
7. **Serves historical blocks** via a REST endpoint at `/blocks`
|
||||
8. **Pretty prints** transaction details to the console with colored output
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
cargo build --release -p logos-blockchain-archiver
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### Command Line Arguments
|
||||
|
||||
| Flag | Env Variable | Description | Default |
|
||||
|------|--------------|-------------|---------|
|
||||
| `-e` | `TESTNET_ENDPOINT` | Logos Blockchain node HTTP endpoint URL | Required |
|
||||
| `-u` | `TESTNET_USERNAME` | Basic auth username | Optional |
|
||||
| `-p` | `TESTNET_PASSWORD` | Basic auth password | Optional |
|
||||
| `-c` | `CHANNEL_ID` | Channel ID (64 hex chars / 32 bytes) | Required |
|
||||
| `-t` | `TOKEN_NAME` | Token name to display in output | Required |
|
||||
| `-b` | `INITIAL_BALANCE` | Initial balance for new accounts | `1000` |
|
||||
| `-n` | `PORT_NUMBER` | HTTP server port | `8090` |
|
||||
|
||||
### Using CLI Flags
|
||||
|
||||
```bash
|
||||
./target/release/logos-blockchain-archiver \
|
||||
-e http://localhost:8080 \
|
||||
-c 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
|
||||
-t DEMO
|
||||
```
|
||||
|
||||
With optional authentication:
|
||||
|
||||
```bash
|
||||
./target/release/logos-blockchain-archiver \
|
||||
-e http://localhost:8080 \
|
||||
-u admin \
|
||||
-p secret \
|
||||
-c 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
|
||||
-t DEMO \
|
||||
-b 1000 \
|
||||
-n 8090
|
||||
```
|
||||
|
||||
### Using Environment Variables
|
||||
|
||||
```bash
|
||||
export TESTNET_ENDPOINT=http://localhost:8080
|
||||
export CHANNEL_ID=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
export TOKEN_NAME=DEMO
|
||||
|
||||
# Optional
|
||||
export TESTNET_USERNAME=admin
|
||||
export TESTNET_PASSWORD=secret
|
||||
export INITIAL_BALANCE=1000
|
||||
export PORT_NUMBER=8090
|
||||
|
||||
./target/release/logos-blockchain-archiver
|
||||
```
|
||||
|
||||
### Using a `.env` File
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```env
|
||||
TESTNET_ENDPOINT=http://localhost:8080
|
||||
CHANNEL_ID=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
TOKEN_NAME=DEMO
|
||||
|
||||
# Optional
|
||||
TESTNET_USERNAME=admin
|
||||
TESTNET_PASSWORD=secret
|
||||
INITIAL_BALANCE=1000
|
||||
PORT_NUMBER=8090
|
||||
```
|
||||
|
||||
Then run with a tool like `dotenv`:
|
||||
|
||||
```bash
|
||||
dotenv ./target/release/logos-blockchain-archiver
|
||||
```
|
||||
|
||||
## HTTP API
|
||||
|
||||
The archiver starts an HTTP server on the configured port (default `8090`). CORS is enabled for all origins.
|
||||
|
||||
### GET `/block_stream`
|
||||
|
||||
Server-Sent Events stream of validated L2 blocks in real-time.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -N http://localhost:8090/block_stream
|
||||
```
|
||||
|
||||
**Response format:**
|
||||
|
||||
```
|
||||
data: {"data":{"block_id":1,"parent_block_id":0,"transactions":[{"id":"...","from":"alice","to":"bob","amount":100,"confirmed":false,"index":0}]},"l1_block_id":"..."}
|
||||
|
||||
data: {"data":{"block_id":2,"parent_block_id":1,"transactions":[{"id":"...","from":"bob","to":"charlie","amount":50,"confirmed":false,"index":0}]},"l1_block_id":"..."}
|
||||
```
|
||||
|
||||
Each `data:` line contains a JSON-serialized validated block object with the L1 block ID where it was inscribed.
|
||||
|
||||
### GET `/blocks`
|
||||
|
||||
Returns all stored validated blocks as a JSON array.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:8090/blocks
|
||||
```
|
||||
|
||||
**Response format:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"data": {
|
||||
"block_id": 1,
|
||||
"parent_block_id": 0,
|
||||
"transactions": [
|
||||
{
|
||||
"id": "tx-uuid",
|
||||
"from": "alice",
|
||||
"to": "bob",
|
||||
"amount": 100,
|
||||
"confirmed": false,
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"l1_block_id": "0123456789abcdef..."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Data Storage
|
||||
|
||||
The archiver uses [redb](https://github.com/cberner/redb) for persistent storage:
|
||||
|
||||
- **`blocks.database`** — Stores validated L2 blocks
|
||||
- **`accounts.database`** — Tracks account balances for transaction validation
|
||||
|
||||
## Console Output
|
||||
|
||||
When running, the archiver displays:
|
||||
|
||||
- A startup banner with connection details
|
||||
- Real-time L1 block notifications with height and header ID
|
||||
- L2 block details with transaction information
|
||||
- Colored output showing sender → receiver transfers
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
_ _ _ ____
|
||||
/ \ _ __ ___| |__ (_)_ _____ _ __ | _ \ ___ _ __ ___ ___
|
||||
/ _ \ | '__/ __| '_ \| \ \ / / _ \ '__|| | | |/ _ \ '_ ` _ \ / _ \
|
||||
/ ___ \| | | (__| | | | |\ V / __/ | | |_| | __/ | | | | | (_) |
|
||||
/_/ \_\_| \___|_| |_|_| \_/ \___|_| |____/ \___|_| |_| |_|\___/
|
||||
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
📡 Nomos Node: http://localhost:8080
|
||||
📺 Channel ID: 0123456789abcdef...
|
||||
🌐 HTTP Server: http://0.0.0.0:8090/blocks
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
⏳ Waiting for blocks...
|
||||
|
||||
🔗 Block at height 42 (abc123...)
|
||||
┌
|
||||
│ 📦 Block #1
|
||||
│ 💳 2 transaction(s)
|
||||
│ ↳ alice → bob (100 DEMO)
|
||||
│ ↳ bob → charlie (50 DEMO)
|
||||
└
|
||||
```
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
Press `Ctrl+C` to initiate a graceful shutdown. The archiver will:
|
||||
|
||||
1. Stop accepting new SSE connections
|
||||
2. Complete any in-flight block processing
|
||||
3. Close all connections cleanly
|
||||
225
testnet/l2-sequencer-archival-demo/archiver/src/block.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use async_stream::stream;
|
||||
use broadcast_service::BlockInfo;
|
||||
use common_http_client::CommonHttpClient;
|
||||
use demo_sequencer::{BlockData, db::AccountDb};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use nomos_core::{
|
||||
header::HeaderId,
|
||||
mantle::{
|
||||
Op, SignedMantleTx, Transaction as _, TxHash,
|
||||
ops::channel::{ChannelId, inscribe::InscriptionOp},
|
||||
},
|
||||
};
|
||||
use owo_colors::OwoColorize as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::select;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use url::Url;
|
||||
|
||||
use crate::db::BlockStore;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct L2BlockInfo {
|
||||
pub data: BlockData,
|
||||
pub l1_block_id: HeaderId,
|
||||
pub l1_transaction_id: TxHash,
|
||||
}
|
||||
|
||||
pub struct BlockStream;
|
||||
|
||||
impl BlockStream {
|
||||
pub fn create(
|
||||
cancellation_token: CancellationToken,
|
||||
http_client: CommonHttpClient,
|
||||
endpoint_url: &Url,
|
||||
channel_id: &ChannelId,
|
||||
token_name: &str,
|
||||
) -> impl Stream<Item = L2BlockInfo> {
|
||||
#[expect(tail_expr_drop_order, reason = "Generated internally by stream macro.")]
|
||||
let block_stream = stream! {
|
||||
let mut lib_stream = Box::pin(http_client
|
||||
.get_lib_stream(endpoint_url.clone())
|
||||
.await.unwrap());
|
||||
|
||||
loop {
|
||||
select! {
|
||||
// Always poll cancellation token first.
|
||||
biased;
|
||||
|
||||
() = cancellation_token.cancelled() => {
|
||||
break;
|
||||
}
|
||||
|
||||
block_info = lib_stream.next() => {
|
||||
let Some(BlockInfo { header_id, height }) = block_info else {
|
||||
println!(
|
||||
" {} Stream ended unexpectedly",
|
||||
"⚠️".yellow()
|
||||
);
|
||||
break;
|
||||
};
|
||||
|
||||
println!(" {} Block at height {} ({})","🔗".blue(),
|
||||
height.bright_white().bold(),
|
||||
&hex::encode(header_id.as_ref()
|
||||
).dimmed());
|
||||
|
||||
let block = http_client.get_block_by_id(endpoint_url.clone(), header_id).await.unwrap().unwrap();
|
||||
for (l2_block, l1_transaction_id) in extract_l2_blocks(block.transactions().cloned(), channel_id, token_name) {
|
||||
yield L2BlockInfo {
|
||||
data: l2_block,
|
||||
l1_block_id: block.header().id(),
|
||||
l1_transaction_id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
block_stream
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_l2_blocks(
|
||||
block_txs: impl Iterator<Item = SignedMantleTx>,
|
||||
decoded_channel_id: &ChannelId,
|
||||
token_name: &str,
|
||||
) -> Vec<(BlockData, TxHash)> {
|
||||
let block_channel_ops: Vec<(BlockData, TxHash)> = block_txs
|
||||
.flat_map(|tx| {
|
||||
let tx_hash = tx.mantle_tx.hash();
|
||||
tx.mantle_tx
|
||||
.ops
|
||||
.iter()
|
||||
.filter_map(|op| match op {
|
||||
Op::ChannelInscribe(InscriptionOp {
|
||||
channel_id,
|
||||
inscription,
|
||||
..
|
||||
}) if channel_id == decoded_channel_id => {
|
||||
let Ok(block_data) = serde_json::from_slice::<BlockData>(inscription)
|
||||
else {
|
||||
println!(
|
||||
" {} Failed to decode L2 block in tx {}",
|
||||
"⚠️".yellow(),
|
||||
hex::encode(tx_hash.as_signing_bytes()).dimmed()
|
||||
);
|
||||
return None;
|
||||
};
|
||||
Some((block_data, tx_hash))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if block_channel_ops.is_empty() {
|
||||
println!(" {} No inscriptions in this block", "○".dimmed());
|
||||
} else {
|
||||
for (block_data, _) in &block_channel_ops {
|
||||
println!("{}", "┌".bright_green());
|
||||
println!(
|
||||
"│ {} Block #{}",
|
||||
"📦".green(),
|
||||
block_data.block_id.bright_green().bold()
|
||||
);
|
||||
println!(
|
||||
"│ 💳 {} transaction(s)",
|
||||
block_data.transactions.len().yellow().bold()
|
||||
);
|
||||
|
||||
for tx_item in &block_data.transactions {
|
||||
println!(
|
||||
"│ {} {} → {} ({} {})",
|
||||
"↳".dimmed(),
|
||||
tx_item.from.bright_cyan(),
|
||||
tx_item.to.bright_magenta(),
|
||||
tx_item.amount.yellow(),
|
||||
token_name
|
||||
);
|
||||
}
|
||||
println!("{}", "└".bright_green());
|
||||
}
|
||||
}
|
||||
|
||||
block_channel_ops
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ValidatedBlockData(BlockData);
|
||||
|
||||
impl AsRef<BlockData> for ValidatedBlockData {
|
||||
fn as_ref(&self) -> &BlockData {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ValidatedL2Info(L2BlockInfo);
|
||||
|
||||
impl ValidatedL2Info {
|
||||
pub fn new(
|
||||
validated_block_data: ValidatedBlockData,
|
||||
l1_block_id: HeaderId,
|
||||
l1_transaction_id: TxHash,
|
||||
) -> Self {
|
||||
Self(L2BlockInfo {
|
||||
data: validated_block_data.0,
|
||||
l1_block_id,
|
||||
l1_transaction_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<L2BlockInfo> for ValidatedL2Info {
|
||||
fn as_ref(&self) -> &L2BlockInfo {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn validate_block(
|
||||
block: BlockData,
|
||||
accounts_db: &AccountDb,
|
||||
blocks_db: &BlockStore,
|
||||
) -> Result<ValidatedBlockData, BlockData> {
|
||||
// We consider block `0` to be the genesis block and always valid, hence its
|
||||
// children won't be checked against the DB.
|
||||
if block.parent_block_id > 0
|
||||
&& !blocks_db
|
||||
.is_block_valid(block.parent_block_id)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
println!(
|
||||
" {} Block {} rejected: parent block {} is invalid",
|
||||
"❌".red(),
|
||||
block.block_id.bright_red().bold(),
|
||||
block.parent_block_id.yellow()
|
||||
);
|
||||
return Err(block);
|
||||
}
|
||||
|
||||
let are_txs_valid = accounts_db
|
||||
.try_apply_transfers(
|
||||
block
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| (tx.from.as_str(), tx.to.as_str(), tx.amount)),
|
||||
)
|
||||
.await
|
||||
.is_ok();
|
||||
if !are_txs_valid {
|
||||
println!(
|
||||
" {} Block {} rejected: contains invalid transactions",
|
||||
"❌".red(),
|
||||
block.block_id.bright_red().bold()
|
||||
);
|
||||
return Err(block);
|
||||
}
|
||||
|
||||
Ok(ValidatedBlockData(block))
|
||||
}
|
||||
47
testnet/l2-sequencer-archival-demo/archiver/src/cli.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use core::convert::Infallible;
|
||||
|
||||
use clap::Parser;
|
||||
use nomos_core::mantle::ops::channel::ChannelId;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct CliArgs {
|
||||
#[clap(short = 'e', env = "TESTNET_ENDPOINT")]
|
||||
pub nomos_node_http_endpoint: Url,
|
||||
#[clap(short = 'u', env = "TESTNET_USERNAME")]
|
||||
pub username: Option<String>,
|
||||
#[clap(short = 'p', env = "TESTNET_PASSWORD")]
|
||||
pub password: Option<String>,
|
||||
#[clap(short = 'c', env = "CHANNEL_ID", value_parser = parse_channel_id)]
|
||||
pub channel_id: ChannelId,
|
||||
#[clap(short = 't', env = "TOKEN_NAME")]
|
||||
pub token_name: String,
|
||||
#[clap(short = 'b', env = "INITIAL_BALANCE", default_value = "1000")]
|
||||
pub initial_balance: u64,
|
||||
#[clap(short = 'n', env = "PORT_NUMBER", default_value = "8090")]
|
||||
pub port_number: u16,
|
||||
#[clap(
|
||||
long,
|
||||
env = "ARCHIVER_BLOCKS_DB_PATH",
|
||||
default_value = "blocks.database"
|
||||
)]
|
||||
pub blocks_db_path: String,
|
||||
#[clap(
|
||||
long,
|
||||
env = "ARCHIVER_ACCOUNTS_DB_PATH",
|
||||
default_value = "accounts.database"
|
||||
)]
|
||||
pub accounts_db_path: String,
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::unnecessary_wraps,
|
||||
reason = "Clap requires a Result type for custom parsers"
|
||||
)]
|
||||
fn parse_channel_id(encoded_channel_id: &str) -> Result<ChannelId, Infallible> {
|
||||
Ok(
|
||||
<[u8; 32]>::try_from(hex::decode(encoded_channel_id).unwrap())
|
||||
.unwrap()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
11
testnet/l2-sequencer-archival-demo/archiver/src/ctrl_c.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use owo_colors::OwoColorize as _;
|
||||
use tokio::signal::ctrl_c;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub fn listen_for_sigint(cancellation_token: CancellationToken) {
|
||||
tokio::spawn(async move {
|
||||
ctrl_c().await.unwrap();
|
||||
println!("\n {} Graceful shutdown initiated...", "🛑".red());
|
||||
cancellation_token.cancel();
|
||||
});
|
||||
}
|
||||
106
testnet/l2-sequencer-archival-demo/archiver/src/db.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use nomos_core::codec::{DeserializeOp as _, SerializeOp as _};
|
||||
use redb::{
|
||||
CommitError, Database, DatabaseError, ReadableDatabase as _, ReadableTable as _, StorageError,
|
||||
TableDefinition, TableError, TransactionError,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::block::ValidatedL2Info;
|
||||
|
||||
const BLOCKS_TABLE: TableDefinition<u64, &[u8]> = TableDefinition::new("blocks");
|
||||
const INVALID_BLOCKS_TABLE: TableDefinition<u64, ()> = TableDefinition::new("invalid_blocks");
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DbError {
|
||||
#[error("Database error: {0}")]
|
||||
Database(#[from] DatabaseError),
|
||||
#[error("Transaction error: {0}")]
|
||||
Transaction(#[from] Box<TransactionError>),
|
||||
#[error("Table error: {0}")]
|
||||
Table(#[from] TableError),
|
||||
#[error("Storage error: {0}")]
|
||||
Storage(#[from] StorageError),
|
||||
#[error("Commit error: {0}")]
|
||||
Commit(#[from] CommitError),
|
||||
#[error("Serialization error: {0}")]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
impl From<TransactionError> for DbError {
|
||||
fn from(err: TransactionError) -> Self {
|
||||
Self::Transaction(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
/// Persistent storage for blocks using redb
|
||||
#[derive(Clone)]
|
||||
pub struct BlockStore {
|
||||
db: Arc<RwLock<Database>>,
|
||||
}
|
||||
|
||||
impl BlockStore {
|
||||
pub fn new(path: &str) -> Result<Self, DbError> {
|
||||
let db = Database::create(path)?;
|
||||
|
||||
let write_txn = db.begin_write()?;
|
||||
drop(write_txn.open_table(BLOCKS_TABLE)?);
|
||||
drop(write_txn.open_table(INVALID_BLOCKS_TABLE)?);
|
||||
write_txn.commit()?;
|
||||
|
||||
Ok(Self {
|
||||
db: Arc::new(RwLock::new(db)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn add_block(&self, block: ValidatedL2Info) -> Result<(), DbError> {
|
||||
let serialized = block.to_bytes().unwrap();
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
write_txn
|
||||
.open_table(BLOCKS_TABLE)?
|
||||
.insert(block.as_ref().data.block_id, &*serialized)?;
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn mark_block_as_invalid(&self, block_id: u64) -> Result<(), DbError> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
write_txn
|
||||
.open_table(INVALID_BLOCKS_TABLE)?
|
||||
.insert(block_id, &())?;
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unmark_block_as_invalid(&self, block_id: u64) -> Result<bool, DbError> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
let is_old_value_removed = write_txn
|
||||
.open_table(INVALID_BLOCKS_TABLE)?
|
||||
.remove(&block_id)?
|
||||
.is_some();
|
||||
write_txn.commit()?;
|
||||
Ok(is_old_value_removed)
|
||||
}
|
||||
|
||||
pub async fn is_block_valid(&self, block_id: u64) -> Result<bool, DbError> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
let table = read_txn.open_table(INVALID_BLOCKS_TABLE)?;
|
||||
Ok(table.get(&block_id)?.is_none())
|
||||
}
|
||||
|
||||
pub async fn get_all_blocks(&self) -> Result<Vec<ValidatedL2Info>, DbError> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
|
||||
let deserialized_blocks: Vec<ValidatedL2Info> = read_txn
|
||||
.open_table(BLOCKS_TABLE)?
|
||||
.iter()?
|
||||
.filter_map(Result::ok)
|
||||
.map(|(_, value)| value)
|
||||
.map(|value| ValidatedL2Info::from_bytes(value.value()).unwrap())
|
||||
.collect();
|
||||
|
||||
Ok(deserialized_blocks)
|
||||
}
|
||||
}
|
||||
100
testnet/l2-sequencer-archival-demo/archiver/src/http.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use core::{convert::Infallible, net::SocketAddr};
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::State,
|
||||
response::{Sse, sse::Event},
|
||||
routing::get,
|
||||
serve,
|
||||
};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use reqwest::{Method, header};
|
||||
use tokio::{net::TcpListener, sync::broadcast::Receiver};
|
||||
use tokio_stream::wrappers::BroadcastStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
use crate::{block::ValidatedL2Info, db::BlockStore};
|
||||
|
||||
pub struct Server {
|
||||
block_receiver_channel: Receiver<ValidatedL2Info>,
|
||||
cancellation_token: CancellationToken,
|
||||
blocks_db: BlockStore,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub const fn new(
|
||||
block_receiver_channel: Receiver<ValidatedL2Info>,
|
||||
cancellation_token: CancellationToken,
|
||||
blocks_db: BlockStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_receiver_channel,
|
||||
cancellation_token,
|
||||
blocks_db,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self, address: SocketAddr) {
|
||||
let (router, cancellation_token) = self.into_router_and_cancellation_token();
|
||||
tokio::spawn(async move {
|
||||
serve(TcpListener::bind(address).await.unwrap(), router)
|
||||
.with_graceful_shutdown(async move {
|
||||
cancellation_token.cancelled().await;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn into_router_and_cancellation_token(self) -> (Router, CancellationToken) {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
|
||||
.allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION]);
|
||||
|
||||
(
|
||||
Router::new()
|
||||
.route("/block_stream", get(handle_block_stream))
|
||||
.route("/blocks", get(handle_get_blocks))
|
||||
.with_state(AppState {
|
||||
block_receiver_channel: self.block_receiver_channel,
|
||||
blocks_db: self.blocks_db,
|
||||
})
|
||||
.layer(cors),
|
||||
self.cancellation_token,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
block_receiver_channel: Receiver<ValidatedL2Info>,
|
||||
blocks_db: BlockStore,
|
||||
}
|
||||
|
||||
impl Clone for AppState {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
block_receiver_channel: self.block_receiver_channel.resubscribe(),
|
||||
blocks_db: self.blocks_db.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_block_stream(
|
||||
State(state): State<AppState>,
|
||||
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
|
||||
let stream = BroadcastStream::new(state.block_receiver_channel)
|
||||
.map(|block_data_result| block_data_result.unwrap())
|
||||
.map(|block_data| serde_json::to_string(&block_data).unwrap())
|
||||
.map(|json_serialized_block_data| Ok(Event::default().data(json_serialized_block_data)));
|
||||
|
||||
Sse::new(stream)
|
||||
}
|
||||
|
||||
async fn handle_get_blocks(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Vec<ValidatedL2Info>>, Infallible> {
|
||||
let blocks = state.blocks_db.get_all_blocks().await.unwrap();
|
||||
Ok(Json(blocks))
|
||||
}
|
||||
110
testnet/l2-sequencer-archival-demo/archiver/src/main.rs
Normal file
@ -0,0 +1,110 @@
|
||||
#![expect(clippy::non_ascii_literal, reason = "Demo, so emojis are fine.")]
|
||||
|
||||
use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
|
||||
use clap::Parser as _;
|
||||
use common_http_client::{BasicAuthCredentials, CommonHttpClient};
|
||||
use demo_sequencer::db::AccountDb;
|
||||
use futures::StreamExt as _;
|
||||
use owo_colors::OwoColorize as _;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
block::{BlockStream, ValidatedL2Info, validate_block},
|
||||
cli::CliArgs,
|
||||
ctrl_c::listen_for_sigint,
|
||||
db::BlockStore,
|
||||
http::Server,
|
||||
output::print_startup_banner,
|
||||
};
|
||||
|
||||
mod block;
|
||||
mod cli;
|
||||
mod ctrl_c;
|
||||
mod db;
|
||||
mod http;
|
||||
mod output;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let CliArgs {
|
||||
nomos_node_http_endpoint,
|
||||
username,
|
||||
password,
|
||||
channel_id,
|
||||
token_name,
|
||||
initial_balance,
|
||||
port_number,
|
||||
blocks_db_path,
|
||||
accounts_db_path,
|
||||
} = CliArgs::parse();
|
||||
|
||||
let listen_address = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port_number));
|
||||
|
||||
print_startup_banner(&nomos_node_http_endpoint, &channel_id, &listen_address);
|
||||
|
||||
// Setup
|
||||
|
||||
let (rollup_block_sender, _) = broadcast::channel::<ValidatedL2Info>(100);
|
||||
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
let client = CommonHttpClient::new(username.map(|u| BasicAuthCredentials::new(u, password)));
|
||||
|
||||
let blocks_db = BlockStore::new(&blocks_db_path).unwrap();
|
||||
let accounts_db = AccountDb::new(&accounts_db_path, initial_balance).unwrap();
|
||||
|
||||
// Start sigint handler
|
||||
|
||||
listen_for_sigint(cancellation_token.clone());
|
||||
|
||||
// Start HTTP server
|
||||
|
||||
Server::new(
|
||||
rollup_block_sender.subscribe(),
|
||||
cancellation_token.clone(),
|
||||
blocks_db.clone(),
|
||||
)
|
||||
.start(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 8090).into());
|
||||
|
||||
// Start LIB subscriber
|
||||
|
||||
let mut block_stream = Box::pin(BlockStream::create(
|
||||
cancellation_token,
|
||||
client,
|
||||
&nomos_node_http_endpoint,
|
||||
&channel_id,
|
||||
token_name.as_str(),
|
||||
));
|
||||
|
||||
while let Some(block) = block_stream.next().await {
|
||||
match validate_block(block.data, &accounts_db, &blocks_db).await {
|
||||
Ok(validated_l2_block) => {
|
||||
let validated_l2_info = ValidatedL2Info::new(
|
||||
validated_l2_block.clone(),
|
||||
block.l1_block_id,
|
||||
block.l1_transaction_id,
|
||||
);
|
||||
blocks_db
|
||||
.add_block(validated_l2_info.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let block_id = validated_l2_block.as_ref().block_id;
|
||||
if blocks_db.unmark_block_as_invalid(block_id).await.unwrap() {
|
||||
println!(
|
||||
" {} Previously invalid block {block_id} now marked as valid",
|
||||
"✅".green(),
|
||||
);
|
||||
}
|
||||
rollup_block_sender.send(validated_l2_info).unwrap();
|
||||
}
|
||||
Err(invalid_l2_block) => {
|
||||
blocks_db
|
||||
.mark_block_as_invalid(invalid_l2_block.block_id)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
testnet/l2-sequencer-archival-demo/archiver/src/output.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use core::net::SocketAddr;
|
||||
|
||||
use nomos_core::mantle::ops::channel::ChannelId;
|
||||
use owo_colors::OwoColorize as _;
|
||||
use url::Url;
|
||||
|
||||
const BANNER: &str = r"
|
||||
_ _ _ ____
|
||||
/ \ _ __ ___| |__ (_)_ _____ _ __ | _ \ ___ _ __ ___ ___
|
||||
/ _ \ | '__/ __| '_ \| \ \ / / _ \ '__|| | | |/ _ \ '_ ` _ \ / _ \
|
||||
/ ___ \| | | (__| | | | |\ V / __/ | | |_| | __/ | | | | | (_) |
|
||||
/_/ \_\_| \___|_| |_|_| \_/ \___|_| |____/ \___|_| |_| |_|\___/
|
||||
";
|
||||
|
||||
pub fn print_startup_banner(endpoint: &Url, channel_id: &ChannelId, listen_addr: &SocketAddr) {
|
||||
println!("{}", BANNER.cyan().bold());
|
||||
println!("{}", "═".repeat(70).dimmed());
|
||||
println!(
|
||||
" {} {}",
|
||||
"📡 Nomos Node:".bright_blue().bold(),
|
||||
endpoint.white()
|
||||
);
|
||||
println!(
|
||||
" {} {}",
|
||||
"📺 Channel ID:".bright_blue().bold(),
|
||||
hex::encode(channel_id.as_ref()).white()
|
||||
);
|
||||
println!(
|
||||
" {} {}",
|
||||
"🌐 HTTP Server:".bright_blue().bold(),
|
||||
format!("http://{listen_addr}/blocks").green()
|
||||
);
|
||||
println!("{}", "═".repeat(70).dimmed());
|
||||
println!(" {} Waiting for blocks...\n", "⏳".yellow());
|
||||
}
|
||||
47
testnet/l2-sequencer-archival-demo/compose.yml
Normal file
@ -0,0 +1,47 @@
|
||||
# NOTE: This docker-compose only works on x86_64 (AMD64) architecture.
|
||||
# The ZK prover will panic when running under ARM emulation.
|
||||
# For ARM machines (e.g., Apple Silicon), run the binaries directly instead.
|
||||
|
||||
services:
|
||||
l2-nginx:
|
||||
container_name: l2_nginx
|
||||
image: l2-demo-local
|
||||
ports:
|
||||
- "8200:80"
|
||||
depends_on:
|
||||
- l2-sequencer
|
||||
- l2-archiver
|
||||
entrypoint: ["nginx", "-g", "daemon off;"]
|
||||
|
||||
l2-sequencer:
|
||||
container_name: l2_sequencer
|
||||
image: l2-demo-local
|
||||
environment:
|
||||
- SEQUENCER_LISTEN_ADDR=0.0.0.0:8080
|
||||
- SEQUENCER_NODE_ENDPOINT=${SEQUENCER_NODE_ENDPOINT}
|
||||
- SEQUENCER_DB_PATH=/data/sequencer.db
|
||||
- SEQUENCER_SIGNING_KEY_PATH=/data/sequencer.key
|
||||
- SEQUENCER_CHANNEL_ID=${CHANNEL_ID}
|
||||
- SEQUENCER_INITIAL_BALANCE=1000
|
||||
- SEQUENCER_NODE_AUTH_USERNAME=${SEQUENCER_NODE_USERNAME}
|
||||
- SEQUENCER_NODE_AUTH_PASSWORD=${SEQUENCER_NODE_PASSWORD}
|
||||
volumes:
|
||||
- l2-sequencer-data:/data
|
||||
entrypoint: ["/usr/bin/demo-sequencer"]
|
||||
|
||||
l2-archiver:
|
||||
container_name: l2_archiver
|
||||
image: l2-demo-local
|
||||
environment:
|
||||
- TESTNET_ENDPOINT=${ARCHIVER_NODE_ENDPOINT}
|
||||
- TESTNET_USERNAME=${ARCHIVER_NODE_USERNAME}
|
||||
- TESTNET_PASSWORD=${ARCHIVER_NODE_PASSWORD}
|
||||
- CHANNEL_ID=${CHANNEL_ID}
|
||||
- TOKEN_NAME=${TOKEN_NAME:-MEM}
|
||||
volumes:
|
||||
- l2-archiver-data:/data
|
||||
entrypoint: ["/usr/bin/logos-blockchain-archiver"]
|
||||
|
||||
volumes:
|
||||
l2-sequencer-data:
|
||||
l2-archiver-data:
|
||||
47
testnet/l2-sequencer-archival-demo/nginx/nginx.conf
Normal file
@ -0,0 +1,47 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Frontend static files
|
||||
location / {
|
||||
root /var/www/l2-demo;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Sequencer API
|
||||
location /api/sequencer/ {
|
||||
proxy_pass http://l2-sequencer:8080/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Archiver API
|
||||
location /api/archiver/ {
|
||||
proxy_pass http://l2-archiver:8090/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# SSE specific
|
||||
proxy_set_header Connection '';
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
}
|
||||
}
|
||||
278
testnet/l2-sequencer-archival-demo/run-local.sh
Executable file
@ -0,0 +1,278 @@
|
||||
#!/bin/bash
|
||||
|
||||
# L2 Demo - Local Development Runner
|
||||
# Runs sequencer, archiver, and/or frontend without Docker (works on ARM Mac)
|
||||
#
|
||||
# Usage:
|
||||
# ./run-local.sh <service> --env-file /path/to/.env-local [--clean]
|
||||
#
|
||||
# Services:
|
||||
# sequencer - Run only the sequencer
|
||||
# archiver - Run only the archiver
|
||||
# frontend - Run only the frontend
|
||||
# all - Run all services (default)
|
||||
#
|
||||
# Examples:
|
||||
# ./run-local.sh all --env-file ~/Eng/offsite-sequencer-env/.env-local
|
||||
# ./run-local.sh sequencer --env-file ~/Eng/offsite-sequencer-env/.env-local
|
||||
# ./run-local.sh archiver --env-file ~/Eng/offsite-sequencer-env/.env-local
|
||||
# ./run-local.sh frontend --env-file ~/Eng/offsite-sequencer-env/.env-local
|
||||
# ./run-local.sh all --env-file ~/Eng/offsite-sequencer-env/.env-local --clean
|
||||
#
|
||||
# Required env vars:
|
||||
# SEQUENCER_NODE_ENDPOINT - Nomos node HTTP endpoint for sequencer
|
||||
# ARCHIVER_NODE_ENDPOINT - Nomos node HTTP endpoint for archiver
|
||||
# TOKEN_NAME - Token name (e.g., "MEM")
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
DATA_DIR="$SCRIPT_DIR/data"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Parse service argument (first positional arg)
|
||||
SERVICE="all"
|
||||
if [[ $# -gt 0 && ! "$1" =~ ^-- ]]; then
|
||||
SERVICE="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
# Validate service
|
||||
case $SERVICE in
|
||||
sequencer|archiver|frontend|all)
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown service: $SERVICE${NC}"
|
||||
echo "Valid services: sequencer, archiver, frontend, all"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Parse remaining arguments
|
||||
ENV_FILE=""
|
||||
CLEAN_START=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--env-file)
|
||||
ENV_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--clean)
|
||||
CLEAN_START=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown option: $1${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Load env file if provided
|
||||
if [ -n "$ENV_FILE" ]; then
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo -e "${BLUE}Loading environment from: $ENV_FILE${NC}"
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
else
|
||||
echo -e "${RED}Error: env file not found: $ENV_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate required env vars
|
||||
missing_vars=()
|
||||
[ -z "$SEQUENCER_NODE_ENDPOINT" ] && missing_vars+=("SEQUENCER_NODE_ENDPOINT")
|
||||
[ -z "$ARCHIVER_NODE_ENDPOINT" ] && missing_vars+=("ARCHIVER_NODE_ENDPOINT")
|
||||
[ -z "$TOKEN_NAME" ] && missing_vars+=("TOKEN_NAME")
|
||||
|
||||
if [ ${#missing_vars[@]} -ne 0 ]; then
|
||||
echo -e "${RED}Error: Missing required environment variables:${NC}"
|
||||
for var in "${missing_vars[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
echo ""
|
||||
echo "See .env-local.example for the required format."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean data directory if requested
|
||||
if [ "$CLEAN_START" = true ]; then
|
||||
echo -e "${YELLOW}Cleaning data directory...${NC}"
|
||||
rm -rf "$DATA_DIR"
|
||||
fi
|
||||
|
||||
# Create data directory (needed for channel ID file)
|
||||
mkdir -p "$DATA_DIR"
|
||||
|
||||
# Handle CHANNEL_ID - check env, then data file, then generate new
|
||||
CHANNEL_ID_FILE="$DATA_DIR/channel_id"
|
||||
if [ -n "$CHANNEL_ID" ]; then
|
||||
# Use env var and save it
|
||||
echo "$CHANNEL_ID" > "$CHANNEL_ID_FILE"
|
||||
echo -e "${BLUE}Using CHANNEL_ID from environment${NC}"
|
||||
elif [ -f "$CHANNEL_ID_FILE" ]; then
|
||||
# Read from saved file
|
||||
CHANNEL_ID=$(cat "$CHANNEL_ID_FILE")
|
||||
echo -e "${BLUE}Using saved CHANNEL_ID from $CHANNEL_ID_FILE${NC}"
|
||||
else
|
||||
# Generate new random one
|
||||
CHANNEL_ID=$(openssl rand -hex 32)
|
||||
echo "$CHANNEL_ID" > "$CHANNEL_ID_FILE"
|
||||
echo -e "${YELLOW}Generated new CHANNEL_ID: ${CHANNEL_ID}${NC}"
|
||||
fi
|
||||
|
||||
# Set both channel ID vars to the same value
|
||||
export CHANNEL_ID
|
||||
export SEQUENCER_CHANNEL_ID="$CHANNEL_ID"
|
||||
|
||||
# Map shared credentials to what each binary expects
|
||||
export SEQUENCER_NODE_AUTH_USERNAME="$TESTNET_USERNAME"
|
||||
export SEQUENCER_NODE_AUTH_PASSWORD="$TESTNET_PASSWORD"
|
||||
export TESTNET_ENDPOINT="$ARCHIVER_NODE_ENDPOINT"
|
||||
|
||||
# Set defaults for sequencer
|
||||
export SEQUENCER_DB_PATH="${SEQUENCER_DB_PATH:-$DATA_DIR/sequencer.db}"
|
||||
export SEQUENCER_SIGNING_KEY_PATH="${SEQUENCER_SIGNING_KEY_PATH:-$DATA_DIR/sequencer.key}"
|
||||
|
||||
# Set defaults for archiver
|
||||
export ARCHIVER_BLOCKS_DB_PATH="${ARCHIVER_BLOCKS_DB_PATH:-$DATA_DIR/blocks.database}"
|
||||
export ARCHIVER_ACCOUNTS_DB_PATH="${ARCHIVER_ACCOUNTS_DB_PATH:-$DATA_DIR/accounts.database}"
|
||||
|
||||
# Get local IP for sharing
|
||||
LOCAL_IP=$(ipconfig getifaddr en0 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
|
||||
|
||||
# Set VITE URLs using local IP so they work over network
|
||||
export VITE_SEQUENCER_URL="${VITE_SEQUENCER_URL:-http://$LOCAL_IP:8080}"
|
||||
export VITE_ARCHIVER_URL="${VITE_ARCHIVER_URL:-http://$LOCAL_IP:8090}"
|
||||
export VITE_EXPLORER_URL="${VITE_EXPLORER_URL:-http://$LOCAL_IP:8000}"
|
||||
|
||||
echo -e "${GREEN}======================================${NC}"
|
||||
echo -e "${GREEN} L2 Demo - $SERVICE${NC}"
|
||||
echo -e "${GREEN}======================================${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Configuration:${NC}"
|
||||
echo " Sequencer endpoint: $SEQUENCER_NODE_ENDPOINT"
|
||||
echo " Archiver endpoint: $ARCHIVER_NODE_ENDPOINT"
|
||||
echo " Channel ID: $CHANNEL_ID"
|
||||
echo " Token: $TOKEN_NAME"
|
||||
echo " Data directory: $DATA_DIR"
|
||||
echo ""
|
||||
|
||||
# Check if binaries exist, if not build them
|
||||
SEQUENCER_BIN="$REPO_ROOT/target/release/demo-sequencer"
|
||||
ARCHIVER_BIN="$REPO_ROOT/target/release/logos-blockchain-archiver"
|
||||
|
||||
if [[ "$SERVICE" == "sequencer" || "$SERVICE" == "all" ]] && [ ! -f "$SEQUENCER_BIN" ]; then
|
||||
echo -e "${YELLOW}Building sequencer...${NC}"
|
||||
cd "$REPO_ROOT"
|
||||
cargo build --release -p demo-sequencer
|
||||
fi
|
||||
|
||||
if [[ "$SERVICE" == "archiver" || "$SERVICE" == "all" ]] && [ ! -f "$ARCHIVER_BIN" ]; then
|
||||
echo -e "${YELLOW}Building archiver...${NC}"
|
||||
cd "$REPO_ROOT"
|
||||
cargo build --release -p logos-blockchain-archiver
|
||||
fi
|
||||
|
||||
# Run the selected service(s)
|
||||
case $SERVICE in
|
||||
sequencer)
|
||||
echo -e "${GREEN}Starting sequencer...${NC}"
|
||||
cd "$REPO_ROOT"
|
||||
exec "$SEQUENCER_BIN"
|
||||
;;
|
||||
archiver)
|
||||
echo -e "${GREEN}Starting archiver...${NC}"
|
||||
cd "$REPO_ROOT"
|
||||
exec "$ARCHIVER_BIN"
|
||||
;;
|
||||
frontend)
|
||||
cd "$SCRIPT_DIR/webapp"
|
||||
|
||||
if ! command -v bun &> /dev/null; then
|
||||
echo -e "${RED}Error: bun is not installed. Install it with: curl -fsSL https://bun.sh/install | bash${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo -e "${YELLOW}Installing frontend dependencies...${NC}"
|
||||
bun install
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Starting frontend...${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Access points:${NC}"
|
||||
echo " Frontend: http://localhost:5173"
|
||||
echo " Frontend: http://$LOCAL_IP:5173 (share this with others on same network)"
|
||||
echo " Sequencer: $VITE_SEQUENCER_URL"
|
||||
echo " Archiver: $VITE_ARCHIVER_URL"
|
||||
echo " Explorer: $VITE_EXPLORER_URL"
|
||||
echo ""
|
||||
exec bun run dev --host
|
||||
;;
|
||||
all)
|
||||
# Trap to kill background processes on exit
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo -e "${YELLOW}Shutting down...${NC}"
|
||||
kill $SEQUENCER_PID 2>/dev/null || true
|
||||
kill $ARCHIVER_PID 2>/dev/null || true
|
||||
exit 0
|
||||
}
|
||||
trap cleanup SIGINT SIGTERM
|
||||
|
||||
# Start sequencer
|
||||
echo -e "${GREEN}Starting sequencer...${NC}"
|
||||
cd "$REPO_ROOT"
|
||||
"$SEQUENCER_BIN" &
|
||||
SEQUENCER_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Start archiver
|
||||
echo -e "${GREEN}Starting archiver...${NC}"
|
||||
"$ARCHIVER_BIN" &
|
||||
ARCHIVER_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Start frontend dev server
|
||||
echo -e "${GREEN}Starting frontend...${NC}"
|
||||
cd "$SCRIPT_DIR/webapp"
|
||||
|
||||
if ! command -v bun &> /dev/null; then
|
||||
echo -e "${RED}Error: bun is not installed. Install it with: curl -fsSL https://bun.sh/install | bash${NC}"
|
||||
cleanup
|
||||
fi
|
||||
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo -e "${YELLOW}Installing frontend dependencies...${NC}"
|
||||
bun install
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}======================================${NC}"
|
||||
echo -e "${GREEN} All services running!${NC}"
|
||||
echo -e "${GREEN}======================================${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Access points:${NC}"
|
||||
echo " Frontend: http://localhost:5173"
|
||||
echo " Frontend: http://$LOCAL_IP:5173 (share this with others on same network)"
|
||||
echo " Sequencer: $VITE_SEQUENCER_URL"
|
||||
echo " Archiver: $VITE_ARCHIVER_URL"
|
||||
echo " Explorer: $VITE_EXPLORER_URL"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Press Ctrl+C to stop all services${NC}"
|
||||
echo ""
|
||||
|
||||
# Run frontend in foreground
|
||||
bun run dev --host
|
||||
;;
|
||||
esac
|
||||
21
testnet/l2-sequencer-archival-demo/sequencer/.env.example
Normal file
@ -0,0 +1,21 @@
|
||||
# Address the sequencer HTTP server listens on
|
||||
export SEQUENCER_LISTEN_ADDR="0.0.0.0:8080"
|
||||
|
||||
# Nomos node HTTP endpoint to submit transactions
|
||||
export SEQUENCER_NODE_ENDPOINT="https://testnet.nomos.tech/node/1/"
|
||||
|
||||
# Path to store the redb database
|
||||
export SEQUENCER_DB_PATH="./sequencer.db"
|
||||
|
||||
# Path to store/load the signing key (persists across restarts)
|
||||
export SEQUENCER_SIGNING_KEY_PATH="./sequencer.key"
|
||||
|
||||
# Channel ID for inscriptions (64-character hex string = 32 bytes)
|
||||
export SEQUENCER_CHANNEL_ID="6d656d636f696e00000000000000000000000000000000000000000000000000"
|
||||
|
||||
# Initial balance for new accounts (default: 1000)
|
||||
export SEQUENCER_INITIAL_BALANCE="1000"
|
||||
|
||||
# Basic auth credentials for node endpoint (optional)
|
||||
export SEQUENCER_NODE_AUTH_USERNAME=
|
||||
export SEQUENCER_NODE_AUTH_PASSWORD=
|
||||
32
testnet/l2-sequencer-archival-demo/sequencer/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
categories = { workspace = true }
|
||||
description = "Simple sequencer for demo - receives HTTP transactions and submits to Nomos node"
|
||||
edition = { workspace = true }
|
||||
keywords = { workspace = true }
|
||||
license = { workspace = true }
|
||||
name = "demo-sequencer"
|
||||
readme = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
version = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
axum = { default-features = false, features = ["http1", "http2", "json", "tokio"], version = "0.7.5" }
|
||||
common-http-client = { workspace = true }
|
||||
hex = "0.4"
|
||||
key-management-system-service = { workspace = true }
|
||||
nomos-core = { workspace = true }
|
||||
owo-colors = { default-features = false, version = "4.2.3" }
|
||||
rand = { workspace = true }
|
||||
redb = "2.2"
|
||||
reqwest = { features = ["json", "rustls-tls"], workspace = true }
|
||||
serde = { features = ["derive"], workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { default-features = false, features = ["macros", "net", "rt-multi-thread", "signal", "sync"], version = "1" }
|
||||
tokio-util = { version = "0.7" }
|
||||
tower-http = { version = "0.6.8", features = ["cors"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
179
testnet/l2-sequencer-archival-demo/sequencer/README.md
Normal file
@ -0,0 +1,179 @@
|
||||
# MemChain Sequencer Demo
|
||||
|
||||
A demo L2 sequencer that processes token transfers and inscribes block data onto the Logos Blockchain.
|
||||
It maintains account balances, batches transactions into blocks, and submits them as channel inscriptions.
|
||||
|
||||
## What It Does
|
||||
|
||||
1. **Accepts transfer requests** via a REST API
|
||||
2. **Maintains account balances** in a persistent database (redb)
|
||||
3. **Batches transactions** into blocks periodically
|
||||
4. **Inscribes blocks** onto the Logos Blockchain via channel inscriptions
|
||||
5. **Tracks confirmation status** by monitoring inscribed blocks on-chain
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
cargo build --release -p demo-sequencer
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `SEQUENCER_LISTEN_ADDR` | HTTP server listen address | `0.0.0.0:8080` |
|
||||
| `SEQUENCER_NODE_ENDPOINT` | Nomos node HTTP endpoint | `http://localhost:18080` |
|
||||
| `SEQUENCER_DB_PATH` | Path to redb database file | `sequencer.redb` |
|
||||
| `SEQUENCER_SIGNING_KEY_PATH` | Path to signing key file (created if missing) | `sequencer.key` |
|
||||
| `SEQUENCER_CHANNEL_ID` | Channel ID for inscriptions (64 hex chars) | **Required** |
|
||||
| `SEQUENCER_INITIAL_BALANCE` | Initial token balance for new accounts | `1000` |
|
||||
| `SEQUENCER_NODE_AUTH_USERNAME` | Basic auth username for Nomos node (optional) | - |
|
||||
| `SEQUENCER_NODE_AUTH_PASSWORD` | Basic auth password for Nomos node (optional) | - |
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
export SEQUENCER_LISTEN_ADDR=0.0.0.0:3000
|
||||
export SEQUENCER_NODE_ENDPOINT=http://localhost:18080
|
||||
export SEQUENCER_DB_PATH=./data/sequencer.redb
|
||||
export SEQUENCER_SIGNING_KEY_PATH=./data/sequencer.key
|
||||
export SEQUENCER_INITIAL_BALANCE=1000
|
||||
export SEQUENCER_NODE_AUTH_USERNAME=admin
|
||||
export SEQUENCER_NODE_AUTH_PASSWORD=secret
|
||||
|
||||
./target/release/demo-sequencer
|
||||
```
|
||||
|
||||
### Using a `.env` File
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```env
|
||||
SEQUENCER_LISTEN_ADDR=0.0.0.0:3000
|
||||
SEQUENCER_NODE_ENDPOINT=http://localhost:18080
|
||||
SEQUENCER_DB_PATH=./data/sequencer.redb
|
||||
SEQUENCER_SIGNING_KEY_PATH=./data/sequencer.key
|
||||
SEQUENCER_INITIAL_BALANCE=1000
|
||||
SEQUENCER_NODE_AUTH_USERNAME=admin
|
||||
SEQUENCER_NODE_AUTH_PASSWORD=secret
|
||||
```
|
||||
|
||||
Then run with a tool like `dotenv`:
|
||||
|
||||
```bash
|
||||
dotenv ./target/release/demo-sequencer
|
||||
```
|
||||
|
||||
## HTTP API
|
||||
|
||||
### POST `/transfer`
|
||||
|
||||
Submit a token transfer between accounts.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/transfer \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"from": "alice", "to": "bob", "amount": 100}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"from_balance": 900,
|
||||
"to_balance": 1100,
|
||||
"tx_hash": "a1b2c3d4..."
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/accounts/:account`
|
||||
|
||||
Get account balance and optionally transaction history.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
# Balance only
|
||||
curl http://localhost:8080/accounts/alice
|
||||
|
||||
# With transaction history
|
||||
curl http://localhost:8080/accounts/alice?tx=true
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"account": "alice",
|
||||
"balance": 900,
|
||||
"confirmed_balance": 900,
|
||||
"transactions": [
|
||||
{
|
||||
"id": "abc123...",
|
||||
"from": "alice",
|
||||
"to": "bob",
|
||||
"amount": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/accounts`
|
||||
|
||||
List all accounts and their balances.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/accounts
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"accounts": [
|
||||
{ "account": "alice", "balance": 900 },
|
||||
{ "account": "bob", "balance": 1100 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/health`
|
||||
|
||||
Health check endpoint.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/health
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```
|
||||
OK
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
The sequencer uses `tracing` for structured logging. Control log level via the `RUST_LOG` environment variable:
|
||||
|
||||
```bash
|
||||
# Debug logging
|
||||
RUST_LOG=debug ./target/release/demo-sequencer
|
||||
|
||||
# Only show warnings and errors
|
||||
RUST_LOG=warn ./target/release/demo-sequencer
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
- **Database:** Account balances and transaction history are stored in a [redb](https://github.com/cberner/redb) database file
|
||||
- **Signing Key:** An Ed25519 signing key is generated on first run and stored at the configured path
|
||||
|
||||
Both files are created automatically if they don't exist.
|
||||
202
testnet/l2-sequencer-archival-demo/sequencer/src/api.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
};
|
||||
use demo_sequencer::{Transaction, TransferRequest, db::DbError};
|
||||
use reqwest::{Method, header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::sequencer::{Sequencer, SequencerError};
|
||||
|
||||
pub type AppState = Arc<Sequencer>;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
fn friendly_error(err: &SequencerError) -> String {
|
||||
match err {
|
||||
SequencerError::Db(db_err) => match db_err.as_ref() {
|
||||
DbError::InsufficientBalance {
|
||||
account,
|
||||
balance,
|
||||
required,
|
||||
} => {
|
||||
format!("Insufficient balance: {account} has {balance} tokens but needs {required}")
|
||||
}
|
||||
DbError::SelfTransfer { account } => {
|
||||
format!("Cannot transfer to yourself ({account})")
|
||||
}
|
||||
_ => "Internal database error".to_owned(),
|
||||
},
|
||||
SequencerError::Timeout => "Transaction timed out waiting for confirmation".to_owned(),
|
||||
SequencerError::Serialization(_) => "Invalid transaction data".to_owned(),
|
||||
_ => "Internal server error".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BalanceResponse {
|
||||
pub account: String,
|
||||
pub balance: u64,
|
||||
pub confirmed_balance: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transactions: Option<Vec<Transaction>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AccountQuery {
|
||||
#[serde(default)]
|
||||
pub tx: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AccountEntry {
|
||||
pub account: String,
|
||||
pub balance: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AccountsResponse {
|
||||
pub accounts: Vec<AccountEntry>,
|
||||
}
|
||||
|
||||
/// POST /transfer
|
||||
/// Request body: { "from": "alice", "to": "bob", "amount": 100 }
|
||||
async fn transfer(
|
||||
State(sequencer): State<AppState>,
|
||||
Json(request): Json<TransferRequest>,
|
||||
) -> impl IntoResponse {
|
||||
debug!(
|
||||
"API /transfer {} -> {} ({})",
|
||||
request.from, request.to, request.amount
|
||||
);
|
||||
|
||||
match sequencer.process_transfer(request).await {
|
||||
Ok(response) => (StatusCode::OK, Json(response)).into_response(),
|
||||
Err(e) => {
|
||||
error!("Transfer failed: {e}");
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(ErrorResponse {
|
||||
error: friendly_error(&e),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_account_data(
|
||||
sequencer: &Sequencer,
|
||||
account: &str,
|
||||
include_tx: bool,
|
||||
) -> Result<(u64, u64, Option<Vec<Transaction>>), String> {
|
||||
let balance = sequencer
|
||||
.get_balance(account)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let confirmed_balance = sequencer
|
||||
.get_confirmed_balance(account)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let transactions = if include_tx {
|
||||
Some(
|
||||
sequencer
|
||||
.get_account_transactions(account)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((balance, confirmed_balance, transactions))
|
||||
}
|
||||
|
||||
/// GET /accounts/{account}?tx=true
|
||||
async fn get_balance(
|
||||
State(sequencer): State<AppState>,
|
||||
axum::extract::Path(account): axum::extract::Path<String>,
|
||||
axum::extract::Query(query): axum::extract::Query<AccountQuery>,
|
||||
) -> impl IntoResponse {
|
||||
debug!("API /accounts/{}", account);
|
||||
|
||||
match fetch_account_data(&sequencer, &account, query.tx).await {
|
||||
Ok((balance, confirmed_balance, transactions)) => (
|
||||
StatusCode::OK,
|
||||
Json(BalanceResponse {
|
||||
account,
|
||||
balance,
|
||||
confirmed_balance,
|
||||
transactions,
|
||||
}),
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
error!("Get account failed: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(ErrorResponse { error: e }),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GET /accounts
|
||||
/// Returns all accounts and their balances
|
||||
async fn list_accounts(State(sequencer): State<AppState>) -> impl IntoResponse {
|
||||
debug!("API /accounts");
|
||||
|
||||
match sequencer.list_accounts().await {
|
||||
Ok(accounts) => {
|
||||
let accounts = accounts
|
||||
.into_iter()
|
||||
.map(|(account, balance)| AccountEntry { account, balance })
|
||||
.collect();
|
||||
(StatusCode::OK, Json(AccountsResponse { accounts })).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("List accounts failed: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(ErrorResponse {
|
||||
error: e.to_string(),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GET /health
|
||||
/// Health check endpoint
|
||||
async fn health() -> impl IntoResponse {
|
||||
(StatusCode::OK, "OK")
|
||||
}
|
||||
|
||||
pub fn create_router(sequencer: Arc<Sequencer>) -> axum::Router {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
|
||||
.allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION]);
|
||||
|
||||
axum::Router::new()
|
||||
.route("/transfer", post(transfer))
|
||||
.route("/accounts/:account", get(get_balance))
|
||||
.route("/accounts", get(list_accounts))
|
||||
.route("/health", get(health))
|
||||
.with_state(sequencer)
|
||||
.layer(cors)
|
||||
}
|
||||
69
testnet/l2-sequencer-archival-demo/sequencer/src/config.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
/// HTTP server listen address (e.g., "0.0.0.0:8080")
|
||||
pub listen_addr: SocketAddr,
|
||||
/// Nomos node HTTP endpoint to submit transactions to (e.g., "<http://localhost:18080>")
|
||||
pub node_endpoint: String,
|
||||
/// Path to the redb database file
|
||||
pub db_path: String,
|
||||
/// Path to the signing key file (will be created if it doesn't exist)
|
||||
pub signing_key_path: String,
|
||||
/// Channel ID for inscriptions (hex string, will be padded/truncated to 32
|
||||
/// bytes)
|
||||
pub channel_id: String,
|
||||
/// Initial balance for new accounts
|
||||
#[serde(default = "default_initial_balance")]
|
||||
pub initial_balance: u64,
|
||||
/// Basic auth username for node endpoint (optional)
|
||||
pub node_auth_username: Option<String>,
|
||||
/// Basic auth password for node endpoint (optional)
|
||||
pub node_auth_password: Option<String>,
|
||||
}
|
||||
|
||||
const fn default_initial_balance() -> u64 {
|
||||
1000
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
listen_addr: "0.0.0.0:8080".parse().expect("valid address"),
|
||||
node_endpoint: "http://localhost:18080".to_owned(),
|
||||
db_path: "sequencer.redb".to_owned(),
|
||||
signing_key_path: "sequencer.key".to_owned(),
|
||||
channel_id: String::new(),
|
||||
initial_balance: default_initial_balance(),
|
||||
node_auth_username: None,
|
||||
node_auth_password: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> Self {
|
||||
Self {
|
||||
listen_addr: std::env::var("SEQUENCER_LISTEN_ADDR")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(|| "0.0.0.0:8080".parse().unwrap()),
|
||||
node_endpoint: std::env::var("SEQUENCER_NODE_ENDPOINT")
|
||||
.unwrap_or_else(|_| "http://localhost:18080".to_owned()),
|
||||
db_path: std::env::var("SEQUENCER_DB_PATH")
|
||||
.unwrap_or_else(|_| "sequencer.redb".to_owned()),
|
||||
signing_key_path: std::env::var("SEQUENCER_SIGNING_KEY_PATH")
|
||||
.unwrap_or_else(|_| "sequencer.key".to_owned()),
|
||||
channel_id: std::env::var("SEQUENCER_CHANNEL_ID")
|
||||
.expect("SEQUENCER_CHANNEL_ID env var is required"),
|
||||
initial_balance: std::env::var("SEQUENCER_INITIAL_BALANCE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(default_initial_balance),
|
||||
node_auth_username: std::env::var("SEQUENCER_NODE_AUTH_USERNAME").ok(),
|
||||
node_auth_password: std::env::var("SEQUENCER_NODE_AUTH_PASSWORD").ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
11
testnet/l2-sequencer-archival-demo/sequencer/src/ctrl_c.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use owo_colors::OwoColorize as _;
|
||||
use tokio::signal::ctrl_c;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub fn listen_for_sigint(cancellation_token: CancellationToken) {
|
||||
tokio::spawn(async move {
|
||||
ctrl_c().await.unwrap();
|
||||
println!("\n {} Graceful shutdown initiated...", "\u{1f6d1}".red());
|
||||
cancellation_token.cancel();
|
||||
});
|
||||
}
|
||||
327
testnet/l2-sequencer-archival-demo/sequencer/src/db.rs
Normal file
@ -0,0 +1,327 @@
|
||||
use core::iter::once;
|
||||
use std::sync::Arc;
|
||||
|
||||
use redb::{Database, ReadableTable as _, ReadableTableMetadata as _, TableDefinition};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
const ACCOUNTS_TABLE: TableDefinition<&str, u64> = TableDefinition::new("accounts");
|
||||
const STATE_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("state");
|
||||
const COUNTER_TABLE: TableDefinition<&str, u64> = TableDefinition::new("counters");
|
||||
// Queue table: key is tx_id, value is JSON-serialized PendingTransfer
|
||||
const QUEUE_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("queue");
|
||||
// Transactions table: key is tx_id, value is JSON-serialized Transaction
|
||||
const TRANSACTIONS_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("transactions");
|
||||
const LAST_MSG_ID_KEY: &str = "last_msg_id";
|
||||
const BLOCK_ID_KEY: &str = "block_id";
|
||||
const TX_INDEX_KEY: &str = "tx_index";
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DbError {
|
||||
#[error("Database error: {0}")]
|
||||
Database(#[from] redb::DatabaseError),
|
||||
#[error("Transaction error: {0}")]
|
||||
Transaction(#[from] Box<redb::TransactionError>),
|
||||
#[error("Table error: {0}")]
|
||||
Table(#[from] redb::TableError),
|
||||
#[error("Storage error: {0}")]
|
||||
Storage(#[from] redb::StorageError),
|
||||
#[error("Commit error: {0}")]
|
||||
Commit(#[from] redb::CommitError),
|
||||
#[error("Insufficient balance: account {account} has {balance}, needs {required}")]
|
||||
InsufficientBalance {
|
||||
account: String,
|
||||
balance: u64,
|
||||
required: u64,
|
||||
},
|
||||
#[error("Cannot transfer to self: {account}")]
|
||||
SelfTransfer { account: String },
|
||||
}
|
||||
|
||||
impl From<redb::TransactionError> for DbError {
|
||||
fn from(err: redb::TransactionError) -> Self {
|
||||
Self::Transaction(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DbError>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AccountDb {
|
||||
db: Arc<RwLock<Database>>,
|
||||
initial_balance: u64,
|
||||
}
|
||||
|
||||
impl AccountDb {
|
||||
pub fn new(path: &str, initial_balance: u64) -> Result<Self> {
|
||||
let db = Database::create(path)?;
|
||||
|
||||
// Create the tables if they don't exist
|
||||
let write_txn = db.begin_write()?;
|
||||
{
|
||||
drop(write_txn.open_table(ACCOUNTS_TABLE)?);
|
||||
drop(write_txn.open_table(STATE_TABLE)?);
|
||||
drop(write_txn.open_table(COUNTER_TABLE)?);
|
||||
drop(write_txn.open_table(QUEUE_TABLE)?);
|
||||
drop(write_txn.open_table(TRANSACTIONS_TABLE)?);
|
||||
};
|
||||
write_txn.commit()?;
|
||||
|
||||
Ok(Self {
|
||||
db: Arc::new(RwLock::new(db)),
|
||||
initial_balance,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the balance of an account, creating it with initial balance if it
|
||||
/// doesn't exist.
|
||||
pub async fn get_or_create_balance(&self, account: &str) -> Result<u64> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
|
||||
let balance = {
|
||||
let mut table = write_txn.open_table(ACCOUNTS_TABLE)?;
|
||||
|
||||
if let Some(existing) = table.get(account)? {
|
||||
existing.value()
|
||||
} else {
|
||||
// New account - initialize with initial balance
|
||||
table.insert(account, self.initial_balance)?;
|
||||
self.initial_balance
|
||||
}
|
||||
};
|
||||
|
||||
write_txn.commit()?;
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
/// Transfer amount from one account to another.
|
||||
/// Creates accounts with initial balance if they don't exist.
|
||||
/// Returns error if sender has insufficient balance or if from == to.
|
||||
pub async fn transfer(&self, from: &str, to: &str, amount: u64) -> Result<(u64, u64)> {
|
||||
self.try_apply_transfers(once((from, to, amount))).await?;
|
||||
let read_txn = self
|
||||
.db
|
||||
.read()
|
||||
.await
|
||||
.begin_read()?
|
||||
.open_table(ACCOUNTS_TABLE)?;
|
||||
|
||||
Ok((
|
||||
read_txn.get(from)?.unwrap().value(),
|
||||
read_txn.get(to)?.unwrap().value(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn try_apply_transfers(
|
||||
&self,
|
||||
transfers: impl Iterator<Item = (&str, &str, u64)>,
|
||||
) -> Result<()> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(ACCOUNTS_TABLE)?;
|
||||
|
||||
for (from, to, amount) in transfers {
|
||||
if from == to {
|
||||
return Err(DbError::SelfTransfer {
|
||||
account: from.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
// Get or create 'from' account balance
|
||||
let from_balance = if let Some(existing) = table.get(&from)? {
|
||||
existing.value()
|
||||
} else {
|
||||
table.insert(&from, self.initial_balance)?;
|
||||
self.initial_balance
|
||||
};
|
||||
|
||||
// Check if sender has enough balance
|
||||
if from_balance < amount {
|
||||
return Err(DbError::InsufficientBalance {
|
||||
account: from.to_owned(),
|
||||
balance: from_balance,
|
||||
required: amount,
|
||||
});
|
||||
}
|
||||
|
||||
// Get or create 'to' account balance
|
||||
let to_balance = if let Some(existing) = table.get(&to)? {
|
||||
existing.value()
|
||||
} else {
|
||||
table.insert(&to, self.initial_balance)?;
|
||||
self.initial_balance
|
||||
};
|
||||
|
||||
// Perform the transfer
|
||||
let new_from_balance = from_balance - amount;
|
||||
let new_to_balance = to_balance + amount;
|
||||
|
||||
table.insert(&from, new_from_balance)?;
|
||||
table.insert(&to, new_to_balance)?;
|
||||
}
|
||||
}
|
||||
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all accounts and their balances
|
||||
pub async fn list_accounts(&self) -> Result<Vec<(String, u64)>> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
let table = read_txn.open_table(ACCOUNTS_TABLE)?;
|
||||
|
||||
let mut accounts = Vec::new();
|
||||
for entry in table.iter()? {
|
||||
let (key, value) = entry?;
|
||||
accounts.push((key.value().to_owned(), value.value()));
|
||||
}
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
/// Get the last message ID, returns None if not set (use root)
|
||||
pub async fn get_last_msg_id(&self) -> Result<Option<[u8; 32]>> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
let table = read_txn.open_table(STATE_TABLE)?;
|
||||
|
||||
if let Some(value) = table.get(LAST_MSG_ID_KEY)? {
|
||||
let bytes = value.value();
|
||||
if bytes.len() == 32 {
|
||||
let mut arr = [0u8; 32];
|
||||
arr.copy_from_slice(bytes);
|
||||
return Ok(Some(arr));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Set the last message ID
|
||||
pub async fn set_last_msg_id(&self, msg_id: &[u8; 32]) -> Result<()> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(STATE_TABLE)?;
|
||||
table.insert(LAST_MSG_ID_KEY, msg_id.as_slice())?;
|
||||
};
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the next block ID and increment the counter
|
||||
/// Returns (`new_block_id`, `parent_block_id`) where parent is 0 for
|
||||
/// genesis
|
||||
pub async fn next_block_id(&self) -> Result<(u64, u64)> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
|
||||
let (block_id, parent_id) = {
|
||||
let mut table = write_txn.open_table(COUNTER_TABLE)?;
|
||||
let current = table.get(BLOCK_ID_KEY)?.map_or(0, |v| v.value());
|
||||
let next = current + 1;
|
||||
table.insert(BLOCK_ID_KEY, next)?;
|
||||
(next, current)
|
||||
};
|
||||
|
||||
write_txn.commit()?;
|
||||
Ok((block_id, parent_id))
|
||||
}
|
||||
|
||||
/// Get the next transaction index and increment the counter
|
||||
pub async fn next_tx_index(&self) -> Result<u64> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
|
||||
let tx_index = {
|
||||
let mut table = write_txn.open_table(COUNTER_TABLE)?;
|
||||
let current = table.get(TX_INDEX_KEY)?.map_or(0, |v| v.value());
|
||||
let next = current + 1;
|
||||
table.insert(TX_INDEX_KEY, next)?;
|
||||
next
|
||||
};
|
||||
|
||||
write_txn.commit()?;
|
||||
Ok(tx_index)
|
||||
}
|
||||
|
||||
/// Add a pending transfer to the queue
|
||||
pub async fn queue_push(&self, tx_id: &str, data: &[u8]) -> Result<()> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(QUEUE_TABLE)?;
|
||||
table.insert(tx_id, data)?;
|
||||
};
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all pending transfers from the queue and clear it
|
||||
pub async fn queue_drain(&self) -> Result<Vec<(String, Vec<u8>)>> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
|
||||
let items = {
|
||||
let mut table = write_txn.open_table(QUEUE_TABLE)?;
|
||||
let mut items = Vec::new();
|
||||
for entry in table.iter()? {
|
||||
let (key, value) = entry?;
|
||||
items.push((key.value().to_owned(), value.value().to_vec()));
|
||||
}
|
||||
// Clear the queue
|
||||
for (key, _) in &items {
|
||||
table.remove(key.as_str())?;
|
||||
}
|
||||
items
|
||||
};
|
||||
|
||||
write_txn.commit()?;
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
/// Check if queue is empty
|
||||
pub async fn queue_is_empty(&self) -> Result<bool> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
let table = read_txn.open_table(QUEUE_TABLE)?;
|
||||
Ok(table.is_empty()?)
|
||||
}
|
||||
|
||||
/// Get queue length
|
||||
pub async fn queue_len(&self) -> Result<u64> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
let table = read_txn.open_table(QUEUE_TABLE)?;
|
||||
Ok(table.len()?)
|
||||
}
|
||||
|
||||
/// Save a transaction to the transactions table
|
||||
pub async fn save_transaction(&self, tx_id: &str, data: &[u8]) -> Result<()> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(TRANSACTIONS_TABLE)?;
|
||||
table.insert(tx_id, data)?;
|
||||
};
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_transaction(&self, tx_id: &str) -> Result<()> {
|
||||
let write_txn = self.db.write().await.begin_write()?;
|
||||
{
|
||||
let mut table = write_txn.open_table(TRANSACTIONS_TABLE)?;
|
||||
table.remove(tx_id)?;
|
||||
};
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all raw transaction data from the database
|
||||
pub async fn get_all_transactions_raw(&self) -> Result<Vec<Vec<u8>>> {
|
||||
let read_txn = self.db.read().await.begin_read()?;
|
||||
let table = read_txn.open_table(TRANSACTIONS_TABLE)?;
|
||||
|
||||
let mut transactions = Vec::new();
|
||||
for entry in table.iter()? {
|
||||
let (_key, value) = entry?;
|
||||
transactions.push(value.value().to_vec());
|
||||
}
|
||||
Ok(transactions)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn initial_balance(&self) -> u64 {
|
||||
self.initial_balance
|
||||
}
|
||||
}
|
||||
42
testnet/l2-sequencer-archival-demo/sequencer/src/lib.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod db;
|
||||
|
||||
/// Request to transfer funds between accounts
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TransferRequest {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
/// Transaction with unique ID for on-chain inscription
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Transaction {
|
||||
pub id: String,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub amount: u64,
|
||||
#[serde(default)]
|
||||
pub confirmed: bool,
|
||||
#[serde(default)]
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
/// Response after successful transfer
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TransferResponse {
|
||||
pub from_balance: u64,
|
||||
pub to_balance: u64,
|
||||
pub tx_hash: String,
|
||||
}
|
||||
|
||||
/// Block data format inscribed on-chain
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BlockData {
|
||||
pub block_id: u64,
|
||||
/// Parent block ID (0 for genesis)
|
||||
#[serde(default)]
|
||||
pub parent_block_id: u64,
|
||||
pub transactions: Vec<Transaction>,
|
||||
}
|
||||
114
testnet/l2-sequencer-archival-demo/sequencer/src/main.rs
Normal file
@ -0,0 +1,114 @@
|
||||
mod api;
|
||||
mod config;
|
||||
mod ctrl_c;
|
||||
mod sequencer;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use demo_sequencer::db::AccountDb;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
||||
|
||||
use crate::{api::create_router, config::Config, ctrl_c::listen_for_sigint, sequencer::Sequencer};
|
||||
|
||||
fn print_banner() {
|
||||
const BLUE: &str = "\x1b[38;5;39m";
|
||||
const RESET: &str = "\x1b[0m";
|
||||
println!(
|
||||
r"
|
||||
{BLUE} __ __ _____ _ _
|
||||
| \/ | ___ _ __ ___ / ____| |__ __ _(_)_ __
|
||||
| |\/| |/ _ \ '_ ` _ \| | | '_ \ / _` | | '_ \
|
||||
| | | | __/ | | | | | |____| | | | (_| | | | | |
|
||||
|_| |_|\___|_| |_| |_|\_____|_| |_|\__,_|_|_| |_|
|
||||
____
|
||||
/ ___| ___ __ _ _ _ ___ _ __ ___ ___ _ __
|
||||
\___ \ / _ \/ _` | | | |/ _ \ '_ \ / __/ _ \ '__|
|
||||
___) | __/ (_| | |_| | __/ | | | (_| __/ |
|
||||
|____/ \___|\__, |\__,_|\___|_| |_|\___\___|_|
|
||||
|_|{RESET}
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
print_banner();
|
||||
info!("MemChainSequencer starting up...");
|
||||
|
||||
// Load configuration
|
||||
let config = Config::from_env();
|
||||
info!("Configuration");
|
||||
info!(" HTTP API: {}", config.listen_addr);
|
||||
info!(" Nomos Node: {}", config.node_endpoint);
|
||||
info!(" Database: {}", config.db_path);
|
||||
info!(" Channel ID: {}", config.channel_id);
|
||||
info!(" Initial funds: {} tokens", config.initial_balance);
|
||||
|
||||
// Initialize database
|
||||
let db = match AccountDb::new(&config.db_path, config.initial_balance) {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
error!("Database initialization failed: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
info!("Database ready");
|
||||
|
||||
// Initialize sequencer
|
||||
let sequencer = match Sequencer::new(
|
||||
db,
|
||||
&config.node_endpoint,
|
||||
&config.signing_key_path,
|
||||
&config.channel_id,
|
||||
config.node_auth_username,
|
||||
config.node_auth_password,
|
||||
) {
|
||||
Ok(s) => Arc::new(s),
|
||||
Err(e) => {
|
||||
error!("Sequencer initialization failed: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
info!("Sequencer ready");
|
||||
|
||||
// Setup cancellation token for graceful shutdown
|
||||
let cancellation_token = CancellationToken::new();
|
||||
listen_for_sigint(cancellation_token.clone());
|
||||
|
||||
// Spawn background processing loop
|
||||
let sequencer_clone = Arc::clone(&sequencer);
|
||||
tokio::spawn(async move {
|
||||
sequencer_clone.run_processing_loop().await;
|
||||
});
|
||||
info!("Background processor started");
|
||||
|
||||
// Create HTTP router
|
||||
let app = create_router(sequencer);
|
||||
|
||||
// Start HTTP server
|
||||
info!("MemChainSequencer listening on {}", config.listen_addr);
|
||||
let listener = match tokio::net::TcpListener::bind(config.listen_addr).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
error!("Failed to bind: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let shutdown_signal = cancellation_token.cancelled_owned();
|
||||
if let Err(e) = axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal)
|
||||
.await
|
||||
{
|
||||
error!("Server error: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
625
testnet/l2-sequencer-archival-demo/sequencer/src/sequencer.rs
Normal file
@ -0,0 +1,625 @@
|
||||
use std::{collections::HashSet, fs, io, path::Path, time::Duration};
|
||||
|
||||
use common_http_client::CommonHttpClient;
|
||||
use demo_sequencer::{
|
||||
BlockData, Transaction, TransferRequest, TransferResponse,
|
||||
db::{AccountDb, DbError},
|
||||
};
|
||||
use key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key};
|
||||
use nomos_core::{
|
||||
header::HeaderId,
|
||||
mantle::{
|
||||
MantleTx, SignedMantleTx, Transaction as _,
|
||||
ledger::Tx as LedgerTx,
|
||||
ops::{
|
||||
Op, OpProof,
|
||||
channel::{ChannelId, Ed25519PublicKey, MsgId, inscribe::InscriptionOp},
|
||||
},
|
||||
tx::TxHash,
|
||||
},
|
||||
};
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SequencerError {
|
||||
#[error("Database error: {0}")]
|
||||
Db(#[from] Box<DbError>),
|
||||
#[error("HTTP client error: {0}")]
|
||||
Http(#[from] common_http_client::Error),
|
||||
#[error("URL parse error: {0}")]
|
||||
Url(String),
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Invalid key file: expected {expected} bytes, got {actual}")]
|
||||
InvalidKeyFile { expected: usize, actual: usize },
|
||||
#[error("{0}")]
|
||||
InvalidChannelId(String),
|
||||
#[error("Transaction not included after timeout")]
|
||||
Timeout,
|
||||
#[error("Serialization error: {0}")]
|
||||
Serialization(String),
|
||||
}
|
||||
|
||||
impl From<DbError> for SequencerError {
|
||||
fn from(err: DbError) -> Self {
|
||||
Self::Db(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, SequencerError>;
|
||||
|
||||
/// Pending transfer stored in the DB queue
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PendingTransfer {
|
||||
pub tx_id: String,
|
||||
#[serde(default)]
|
||||
pub tx_index: u64,
|
||||
pub request: TransferRequest,
|
||||
pub from_balance: u64,
|
||||
pub to_balance: u64,
|
||||
}
|
||||
|
||||
/// The sequencer that handles transactions
|
||||
pub struct Sequencer {
|
||||
db: AccountDb,
|
||||
http_client: CommonHttpClient,
|
||||
node_url: Url,
|
||||
signing_key: Ed25519Key,
|
||||
channel_id: ChannelId,
|
||||
}
|
||||
|
||||
const MAX_DEPTH_PER_POLL: usize = 50;
|
||||
|
||||
fn empty_ledger_signature(tx_hash: &TxHash) -> key_management_system_service::keys::ZkSignature {
|
||||
key_management_system_service::keys::ZkKey::multi_sign(&[], tx_hash.as_ref())
|
||||
.expect("multi-sign with empty key set works")
|
||||
}
|
||||
|
||||
/// Load signing key from file or generate a new one if it doesn't exist
|
||||
fn load_or_create_signing_key(path: &Path) -> Result<Ed25519Key> {
|
||||
if path.exists() {
|
||||
debug!("Loading existing signing key from {:?}", path);
|
||||
let key_bytes = fs::read(path)?;
|
||||
if key_bytes.len() != ED25519_SECRET_KEY_SIZE {
|
||||
return Err(SequencerError::InvalidKeyFile {
|
||||
expected: ED25519_SECRET_KEY_SIZE,
|
||||
actual: key_bytes.len(),
|
||||
});
|
||||
}
|
||||
let key_array: [u8; ED25519_SECRET_KEY_SIZE] =
|
||||
key_bytes.try_into().expect("length already checked");
|
||||
Ok(Ed25519Key::from_bytes(&key_array))
|
||||
} else {
|
||||
debug!("Generating new signing key and saving to {:?}", path);
|
||||
let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE];
|
||||
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes);
|
||||
fs::write(path, key_bytes)?;
|
||||
Ok(Ed25519Key::from_bytes(&key_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
pub fn new(
|
||||
db: AccountDb,
|
||||
node_endpoint: &str,
|
||||
signing_key_path: &str,
|
||||
channel_id_str: &str,
|
||||
node_auth_username: Option<String>,
|
||||
node_auth_password: Option<String>,
|
||||
) -> Result<Self> {
|
||||
let node_url = Url::parse(node_endpoint).map_err(|e| SequencerError::Url(e.to_string()))?;
|
||||
|
||||
let basic_auth = node_auth_username.map(|username| {
|
||||
common_http_client::BasicAuthCredentials::new(username, node_auth_password)
|
||||
});
|
||||
let http_client = CommonHttpClient::new(basic_auth);
|
||||
|
||||
// Load or generate the signing key
|
||||
let signing_key = load_or_create_signing_key(Path::new(signing_key_path))?;
|
||||
|
||||
// Decode channel ID from 64-character hex string (32 bytes)
|
||||
let decoded = hex::decode(channel_id_str).map_err(|_| {
|
||||
SequencerError::InvalidChannelId(format!(
|
||||
"SEQUENCER_CHANNEL_ID must be a valid hex string, got: '{channel_id_str}'"
|
||||
))
|
||||
})?;
|
||||
let channel_bytes: [u8; 32] = decoded.try_into().map_err(|v: Vec<u8>| {
|
||||
SequencerError::InvalidChannelId(format!(
|
||||
"SEQUENCER_CHANNEL_ID must be exactly 64 hex characters (32 bytes), got {} characters ({} bytes)",
|
||||
v.len() * 2,
|
||||
v.len()
|
||||
))
|
||||
})?;
|
||||
let channel_id = ChannelId::from(channel_bytes);
|
||||
info!("Channel ID: {}", hex::encode(channel_id.as_ref()));
|
||||
|
||||
Ok(Self {
|
||||
db,
|
||||
http_client,
|
||||
node_url,
|
||||
signing_key,
|
||||
channel_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the last message ID from the database, or root if not set
|
||||
async fn get_last_msg_id(&self) -> Result<MsgId> {
|
||||
(self.db.get_last_msg_id().await?)
|
||||
.map_or_else(|| Ok(MsgId::root()), |bytes| Ok(MsgId::from(bytes)))
|
||||
}
|
||||
|
||||
/// Save the last message ID to the database
|
||||
async fn set_last_msg_id(&self, msg_id: MsgId) -> Result<()> {
|
||||
let bytes: [u8; 32] = msg_id.into();
|
||||
self.db.set_last_msg_id(&bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create and sign a transaction for inscribing data
|
||||
fn create_inscribe_tx(&self, data: Vec<u8>, parent: MsgId) -> SignedMantleTx {
|
||||
let verifying_key_bytes = self.signing_key.public_key().to_bytes();
|
||||
let verifying_key =
|
||||
Ed25519PublicKey::from_bytes(&verifying_key_bytes).expect("valid ed25519 public key");
|
||||
|
||||
let inscribe_op = InscriptionOp {
|
||||
channel_id: self.channel_id,
|
||||
inscription: data,
|
||||
parent,
|
||||
signer: verifying_key,
|
||||
};
|
||||
|
||||
let ledger_tx = LedgerTx::new(vec![], vec![]);
|
||||
|
||||
let inscribe_tx = MantleTx {
|
||||
ops: vec![Op::ChannelInscribe(inscribe_op)],
|
||||
ledger_tx,
|
||||
storage_gas_price: 0,
|
||||
execution_gas_price: 0,
|
||||
};
|
||||
|
||||
let tx_hash = inscribe_tx.hash();
|
||||
let signature_bytes = self
|
||||
.signing_key
|
||||
.sign_payload(tx_hash.as_signing_bytes().as_ref())
|
||||
.to_bytes();
|
||||
let signature =
|
||||
key_management_system_service::keys::Ed25519Signature::from_bytes(&signature_bytes);
|
||||
|
||||
SignedMantleTx {
|
||||
ops_proofs: vec![OpProof::Ed25519Sig(signature)],
|
||||
ledger_tx_proof: empty_ledger_signature(&tx_hash),
|
||||
mantle_tx: inscribe_tx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Post a transaction to the node and wait for inclusion
|
||||
async fn post_and_wait(&self, tx: &SignedMantleTx) -> Result<()> {
|
||||
// Post the transaction
|
||||
self.http_client
|
||||
.post_transaction(self.node_url.clone(), tx.clone())
|
||||
.await?;
|
||||
|
||||
debug!("Transaction posted, waiting for inclusion...");
|
||||
|
||||
// Wait for the transaction to be included
|
||||
self.wait_for_inclusion(tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn block_contains_inscription(
|
||||
block: &nomos_core::block::Block<SignedMantleTx>,
|
||||
expected: &InscriptionOp,
|
||||
block_id: HeaderId,
|
||||
) -> bool {
|
||||
for tx in block.transactions() {
|
||||
for op in &tx.mantle_tx.ops {
|
||||
if let Op::ChannelInscribe(inscribe) = op {
|
||||
tracing::debug!(
|
||||
"Found inscription: channel={}, parent={}",
|
||||
hex::encode(inscribe.channel_id.as_ref()),
|
||||
hex::encode(<[u8; 32]>::from(inscribe.parent))
|
||||
);
|
||||
|
||||
if inscribe.inscription == expected.inscription
|
||||
&& inscribe.channel_id == expected.channel_id
|
||||
&& inscribe.parent == expected.parent
|
||||
{
|
||||
debug!("Transaction included in block {}", block_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Walk back from tip checking blocks for the expected inscription
|
||||
async fn check_blocks_for_inscription(
|
||||
&self,
|
||||
expected: &InscriptionOp,
|
||||
checked_blocks: &mut HashSet<HeaderId>,
|
||||
tip: HeaderId,
|
||||
) -> Result<bool> {
|
||||
let mut current_id = Some(tip);
|
||||
let mut depth = 0;
|
||||
|
||||
while let Some(block_id) = current_id {
|
||||
if checked_blocks.contains(&block_id) || depth >= MAX_DEPTH_PER_POLL {
|
||||
break;
|
||||
}
|
||||
|
||||
let Some(block) = self
|
||||
.http_client
|
||||
.get_block(self.node_url.clone(), block_id)
|
||||
.await?
|
||||
else {
|
||||
break;
|
||||
};
|
||||
|
||||
checked_blocks.insert(block_id);
|
||||
depth += 1;
|
||||
|
||||
tracing::debug!(
|
||||
"Checking block {} (depth {}): {} transactions",
|
||||
block_id,
|
||||
depth,
|
||||
block.transactions().len()
|
||||
);
|
||||
|
||||
if Self::block_contains_inscription(&block, expected, block_id) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
current_id = Some(block.header().parent());
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn get_expected_inscription(tx: &SignedMantleTx) -> &InscriptionOp {
|
||||
let expected_op = tx
|
||||
.mantle_tx
|
||||
.ops
|
||||
.first()
|
||||
.expect("transaction should have at least one op");
|
||||
|
||||
let Op::ChannelInscribe(expected_inscription) = expected_op else {
|
||||
panic!("Expected ChannelInscribe op")
|
||||
};
|
||||
|
||||
expected_inscription
|
||||
}
|
||||
|
||||
async fn poll_for_inclusion(
|
||||
&self,
|
||||
expected: &InscriptionOp,
|
||||
checked_blocks: &mut HashSet<HeaderId>,
|
||||
) -> Result<bool> {
|
||||
let info = self
|
||||
.http_client
|
||||
.consensus_info(self.node_url.clone())
|
||||
.await?;
|
||||
|
||||
tracing::debug!(
|
||||
"Polling: tip={}, height={}, checked_blocks={}",
|
||||
info.tip,
|
||||
info.height,
|
||||
checked_blocks.len()
|
||||
);
|
||||
|
||||
self.check_blocks_for_inscription(expected, checked_blocks, info.tip)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Wait for a transaction to be included in a block.
|
||||
async fn wait_for_inclusion(&self, tx: &SignedMantleTx) -> Result<()> {
|
||||
let expected_inscription = Self::get_expected_inscription(tx);
|
||||
|
||||
let timeout_duration = Duration::from_mins(5);
|
||||
let poll_interval = Duration::from_millis(500);
|
||||
let start = std::time::Instant::now();
|
||||
let mut checked_blocks: HashSet<HeaderId> = HashSet::new();
|
||||
|
||||
tracing::debug!(
|
||||
"Waiting for inscription: channel={}, parent={}",
|
||||
hex::encode(expected_inscription.channel_id.as_ref()),
|
||||
hex::encode(<[u8; 32]>::from(expected_inscription.parent))
|
||||
);
|
||||
|
||||
while start.elapsed() < timeout_duration {
|
||||
if self
|
||||
.poll_for_inclusion(expected_inscription, &mut checked_blocks)
|
||||
.await?
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
sleep(poll_interval).await;
|
||||
}
|
||||
|
||||
warn!(
|
||||
"Timeout waiting for chain inclusion after {:?}",
|
||||
timeout_duration
|
||||
);
|
||||
Err(SequencerError::Timeout)
|
||||
}
|
||||
|
||||
/// Process a transfer request - validates, updates DB, adds to queue,
|
||||
/// returns immediately
|
||||
pub async fn process_transfer(&self, request: TransferRequest) -> Result<TransferResponse> {
|
||||
info!(
|
||||
"TRANSFER {} -> {} ({} tokens)",
|
||||
request.from, request.to, request.amount
|
||||
);
|
||||
|
||||
// Validate and update balances in the database first
|
||||
let (from_balance, to_balance) = self
|
||||
.db
|
||||
.transfer(&request.from, &request.to, request.amount)
|
||||
.await?;
|
||||
|
||||
// Generate transaction ID
|
||||
let tx_id = {
|
||||
let mut id_bytes = [0u8; 16];
|
||||
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut id_bytes);
|
||||
hex::encode(id_bytes)
|
||||
};
|
||||
|
||||
// Save transaction immediately with confirmed=false
|
||||
let tx_index = self.db.next_tx_index().await?;
|
||||
let tx = Transaction {
|
||||
id: tx_id.clone(),
|
||||
from: request.from.clone(),
|
||||
to: request.to.clone(),
|
||||
amount: request.amount,
|
||||
confirmed: false,
|
||||
index: tx_index,
|
||||
};
|
||||
let tx_data =
|
||||
serde_json::to_vec(&tx).map_err(|e| SequencerError::Serialization(e.to_string()))?;
|
||||
self.db.save_transaction(&tx_id, &tx_data).await?;
|
||||
|
||||
// Create pending transfer and serialize for DB queue
|
||||
let pending = PendingTransfer {
|
||||
tx_id: tx_id.clone(),
|
||||
tx_index,
|
||||
request,
|
||||
from_balance,
|
||||
to_balance,
|
||||
};
|
||||
|
||||
let data = serde_json::to_vec(&pending)
|
||||
.map_err(|e| SequencerError::Serialization(e.to_string()))?;
|
||||
|
||||
// Add to DB queue
|
||||
self.db.queue_push(&tx_id, &data).await?;
|
||||
|
||||
let queue_len = self.db.queue_len().await?;
|
||||
debug!("Queued tx {} (queue size: {})", tx_id, queue_len);
|
||||
|
||||
// Return success immediately - actual on-chain posting happens in background
|
||||
Ok(TransferResponse {
|
||||
from_balance,
|
||||
to_balance,
|
||||
tx_hash: tx_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Background processing loop - call this in a spawned task
|
||||
pub async fn run_processing_loop(&self) {
|
||||
let poll_interval = Duration::from_millis(100);
|
||||
|
||||
loop {
|
||||
// Check if there are pending transfers
|
||||
let is_empty = match self.db.queue_is_empty().await {
|
||||
Ok(empty) => empty,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check queue: {}", e);
|
||||
sleep(poll_interval).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if is_empty {
|
||||
sleep(poll_interval).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Drain and process all pending transfers
|
||||
if let Err(e) = self.process_pending_batch().await {
|
||||
tracing::error!("Batch processing failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_pending_transfers(items: &[(String, Vec<u8>)]) -> Vec<PendingTransfer> {
|
||||
let mut pending = Vec::new();
|
||||
for (tx_id, data) in items {
|
||||
match serde_json::from_slice::<PendingTransfer>(data) {
|
||||
Ok(p) => pending.push(p),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to deserialize pending transfer {}: {}", tx_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
pending
|
||||
}
|
||||
|
||||
async fn revert_transfers(&self, pending: &[PendingTransfer]) {
|
||||
for p in pending {
|
||||
if let Err(revert_err) = self
|
||||
.db
|
||||
.transfer(&p.request.to, &p.request.from, p.request.amount)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to revert transfer {} -> {}: {}",
|
||||
p.request.from,
|
||||
p.request.to,
|
||||
revert_err
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"REVERTED {} -> {} ({} tokens)",
|
||||
p.request.from, p.request.to, p.request.amount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn confirm_transactions(&self, pending: &[PendingTransfer]) -> Result<()> {
|
||||
for p in pending {
|
||||
let tx = Transaction {
|
||||
id: p.tx_id.clone(),
|
||||
from: p.request.from.clone(),
|
||||
to: p.request.to.clone(),
|
||||
amount: p.request.amount,
|
||||
confirmed: true,
|
||||
index: p.tx_index,
|
||||
};
|
||||
let tx_data = serde_json::to_vec(&tx)
|
||||
.map_err(|e| SequencerError::Serialization(e.to_string()))?;
|
||||
self.db.save_transaction(&tx.id, &tx_data).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_and_post_block(
|
||||
&self,
|
||||
pending: &[PendingTransfer],
|
||||
) -> Result<(BlockData, MsgId)> {
|
||||
let (block_id, parent_block_id) = self.db.next_block_id().await?;
|
||||
let transactions: Vec<Transaction> = pending
|
||||
.iter()
|
||||
.map(|p| Transaction {
|
||||
id: p.tx_id.clone(),
|
||||
from: p.request.from.clone(),
|
||||
to: p.request.to.clone(),
|
||||
amount: p.request.amount,
|
||||
confirmed: false,
|
||||
index: p.tx_index,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let block_data = BlockData {
|
||||
block_id,
|
||||
parent_block_id,
|
||||
transactions,
|
||||
};
|
||||
|
||||
let inscription_data = serde_json::to_vec(&block_data)
|
||||
.map_err(|e| SequencerError::Serialization(e.to_string()))?;
|
||||
|
||||
info!(
|
||||
"BLOCK #{} (parent: #{}) posting to chain ({} tx)",
|
||||
block_id,
|
||||
parent_block_id,
|
||||
pending.len()
|
||||
);
|
||||
|
||||
let parent = self.get_last_msg_id().await?;
|
||||
let tx = self.create_inscribe_tx(inscription_data, parent);
|
||||
|
||||
let new_msg_id = match tx.mantle_tx.ops.first() {
|
||||
Some(Op::ChannelInscribe(inscribe)) => inscribe.id(),
|
||||
_ => panic!("Expected ChannelInscribe op"),
|
||||
};
|
||||
|
||||
self.post_and_wait(&tx).await?;
|
||||
|
||||
Ok((block_data, new_msg_id))
|
||||
}
|
||||
|
||||
/// Process all pending transfers as a single block
|
||||
async fn process_pending_batch(&self) -> Result<()> {
|
||||
let items = self.db.queue_drain().await?;
|
||||
if items.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let pending = Self::deserialize_pending_transfers(&items);
|
||||
if pending.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let count = pending.len();
|
||||
debug!("Processing batch of {} transfers", count);
|
||||
|
||||
match self.create_and_post_block(&pending).await {
|
||||
Ok((block_data, new_msg_id)) => {
|
||||
self.set_last_msg_id(new_msg_id).await?;
|
||||
self.confirm_transactions(&pending).await?;
|
||||
info!(
|
||||
"BLOCK #{} confirmed on chain ({} tx)",
|
||||
block_data.block_id, count
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
self.revert_transfers(&pending).await;
|
||||
self.delete_transactions(&pending).await;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_transactions(&self, pending: &[PendingTransfer]) {
|
||||
for p in pending {
|
||||
if let Err(e) = self.db.delete_transaction(&p.tx_id).await {
|
||||
warn!("Failed to delete transaction {}: {}", p.tx_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the balance of an account
|
||||
pub async fn get_balance(&self, account: &str) -> Result<u64> {
|
||||
Ok(self.db.get_or_create_balance(account).await?)
|
||||
}
|
||||
|
||||
/// List all accounts
|
||||
pub async fn list_accounts(&self) -> Result<Vec<(String, u64)>> {
|
||||
Ok(self.db.list_accounts().await?)
|
||||
}
|
||||
|
||||
/// Get all transactions for an account (as sender or receiver), sorted by
|
||||
/// index
|
||||
pub async fn get_account_transactions(&self, account: &str) -> Result<Vec<Transaction>> {
|
||||
let all_txs = self.db.get_all_transactions_raw().await?;
|
||||
let mut transactions: Vec<Transaction> = all_txs
|
||||
.iter()
|
||||
.filter_map(|data| serde_json::from_slice::<Transaction>(data).ok())
|
||||
.filter(|tx| tx.from == account || tx.to == account)
|
||||
.collect();
|
||||
|
||||
transactions.sort_by_key(|tx| tx.index);
|
||||
transactions.reverse();
|
||||
Ok(transactions)
|
||||
}
|
||||
|
||||
/// Get confirmed balance based only on confirmed transactions
|
||||
pub async fn get_confirmed_balance(&self, account: &str) -> Result<u64> {
|
||||
let initial_balance = self.db.initial_balance();
|
||||
let all_txs = self.db.get_all_transactions_raw().await?;
|
||||
|
||||
let mut balance = initial_balance;
|
||||
|
||||
for data in all_txs {
|
||||
if let Ok(tx) = serde_json::from_slice::<Transaction>(&data)
|
||||
&& tx.confirmed
|
||||
{
|
||||
if tx.from == account {
|
||||
balance = balance.saturating_sub(tx.amount);
|
||||
}
|
||||
if tx.to == account {
|
||||
balance = balance.saturating_add(tx.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(balance)
|
||||
}
|
||||
}
|
||||
1
testnet/l2-sequencer-archival-demo/webapp/.env.dev
Normal file
@ -0,0 +1 @@
|
||||
VITE_API_BASE_URL=http://localhost:8080
|
||||
1
testnet/l2-sequencer-archival-demo/webapp/.env.testnet
Normal file
@ -0,0 +1 @@
|
||||
VITE_API_BASE_URL=https://demo.testnet.nomos.tech
|
||||
21
testnet/l2-sequencer-archival-demo/webapp/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Simple L2 frontend
|
||||
|
||||
A website built with Vuejs, Headless UI and Tailwind.
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
768
testnet/l2-sequencer-archival-demo/webapp/bun.lock
Normal file
@ -0,0 +1,768 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "inkaguco",
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.16",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"heroicons": "^2.1.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.3.11",
|
||||
"vue-router": "^4.2.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"autoprefixer": "latest",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-vue": "^9.19.2",
|
||||
"tailwindcss": "latest",
|
||||
"vite": "^5.0.10",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
|
||||
|
||||
"@headlessui/vue": ["@headlessui/vue@1.7.23", "", { "dependencies": { "@tanstack/vue-virtual": "^3.0.0-beta.60" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg=="],
|
||||
|
||||
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.5", "", { "os": "android", "cpu": "arm" }, "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.5", "", { "os": "android", "cpu": "arm64" }, "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.5", "", { "os": "linux", "cpu": "arm" }, "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.5", "", { "os": "linux", "cpu": "arm" }, "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.5", "", { "os": "linux", "cpu": "none" }, "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.5", "", { "os": "linux", "cpu": "none" }, "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.5", "", { "os": "linux", "cpu": "none" }, "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.5", "", { "os": "linux", "cpu": "x64" }, "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.5", "", { "os": "none", "cpu": "arm64" }, "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.5", "", { "os": "win32", "cpu": "x64" }, "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.5", "", { "os": "win32", "cpu": "x64" }, "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ=="],
|
||||
|
||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||
|
||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.13", "", {}, "sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA=="],
|
||||
|
||||
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.13", "", { "dependencies": { "@tanstack/virtual-core": "3.13.13" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-Cf2xIEE8nWAfsX0N5nihkPYMeQRT+pHt4NEkuP8rNCn6lVnLDiV8rC8IeIxbKmQC0yPnj4SIBLwXYVf86xxKTQ=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@4.6.2", "", { "peerDependencies": { "vite": "^4.0.0 || ^5.0.0", "vue": "^3.2.25" } }, "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.25", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.25", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.25", "", { "dependencies": { "@vue/compiler-core": "3.5.25", "@vue/shared": "3.5.25" } }, "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q=="],
|
||||
|
||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.25", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.25", "@vue/compiler-dom": "3.5.25", "@vue/compiler-ssr": "3.5.25", "@vue/shared": "3.5.25", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag=="],
|
||||
|
||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.25", "", { "dependencies": { "@vue/compiler-dom": "3.5.25", "@vue/shared": "3.5.25" } }, "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A=="],
|
||||
|
||||
"@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||
|
||||
"@vue/reactivity": ["@vue/reactivity@3.5.25", "", { "dependencies": { "@vue/shared": "3.5.25" } }, "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA=="],
|
||||
|
||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.25", "", { "dependencies": { "@vue/reactivity": "3.5.25", "@vue/shared": "3.5.25" } }, "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA=="],
|
||||
|
||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.25", "", { "dependencies": { "@vue/reactivity": "3.5.25", "@vue/runtime-core": "3.5.25", "@vue/shared": "3.5.25", "csstype": "^3.1.3" } }, "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA=="],
|
||||
|
||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.25", "", { "dependencies": { "@vue/compiler-ssr": "3.5.25", "@vue/shared": "3.5.25" }, "peerDependencies": { "vue": "3.5.25" } }, "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.25", "", {}, "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
||||
"array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
|
||||
|
||||
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
|
||||
|
||||
"array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
|
||||
|
||||
"array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
|
||||
|
||||
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
|
||||
|
||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="],
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.7", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
|
||||
|
||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001760", "", {}, "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"confusing-browser-globals": ["confusing-browser-globals@1.0.11", "", {}, "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||
|
||||
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
|
||||
|
||||
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
||||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
|
||||
|
||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||
|
||||
"esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
|
||||
|
||||
"eslint-config-airbnb-base": ["eslint-config-airbnb-base@15.0.0", "", { "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", "object.entries": "^1.1.5", "semver": "^6.3.0" }, "peerDependencies": { "eslint": "^7.32.0 || ^8.2.0", "eslint-plugin-import": "^2.25.2" } }, "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig=="],
|
||||
|
||||
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
|
||||
|
||||
"eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="],
|
||||
|
||||
"eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
|
||||
|
||||
"eslint-plugin-vue": ["eslint-plugin-vue@9.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "vue-eslint-parser": "^9.4.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
|
||||
|
||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
||||
|
||||
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
||||
|
||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
||||
|
||||
"has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"heroicons": ["heroicons@2.2.0", "", {}, "sha512-yOwvztmNiBWqR946t+JdgZmyzEmnRMC2nxvHFC90bF1SUttwB6yJKYeme1JeEcBfobdOs827nCyiWBS2z/brog=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||
|
||||
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
|
||||
|
||||
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
|
||||
|
||||
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
|
||||
|
||||
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
|
||||
|
||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
||||
|
||||
"is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
||||
|
||||
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
||||
|
||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
||||
|
||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
||||
|
||||
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
|
||||
|
||||
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
|
||||
|
||||
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
|
||||
|
||||
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
|
||||
|
||||
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
|
||||
|
||||
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
|
||||
|
||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
||||
|
||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
||||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
|
||||
|
||||
"object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
|
||||
|
||||
"object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="],
|
||||
|
||||
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"pinia": ["pinia@2.3.1", "", { "dependencies": { "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" }, "peerDependencies": { "typescript": ">=4.4.4", "vue": "^2.7.0 || ^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
|
||||
|
||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"rollup": ["rollup@4.53.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.5", "@rollup/rollup-android-arm64": "4.53.5", "@rollup/rollup-darwin-arm64": "4.53.5", "@rollup/rollup-darwin-x64": "4.53.5", "@rollup/rollup-freebsd-arm64": "4.53.5", "@rollup/rollup-freebsd-x64": "4.53.5", "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", "@rollup/rollup-linux-arm-musleabihf": "4.53.5", "@rollup/rollup-linux-arm64-gnu": "4.53.5", "@rollup/rollup-linux-arm64-musl": "4.53.5", "@rollup/rollup-linux-loong64-gnu": "4.53.5", "@rollup/rollup-linux-ppc64-gnu": "4.53.5", "@rollup/rollup-linux-riscv64-gnu": "4.53.5", "@rollup/rollup-linux-riscv64-musl": "4.53.5", "@rollup/rollup-linux-s390x-gnu": "4.53.5", "@rollup/rollup-linux-x64-gnu": "4.53.5", "@rollup/rollup-linux-x64-musl": "4.53.5", "@rollup/rollup-openharmony-arm64": "4.53.5", "@rollup/rollup-win32-arm64-msvc": "4.53.5", "@rollup/rollup-win32-ia32-msvc": "4.53.5", "@rollup/rollup-win32-x64-gnu": "4.53.5", "@rollup/rollup-win32-x64-msvc": "4.53.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
||||
|
||||
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
|
||||
|
||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
|
||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||
|
||||
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
|
||||
|
||||
"string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
|
||||
|
||||
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
|
||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||
|
||||
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
|
||||
|
||||
"typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
|
||||
|
||||
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
|
||||
|
||||
"vue": ["vue@3.5.25", "", { "dependencies": { "@vue/compiler-dom": "3.5.25", "@vue/compiler-sfc": "3.5.25", "@vue/runtime-dom": "3.5.25", "@vue/server-renderer": "3.5.25", "@vue/shared": "3.5.25" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g=="],
|
||||
|
||||
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
||||
|
||||
"vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
|
||||
|
||||
"vue-router": ["vue-router@4.6.4", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
|
||||
"which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
|
||||
|
||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-plugin-import/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||
|
||||
"eslint-plugin-vue/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"vue-eslint-parser/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
}
|
||||
}
|
||||
13
testnet/l2-sequencer-archival-demo/webapp/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LOGOS COOKIE CHAIN L2 BLAZING FAST</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
testnet/l2-sequencer-archival-demo/webapp/jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
37
testnet/l2-sequencer-archival-demo/webapp/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "inkaguco",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --mode dev",
|
||||
"build": "vite build",
|
||||
"build:dev": "vite build --mode dev",
|
||||
"build:testnet": "vite build --mode testnet",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --fix src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.16",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"heroicons": "^2.1.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.3.11",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"autoprefixer": "latest",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-vue": "^9.19.2",
|
||||
"tailwindcss": "latest",
|
||||
"vite": "^5.0.10"
|
||||
},
|
||||
"module": "index.ts",
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
BIN
testnet/l2-sequencer-archival-demo/webapp/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 13 KiB |
527
testnet/l2-sequencer-archival-demo/webapp/public/favicon.svg
Normal file
@ -0,0 +1,527 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="4.4406605mm"
|
||||
height="3.803216mm"
|
||||
viewBox="0 0 4.4406605 3.803216"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter357"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix356" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix357" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter359"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix358" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix359" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter361"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix360" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix361" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter363"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix362" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix363" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter365"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix364" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix365" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter367"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix366" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix367" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter369"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix368" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix369" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter371"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix370" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix371" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter373"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1">
|
||||
<feColorMatrix
|
||||
type="hueRotate"
|
||||
values="180"
|
||||
result="color1"
|
||||
id="feColorMatrix372" />
|
||||
<feColorMatrix
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
|
||||
result="color2"
|
||||
id="feColorMatrix373" />
|
||||
</filter>
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-75.095468,-77.508589)">
|
||||
<g
|
||||
id="g27"
|
||||
transform="rotate(42.189961,59.167567,102.48634)">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect419"
|
||||
width="8.4005213"
|
||||
height="6.6807294"
|
||||
x="-3.8882036"
|
||||
y="95.595848"
|
||||
transform="rotate(-42.189961)" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect418"
|
||||
width="8.4005213"
|
||||
height="6.6807294"
|
||||
x="-2.5945358"
|
||||
y="89.80809"
|
||||
transform="rotate(-42.189961)" />
|
||||
<rect
|
||||
style="fill:#000080;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect19"
|
||||
width="2.8371327"
|
||||
height="0.52539498"
|
||||
x="46.383415"
|
||||
y="73.725441" />
|
||||
<rect
|
||||
style="fill:#ff8080;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect20"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="46.383415"
|
||||
y="73.988136" />
|
||||
<rect
|
||||
style="fill:#ff2a2a;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect21"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="48.019646"
|
||||
y="73.988136" />
|
||||
<rect
|
||||
style="fill:#00ff00;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect22"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="47.118965"
|
||||
y="71.769485" />
|
||||
<rect
|
||||
style="fill:#ffaaaa;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect23"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="48.755196"
|
||||
y="71.769485" />
|
||||
<rect
|
||||
style="fill:#ff6600;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect24"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="71.769485"
|
||||
y="-48.755196"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#ffff00;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect25"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="73.49276"
|
||||
y="-48.904259"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#008080;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect26"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="82.0989"
|
||||
y="-19.439381"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#010101;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect27"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="82.46711"
|
||||
y="-20.779257"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect28"
|
||||
width="2.8371327"
|
||||
height="0.52539498"
|
||||
x="51.404686"
|
||||
y="69.5159" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect29"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="51.404686"
|
||||
y="69.778595" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect30"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="53.040916"
|
||||
y="69.778595" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect31"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="52.140236"
|
||||
y="67.559944" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect32"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="53.776466"
|
||||
y="67.559944" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect33"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="67.559944"
|
||||
y="-53.776466"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect34"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="69.283218"
|
||||
y="-53.925529"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect35"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="78.824844"
|
||||
y="-25.693598"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#241c1c;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect36"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="79.193054"
|
||||
y="-27.033474"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#000080;fill-opacity:1;stroke-width:0.16;filter:url(#filter373)"
|
||||
id="rect102"
|
||||
width="2.8371327"
|
||||
height="0.52539498"
|
||||
x="50.074295"
|
||||
y="77.797348" />
|
||||
<rect
|
||||
style="fill:#ff8080;fill-opacity:1;stroke-width:0.16;filter:url(#filter371)"
|
||||
id="rect103"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="50.074295"
|
||||
y="78.060043" />
|
||||
<rect
|
||||
style="fill:#ff2a2a;fill-opacity:1;stroke-width:0.16;filter:url(#filter369)"
|
||||
id="rect104"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="51.710526"
|
||||
y="78.060043" />
|
||||
<rect
|
||||
style="fill:#00ff00;fill-opacity:1;stroke-width:0.16;filter:url(#filter367)"
|
||||
id="rect105"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="50.809845"
|
||||
y="75.841393" />
|
||||
<rect
|
||||
style="fill:#ffaaaa;fill-opacity:1;stroke-width:0.16;filter:url(#filter365)"
|
||||
id="rect106"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="52.446075"
|
||||
y="75.841393" />
|
||||
<rect
|
||||
style="fill:#ff6600;fill-opacity:1;stroke-width:0.160001;filter:url(#filter363)"
|
||||
id="rect107"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="75.841393"
|
||||
y="-52.446075"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#ffff00;fill-opacity:1;stroke-width:0.160001;filter:url(#filter361)"
|
||||
id="rect108"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="77.564667"
|
||||
y="-52.595139"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#008080;fill-opacity:1;stroke-width:0.161158;filter:url(#filter359)"
|
||||
id="rect109"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="86.841415"
|
||||
y="-21.529877"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#010101;fill-opacity:1;stroke-width:0.161158;filter:url(#filter357)"
|
||||
id="rect110"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="87.209625"
|
||||
y="-22.869753"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#c5ee00;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect400"
|
||||
width="2.8371327"
|
||||
height="0.52539498"
|
||||
x="55.595005"
|
||||
y="73.302124" />
|
||||
<rect
|
||||
style="fill:#80a3ff;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect401"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="55.595005"
|
||||
y="73.564819" />
|
||||
<rect
|
||||
style="fill:#6aedff;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect402"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="57.231236"
|
||||
y="73.564819" />
|
||||
<rect
|
||||
style="fill:#ff00e7;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect403"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="56.330555"
|
||||
y="71.346169" />
|
||||
<rect
|
||||
style="fill:#00cf7c;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect404"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="57.966785"
|
||||
y="71.346169" />
|
||||
<rect
|
||||
style="fill:#00afff;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect405"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="71.346169"
|
||||
y="-57.966785"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#002fff;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect406"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="73.069443"
|
||||
y="-58.115849"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#ff8c00;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect407"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="83.374405"
|
||||
y="-28.359638"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect408"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="83.742615"
|
||||
y="-29.699514"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#f7ff66;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect409"
|
||||
width="2.8371327"
|
||||
height="0.52539498"
|
||||
x="61.030411"
|
||||
y="68.464607" />
|
||||
<rect
|
||||
style="fill:#80a3ff;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect410"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="61.030411"
|
||||
y="68.727303" />
|
||||
<rect
|
||||
style="fill:#6aedff;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect411"
|
||||
width="0.46535105"
|
||||
height="1.2634716"
|
||||
x="62.666641"
|
||||
y="68.727303" />
|
||||
<rect
|
||||
style="fill:#ff00e7;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect412"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="61.765961"
|
||||
y="66.508652" />
|
||||
<rect
|
||||
style="fill:#aaffdd;fill-opacity:1;stroke-width:0.16"
|
||||
id="rect413"
|
||||
width="0.46535105"
|
||||
height="3.228164"
|
||||
x="63.402191"
|
||||
y="66.508652" />
|
||||
<rect
|
||||
style="fill:#00afff;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect414"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="66.508652"
|
||||
y="-63.402191"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#002fff;fill-opacity:1;stroke-width:0.160001"
|
||||
id="rect415"
|
||||
width="0.46535105"
|
||||
height="1.6362293"
|
||||
x="68.231926"
|
||||
y="-63.551254"
|
||||
transform="rotate(90)" />
|
||||
<rect
|
||||
style="fill:#ff8c00;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect416"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="79.550194"
|
||||
y="-35.227894"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.161158"
|
||||
id="rect417"
|
||||
width="0.21415083"
|
||||
height="1.0134755"
|
||||
x="79.918404"
|
||||
y="-36.567772"
|
||||
transform="matrix(0.3447906,0.93867963,-0.98335818,0.18167745,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
18
testnet/l2-sequencer-archival-demo/webapp/src/App.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<NavBar class="fixed top-0 pl-11 pr-11 left-0 right-0 z-10 h-16 mx-auto navbar" />
|
||||
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import NavBar from './components/navbar/NavbarCompact.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navbar {
|
||||
max-width: var(--app-max-w);
|
||||
margin: 1.5rem auto;
|
||||
}
|
||||
</style>
|
||||
104
testnet/l2-sequencer-archival-demo/webapp/src/assets/base.css
Normal file
@ -0,0 +1,104 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #000000;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
--color-border-neutral: var(--vt-c-white-mute);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
--app-max-w: 1280px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
--color-border-neutral: var(--vt-c-black-mute);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
|
||||
/* Only when not using tailwind dark=class strategy.
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 20px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>
|
||||
|
After Width: | Height: | Size: 963 B |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 314 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="194" height="194" version="1.1">
|
||||
<circle fill="#000000" cx="97" cy="97" r="97" />
|
||||
<path fill="#ffffff" d="m 94,9.2 a 88,88 0 0 0 -55,21.8 l 27,0 28,-14.4 0,-7.4 z m 6,0 0,7.4 28,14.4 27,0 a 88,88 0 0 0 -55,-21.8 z m -67.2,27.8 a 88,88 0 0 0 -20,34.2 l 16,27.6 23,-3.6 21,-36.2 -8.4,-22 -31.6,0 z m 96.8,0 -8.4,22 21,36.2 23,3.6 15.8,-27.4 a 88,88 0 0 0 -19.8,-34.4 l -31.6,0 z m -50,26 -20.2,35.2 17.8,30.8 39.6,0 17.8,-30.8 -20.2,-35.2 -34.8,0 z m -68.8,16.6 a 88,88 0 0 0 -1.8,17.4 88,88 0 0 0 10.4,41.4 l 7.4,-4.4 -1.4,-29 -14.6,-25.4 z m 172.4,0.2 -14.6,25.2 -1.4,29 7.4,4.4 a 88,88 0 0 0 10.4,-41.4 88,88 0 0 0 -1.8,-17.2 z m -106,57.2 -15.4,19 L 77.2,182.6 a 88,88 0 0 0 19.8,2.4 88,88 0 0 0 19.8,-2.4 l 15.4,-26.6 -15.4,-19 -39.6,0 z m -47.8,2.6 -7,4 A 88,88 0 0 0 68.8,180.4 l -14,-24.6 -25.4,-16.2 z m 135.2,0 -25.4,16.2 -14,24.4 a 88,88 0 0 0 46.4,-36.6 l -7,-4 z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 972 B |
@ -0,0 +1,53 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: var(--app-max-w);
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.weird-img {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
height: auto;
|
||||
transform-origin: bottom center;
|
||||
animation: wiggle 7s ease-in-out infinite;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wiggle {
|
||||
0%, 100% {
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
|
||||
<path fill-rule="evenodd" d="M9.528 1.718a.75.75 0 0 1 .162.819A8.97 8.97 0 0 0 9 6a9 9 0 0 0 9 9 8.97 8.97 0 0 0 3.463-.69.75.75 0 0 1 .981.98 10.503 10.503 0 0 1-9.694 6.46c-5.799 0-10.5-4.7-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 0 1 .818.162Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
|
||||
<path d="M12 2.25a.75.75 0 0 1 .75.75v2.25a.75.75 0 0 1-1.5 0V3a.75.75 0 0 1 .75-.75ZM7.5 12a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM18.894 6.166a.75.75 0 0 0-1.06-1.06l-1.591 1.59a.75.75 0 1 0 1.06 1.061l1.591-1.59ZM21.75 12a.75.75 0 0 1-.75.75h-2.25a.75.75 0 0 1 0-1.5H21a.75.75 0 0 1 .75.75ZM17.834 18.894a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 1 0-1.061 1.06l1.59 1.591ZM12 18a.75.75 0 0 1 .75.75V21a.75.75 0 0 1-1.5 0v-2.25A.75.75 0 0 1 12 18ZM7.758 17.303a.75.75 0 0 0-1.061-1.06l-1.591 1.59a.75.75 0 0 0 1.06 1.061l1.591-1.59ZM6 12a.75.75 0 0 1-.75.75H3a.75.75 0 0 1 0-1.5h2.25A.75.75 0 0 1 6 12ZM6.697 7.757a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 0 0-1.061 1.06l1.59 1.591Z" />
|
||||
</svg>
|
||||
</template>
|
||||
@ -0,0 +1,4 @@
|
||||
import IconMoon from './IconMoon.vue';
|
||||
import IconSun from './IconSun.vue';
|
||||
|
||||
export { IconMoon, IconSun };
|
||||
@ -0,0 +1,4 @@
|
||||
export * from './icons';
|
||||
export * from './navbar';
|
||||
export * from './ui';
|
||||
export * from './wallet';
|
||||
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="fixed top-4 left-0 right-0 z-50 flex justify-center px-4">
|
||||
<nav class="flex items-center justify-between w-full px-4 sm:px-6 py-3 rounded-full bg-white dark:bg-black border border-gray-100 dark:border-gray-800 transition-all duration-300">
|
||||
|
||||
<div class="flex items-center gap-8">
|
||||
<SpinningLogo class="h-7 w-7 sm:h-8 sm:w-8 flex-shrink-0 mr-8 sm:mr-16" />
|
||||
|
||||
<div class="flex items-center gap-8 sm:gap-16 text-[11px] sm:text-sm font-bold uppercase tracking-wider">
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors whitespace-nowrap"
|
||||
active-class="text-gray-900 dark:text-white"
|
||||
>
|
||||
Wallet
|
||||
</RouterLink>
|
||||
|
||||
<div class="h-4 w-[1px] bg-gray-200 dark:bg-gray-700"></div>
|
||||
|
||||
<RouterLink
|
||||
to="/archive"
|
||||
class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors whitespace-nowrap"
|
||||
active-class="text-gray-900 dark:text-white"
|
||||
>
|
||||
Archive
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-3 sm:space-x-5">
|
||||
<a
|
||||
href="https://github.com/logos-blockchain/logos-blockchain"
|
||||
class="hover:opacity-70 transition-opacity flex-shrink-0"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ThemeImg><img :src="githubSrc" class="h-4 w-4 sm:h-5 sm:w-5"/></ThemeImg>
|
||||
</a>
|
||||
<ThemeSwitch class="flex-shrink-0" />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router';
|
||||
import {
|
||||
ThemeImg,
|
||||
SpinningLogo,
|
||||
ThemeSwitch
|
||||
} from '@/components';
|
||||
import githubSrc from '@/assets/github-mark.svg';
|
||||
</script>
|
||||
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<RouterLink
|
||||
to="/"
|
||||
active-class="noop-link"
|
||||
exact-active-class="noop-link"
|
||||
class="noop-link">
|
||||
<ThemeImg>
|
||||
<img :src="logoSrc" :style="{ height: '20px', transform: `rotate(${logoRotation}deg)` }">
|
||||
</ThemeImg>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ThemeImg } from '@/components/ui';
|
||||
import logoSrc from '@/assets/logo.svg';
|
||||
|
||||
const logoRotation = ref(0);
|
||||
|
||||
let lastScrollTop = 0;
|
||||
const handleScroll = () => {
|
||||
const st = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const scrollDelta = st - lastScrollTop;
|
||||
lastScrollTop = st <= 0 ? 0 : st;
|
||||
logoRotation.value += scrollDelta * 0.9;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<Switch :modelValue="enabled" @update:modelValue="toggleTheme">
|
||||
<template v-slot:icon-enabled>
|
||||
<IconMoon class="h-4 w-4 text-gray-400"/>
|
||||
</template>
|
||||
<template v-slot:icon-disabled>
|
||||
<IconSun class="h-4 w-4"/>
|
||||
</template>
|
||||
</Switch>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watchEffect, onMounted, onUnmounted } from 'vue';
|
||||
import {
|
||||
Switch,
|
||||
IconMoon,
|
||||
IconSun,
|
||||
} from '@/components';
|
||||
|
||||
const enabled = ref(false);
|
||||
|
||||
const updateTheme = (isDarkMode) => {
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
enabled.value = !enabled.value;
|
||||
updateTheme(enabled.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
enabled.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
updateTheme(enabled.value)
|
||||
});
|
||||
|
||||
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
systemPrefersDark.addEventListener('change', (e) => {
|
||||
enabled.value = e.matches;
|
||||
updateTheme(e.matches);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
systemPrefersDark.removeEventListener('change', handleSystemThemeChange);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import NavbarCompact from './NavbarCompact.vue';
|
||||
import SpinningLogo from './SpinningLogo.vue';
|
||||
import ThemeSwitch from './ThemeSwitch.vue';
|
||||
|
||||
export {
|
||||
NavbarCompact, SpinningLogo, ThemeSwitch,
|
||||
};
|
||||
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<Switch
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="$emit('update:modelValue', $event)"
|
||||
|
||||
:class="{
|
||||
'bg-transparent': true, // Always transparent background
|
||||
// Optional: Add a subtle background color if the switch is ON/Enabled
|
||||
// 'bg-gray-200': modelValue,
|
||||
}"
|
||||
|
||||
class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2
|
||||
|
||||
border-gray-500 transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-700/75"
|
||||
>
|
||||
<span class="sr-only">Toggle Theme</span>
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="modelValue ? 'translate-x-5' : 'translate-x-0'"
|
||||
|
||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full shadow-lg ring-0 transition duration-200 ease-in-out"
|
||||
>
|
||||
<div class="h-full w-full flex items-center justify-center text-gray-800">
|
||||
<template v-if="modelValue">
|
||||
<slot name="icon-enabled"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot name="icon-disabled"></slot>
|
||||
</template>
|
||||
</div>
|
||||
</span>
|
||||
</Switch>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// ... (script remains the same)
|
||||
import { Switch } from '@headlessui/vue';
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
</script>
|
||||
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="w-auto" :class="{ 'invert': invertColor }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
invertColor: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.invert {
|
||||
filter: none;
|
||||
}
|
||||
.dark .invert {
|
||||
filter: invert(100%);
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,6 @@
|
||||
import ThemeImg from './ThemeImg.vue';
|
||||
import Switch from './Switch.vue';
|
||||
|
||||
export {
|
||||
ThemeImg, Switch,
|
||||
};
|
||||
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="block-list space-y-12">
|
||||
<div
|
||||
v-for="block in blocks"
|
||||
:key="block.data.block_id"
|
||||
class="block-container pb-20"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-4 px-2">
|
||||
<div class="flex items-center space-x-6">
|
||||
<div class="bg-black dark:bg-white text-white dark:text-black px-4 py-1.5 rounded-xl font-mono font-black text-sm shadow-sm mr-5">
|
||||
BLOCK #{{ block.data.block_id }}
|
||||
</div>
|
||||
<div class="flex flex-col pl-5">
|
||||
<span class="text-[10px] text-gray-400 uppercase font-bold tracking-widest">L1 block ID</span>
|
||||
<span
|
||||
class="text-xs font-mono text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 truncate max-w-[150px] sm:max-w-xs cursor-pointer underline decoration-dotted"
|
||||
@click="emit('openBlock', block.l1_block_id)"
|
||||
:title="`View block ${block.l1_block_id} in explorer`"
|
||||
>
|
||||
{{ block.l1_block_id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col pl-5">
|
||||
<span class="text-[10px] text-gray-400 uppercase font-bold tracking-widest">L1 tx ID</span>
|
||||
<span
|
||||
class="text-xs font-mono text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 truncate max-w-[150px] sm:max-w-xs cursor-pointer underline decoration-dotted"
|
||||
@click="emit('openTransaction', block.l1_transaction_id)"
|
||||
:title="`View transaction ${block.l1_transaction_id} in explorer`"
|
||||
>
|
||||
{{ block.l1_transaction_id }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:block text-right">
|
||||
<span class="text-[10px] text-gray-400 uppercase font-bold tracking-widest block">Parent</span>
|
||||
<span class="text-xs font-mono text-gray-400">#{{ block.data.parent_block_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TransactionList
|
||||
:transactions="block.data.transactions"
|
||||
:currentAccount="currentAccount"
|
||||
@selectTransaction="(tx) => emit('openTransaction', tx.l1_transaction_id)"
|
||||
class="mt-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty state when no blocks -->
|
||||
<div
|
||||
v-if="!blocks || blocks.length === 0"
|
||||
class="w-full p-1 border border-gray-100 dark:border-gray-400 rounded-2xl"
|
||||
>
|
||||
<div class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
|
||||
No recent transactions found.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { TransactionList } from '@/components';
|
||||
|
||||
defineProps({
|
||||
blocks: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
currentAccount: {
|
||||
type: String,
|
||||
default: 'ARCHIVE_VIEW'
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['openBlock', 'openTransaction']);
|
||||
</script>
|
||||
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="send-money-card flex flex-col p-6 sm:p-8 lg:p-10
|
||||
rounded-2xl transition-all duration-300 w-full max-w-none border border-gray-100 dark:border-gray-400">
|
||||
|
||||
<div class="flex items-center justify-between mb-6 border-b pb-4 border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Send MEM
|
||||
</h2>
|
||||
|
||||
<div class="flex items-center space-x-1 bg-gray-50 dark:bg-gray-800/50 p-1 rounded-xl">
|
||||
<button
|
||||
@click="prevRecipient"
|
||||
class="p-2 rounded-lg hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm transition-all group"
|
||||
title="Previous Character"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="randomRecipient"
|
||||
class="p-2 rounded-lg hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm transition-all group"
|
||||
title="Random Character"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="nextRecipient"
|
||||
class="p-2 rounded-lg hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm transition-all group"
|
||||
title="Next Character"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="handleTransfer" class="space-y-6">
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-1">
|
||||
To Character
|
||||
</label>
|
||||
<p class="text-base sm:text-lg font-mono text-gray-700 dark:text-white h-8 flex items-center">
|
||||
{{ transferData.to || 'Select a recipient...' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-2">
|
||||
Amount (MEM)
|
||||
</label>
|
||||
<input
|
||||
v-model.number="transferData.amount"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
class="w-full p-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-transparent text-gray-900 dark:text-white focus:ring-2 focus:ring-gray-400 outline-none font-bold text-lg transition-all"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pt-4">
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full py-4 rounded-xl bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-bold text-lg hover:opacity-90 active:scale-[0.98] transition-all shadow-lg"
|
||||
>
|
||||
Confirm Transfer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed, onMounted } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
fromAddress: {
|
||||
type: String,
|
||||
default: 'Tsubasa'
|
||||
},
|
||||
characters: {
|
||||
type: Array,
|
||||
default: () => ['Tsubasa', 'Hyuga', 'Misaki', 'Wakabayashi', 'Wakashimazu', 'Misugi', 'Matsuyama', 'Ishizaki']
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['transfer']);
|
||||
|
||||
const transferData = reactive({
|
||||
to: '',
|
||||
amount: null
|
||||
});
|
||||
|
||||
const availableRecipients = computed(() => {
|
||||
return props.characters.filter(name => name !== props.fromAddress);
|
||||
});
|
||||
|
||||
const nextRecipient = () => {
|
||||
const list = availableRecipients.value;
|
||||
const currentIndex = list.indexOf(transferData.to);
|
||||
const nextIndex = (currentIndex + 1) % list.length;
|
||||
transferData.to = list[nextIndex];
|
||||
};
|
||||
|
||||
const prevRecipient = () => {
|
||||
const list = availableRecipients.value;
|
||||
const currentIndex = list.indexOf(transferData.to);
|
||||
const prevIndex = (currentIndex - 1 + list.length) % list.length;
|
||||
transferData.to = list[prevIndex];
|
||||
};
|
||||
|
||||
const randomRecipient = () => {
|
||||
const list = availableRecipients.value;
|
||||
let newRecipient;
|
||||
do {
|
||||
newRecipient = list[Math.floor(Math.random() * list.length)];
|
||||
} while (newRecipient === transferData.to && list.length > 1);
|
||||
transferData.to = newRecipient;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (availableRecipients.value.length > 0) {
|
||||
transferData.to = availableRecipients.value[0];
|
||||
}
|
||||
});
|
||||
|
||||
const handleTransfer = () => {
|
||||
if (!transferData.to || !transferData.amount) return;
|
||||
emit('transfer', { to: transferData.to, amount: transferData.amount });
|
||||
transferData.amount = null;
|
||||
};
|
||||
</script>
|
||||
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="transaction-list-container w-full mt-6 p-1 border border-gray-100 dark:border-gray-400 rounded-2xl">
|
||||
<div>
|
||||
<div class="inline-block min-w-full align-middle">
|
||||
<div class="overflow-hidden">
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="sticky top-0 z-10 ">
|
||||
<tr>
|
||||
<th scope="col" class="px-5 py-4 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" class="px-5 py-4 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Transaction Hash (ID)
|
||||
</th>
|
||||
<th scope="col" class="px-5 py-4 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
From / To
|
||||
</th>
|
||||
<th scope="col" class="px-5 py-4 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Amount
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr
|
||||
v-for="tx in transactions"
|
||||
:key="tx.id"
|
||||
class="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 transition duration-150"
|
||||
@click="emit('selectTransaction', tx)"
|
||||
>
|
||||
<td class="px-5 py-5 whitespace-nowrap text-sm">
|
||||
<span
|
||||
:class="[
|
||||
'px-2 py-1 rounded-full text-xs font-medium',
|
||||
tx.confirmed
|
||||
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400 animate-pulse'
|
||||
]"
|
||||
>
|
||||
{{ tx.confirmed ? 'Confirmed' : 'Pending' }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="px-5 py-5 text-sm text-gray-700 dark:text-gray-300 font-mono text-xs break-all max-w-[200px]">
|
||||
{{ tx.id }}
|
||||
</td>
|
||||
|
||||
<td class="px-5 py-5 text-sm text-gray-700 dark:text-gray-300">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-mono text-xs">{{ tx.from }}</span>
|
||||
<div class="text-xs text-gray-500 flex items-center">
|
||||
<span class="mr-1">→</span> {{ tx.to }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="px-5 py-5 whitespace-nowrap text-sm text-right font-semibold">
|
||||
<span :class="tx.from === currentAccount ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'">
|
||||
{{ tx.from === currentAccount ? '-' : '+' }}{{ tx.amount.toFixed(2) }} MEM
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="!transactions || transactions.length === 0">
|
||||
<td colspan="4" class="px-6 py-4 text-center text-gray-500 dark:text-gray-400">
|
||||
No recent transactions found.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
transactions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
// We need the current account name to decide if the amount is +/-
|
||||
currentAccount: {
|
||||
type: String,
|
||||
default: 'Alisa'
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['selectTransaction']);
|
||||
</script>
|
||||
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="wallet-card flex flex-col p-6 sm:p-8 lg:p-10
|
||||
rounded-2xl transition-all duration-300 w-full max-w-none border border-gray-100 dark:border-gray-400">
|
||||
|
||||
<div class="flex items-center justify-between mb-6 border-b pb-4 border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Wallet Overview
|
||||
</h2>
|
||||
|
||||
<div class="flex items-center space-x-1 bg-gray-50 dark:bg-gray-800/50 p-1 rounded-xl">
|
||||
<button
|
||||
@click="emit('prev')"
|
||||
class="p-2 rounded-lg hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm transition-all group"
|
||||
title="Previous Character"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="emit('regenerate')"
|
||||
class="p-2 rounded-lg hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm transition-all group"
|
||||
title="Random Character"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="emit('next')"
|
||||
class="p-2 rounded-lg hover:bg-white dark:hover:bg-gray-700 hover:shadow-sm transition-all group"
|
||||
title="Next Character"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-1">
|
||||
Character Name
|
||||
</span>
|
||||
<p class="text-base sm:text-lg font-mono break-all text-gray-700 dark:text-white">
|
||||
{{ walletAddress }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col pt-4">
|
||||
<span class="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-2">
|
||||
Current Balance
|
||||
</span>
|
||||
<p class="text-4xl sm:text-5xl font-extrabold text-gray-900 dark:text-white">
|
||||
{{ walletAmount }}
|
||||
<span class="text-xl font-medium text-gray-500 dark:text-gray-400 ps-2">
|
||||
MEM
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
walletAddress: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
walletAmount: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['regenerate', 'prev', 'next']);
|
||||
</script>
|
||||
@ -0,0 +1,8 @@
|
||||
import ArchivedBlockList from './ArchivedBlockList.vue';
|
||||
import TransactionCreator from './TransactionCreator.vue';
|
||||
import TransactionList from './TransactionList.vue';
|
||||
import WalletInfo from './WalletInfo.vue';
|
||||
|
||||
export {
|
||||
ArchivedBlockList, TransactionCreator, TransactionList, WalletInfo
|
||||
};
|
||||
3
testnet/l2-sequencer-archival-demo/webapp/src/index.css
Normal file
@ -0,0 +1,3 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
15
testnet/l2-sequencer-archival-demo/webapp/src/main.js
Normal file
@ -0,0 +1,15 @@
|
||||
import './assets/main.css';
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import './index.css';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app');
|
||||
@ -0,0 +1,23 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { ArchiveView, WalletView } from '@/views';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'wallet',
|
||||
component: WalletView,
|
||||
},
|
||||
{
|
||||
path: '/archive',
|
||||
name: 'archive',
|
||||
component: ArchiveView,
|
||||
},
|
||||
],
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
return { top: 0 }
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -0,0 +1,98 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_ARCHIVER_URL || 'http://localhost:8090';
|
||||
const EXPLORER_URL = import.meta.env.VITE_EXPLORER_URL || 'http://localhost:8000';
|
||||
const STREAM_URL = `${BASE_URL}/block_stream`;
|
||||
const CACHE_URL = `${BASE_URL}/blocks`;
|
||||
|
||||
export const useArchiveStore = defineStore('archive', {
|
||||
state: () => ({
|
||||
blocks: [],
|
||||
loading: false,
|
||||
eventSource: null,
|
||||
// Status can be: 'disconnected', 'waiting', 'connected', or 'error'
|
||||
connectionStatus: 'disconnected',
|
||||
}),
|
||||
|
||||
actions: {
|
||||
processBlocks(blocks) {
|
||||
return blocks.map(block => ({
|
||||
...block,
|
||||
data: {
|
||||
...block.data,
|
||||
transactions: (block.data?.transactions || []).map(tx => ({
|
||||
...tx,
|
||||
confirmed: true // Overwriting the pending status from the raw data
|
||||
}))
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
async fetchCachedBlocks() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await fetch(CACHE_URL);
|
||||
if (!res.ok) throw new Error('Failed to fetch cached blocks');
|
||||
|
||||
const data = await res.json();
|
||||
const processed = this.processBlocks(data);
|
||||
|
||||
// Reverse so newest blocks (highest ID) are at the top
|
||||
this.blocks = [...processed].reverse().slice(0, 50);
|
||||
} catch (err) {
|
||||
console.error("Error fetching cached blocks:", err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
startStream() {
|
||||
if (this.eventSource) return;
|
||||
this.connectionStatus = 'connecting';
|
||||
this.eventSource = new EventSource(STREAM_URL);
|
||||
|
||||
this.eventSource.onopen = () => {
|
||||
this.connectionStatus = 'connected';
|
||||
console.log("Archive Stream Connected");
|
||||
};
|
||||
|
||||
this.eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const rawData = JSON.parse(event.data);
|
||||
const rawBlocks = Array.isArray(rawData) ? rawData : [rawData];
|
||||
|
||||
const processed = this.processBlocks(rawBlocks);
|
||||
|
||||
// Prepend new blocks to the beginning of the state
|
||||
this.blocks = [...processed.reverse(), ...this.blocks].slice(0, 50);
|
||||
} catch (err) {
|
||||
console.error("Error parsing stream data:", err);
|
||||
}
|
||||
};
|
||||
|
||||
this.eventSource.onerror = () => {
|
||||
this.connectionStatus = 'error';
|
||||
this.stopStream();
|
||||
setTimeout(() => this.startStream(), 5000);
|
||||
};
|
||||
},
|
||||
|
||||
stopStream() {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
this.eventSource = null;
|
||||
this.connectionStatus = 'disconnected';
|
||||
}
|
||||
},
|
||||
|
||||
openBlockInExplorer(blockId) {
|
||||
if (!blockId) return;
|
||||
window.open(`${EXPLORER_URL}/blocks/${blockId}`, '_blank');
|
||||
},
|
||||
|
||||
openTransactionInExplorer(txId) {
|
||||
if (!txId) return;
|
||||
window.open(`${EXPLORER_URL}/transactions/${txId}`, '_blank');
|
||||
},
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,124 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_SEQUENCER_URL || 'http://localhost:8080';
|
||||
const TSUBASA_NAMES = [
|
||||
'Tsubasa', 'Hyuga', 'Misaki', 'Wakabayashi',
|
||||
'Wakashimazu', 'Misugi', 'Matsuyama', 'Ishizaki'
|
||||
];
|
||||
|
||||
export const useWalletStore = defineStore('wallet', {
|
||||
state: () => ({
|
||||
accountName: '',
|
||||
balance: 0,
|
||||
confirmedBalance: 0,
|
||||
transactions: [],
|
||||
pollingInterval: null,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return null;
|
||||
},
|
||||
|
||||
setCookie(name, value, days = 7) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = "expires=" + date.toUTCString();
|
||||
document.cookie = `${name}=${value}; ${expires}; path=/`;
|
||||
},
|
||||
|
||||
async fetchAccountData() {
|
||||
if (!this.accountName) return;
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/accounts/${this.accountName}?tx=true`);
|
||||
if (!res.ok) throw new Error('Account not found');
|
||||
|
||||
const data = await res.json();
|
||||
this.balance = data.balance;
|
||||
this.confirmedBalance = data.confirmed_balance;
|
||||
this.transactions = data.transactions || [];
|
||||
} catch (err) {
|
||||
console.error('Polling error:', err);
|
||||
}
|
||||
},
|
||||
|
||||
async sendTransaction(payload) {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/transfer`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
from: this.accountName,
|
||||
to: payload.to,
|
||||
amount: payload.amount
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.json();
|
||||
throw new Error(error.error || 'Transfer failed');
|
||||
}
|
||||
|
||||
await this.fetchAccountData();
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
initializeWallet() {
|
||||
const savedName = this.getCookie('sequencer_wallet_name');
|
||||
if (savedName) {
|
||||
this.accountName = savedName;
|
||||
} else {
|
||||
const randomName = TSUBASA_NAMES[Math.floor(Math.random() * TSUBASA_NAMES.length)];
|
||||
this.accountName = randomName;
|
||||
this.setCookie('sequencer_wallet_name', randomName);
|
||||
}
|
||||
},
|
||||
|
||||
regenerateWallet() {
|
||||
document.cookie = "sequencer_wallet_name=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||
this.stopPolling();
|
||||
this.initializeWallet();
|
||||
this.startPolling();
|
||||
},
|
||||
|
||||
nextWallet() {
|
||||
const currentIndex = TSUBASA_NAMES.indexOf(this.accountName);
|
||||
const nextIndex = (currentIndex + 1) % TSUBASA_NAMES.length;
|
||||
this.updateWallet(TSUBASA_NAMES[nextIndex]);
|
||||
},
|
||||
|
||||
prevWallet() {
|
||||
const currentIndex = TSUBASA_NAMES.indexOf(this.accountName);
|
||||
const prevIndex = (currentIndex - 1 + TSUBASA_NAMES.length) % TSUBASA_NAMES.length;
|
||||
this.updateWallet(TSUBASA_NAMES[prevIndex]);
|
||||
},
|
||||
|
||||
updateWallet(newName) {
|
||||
this.stopPolling();
|
||||
this.accountName = newName;
|
||||
this.setCookie('sequencer_wallet_name', newName);
|
||||
this.startPolling();
|
||||
},
|
||||
|
||||
startPolling() {
|
||||
this.initializeWallet();
|
||||
this.stopPolling();
|
||||
this.fetchAccountData();
|
||||
this.pollingInterval = setInterval(() => this.fetchAccountData(), 2000);
|
||||
},
|
||||
|
||||
stopPolling() {
|
||||
if (this.pollingInterval) {
|
||||
clearInterval(this.pollingInterval);
|
||||
this.pollingInterval = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="archive flex flex-col min-h-screen pt-24 p-4 sm:p-6 lg:p-8">
|
||||
|
||||
<header class="w-full grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-12 items-center mb-12 pt-20 pb-10">
|
||||
|
||||
<div class="w-full">
|
||||
<div class="flex flex-col p-6 sm:p-8 lg:p-10 rounded-2xl border border-gray-100 dark:border-gray-400 bg-white dark:bg-black transition-all duration-300 ">
|
||||
|
||||
<div class="flex items-center justify-between mb-6 border-b pb-4 border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Archive Explorer
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-1">
|
||||
Data Source
|
||||
</span>
|
||||
<p class="text-base sm:text-lg font-mono text-gray-700 dark:text-white">
|
||||
Testnet Archiver
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col pt-4">
|
||||
<span class="text-sm font-semibold text-gray-500 dark:text-gray-400 mb-3">
|
||||
Feed Status
|
||||
</span>
|
||||
<div class="flex items-center space-x-3 bg-gray-50 dark:bg-gray-800/50 px-4 py-2 rounded-xl border border-gray-100 dark:border-gray-800 w-fit">
|
||||
<span class="relative flex h-2 w-2">
|
||||
<span
|
||||
:class="statusClasses.ping"
|
||||
class="absolute inline-flex h-full w-full rounded-full opacity-75"
|
||||
></span>
|
||||
<span
|
||||
:class="statusClasses.dot"
|
||||
class="relative inline-flex rounded-full h-2 w-2"
|
||||
></span>
|
||||
</span>
|
||||
|
||||
<p class="text-gray-500 dark:text-gray-400 font-mono uppercase tracking-[0.1em] text-[10px] font-bold">
|
||||
{{ statusText }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center md:justify-start">
|
||||
<img
|
||||
src="@/assets/graphics/tsubasa-archive.png"
|
||||
alt="Logo"
|
||||
class="w-[280px] sm:w-[380px] object-contain "
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="w-full pb-24">
|
||||
<ArchivedBlockList
|
||||
:blocks="archiveStore.blocks"
|
||||
@openBlock="archiveStore.openBlockInExplorer"
|
||||
@openTransaction="archiveStore.openTransactionInExplorer"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, computed } from 'vue';
|
||||
import { useArchiveStore } from '@/stores/archiveView';
|
||||
import { ArchivedBlockList } from '@/components';
|
||||
|
||||
const archiveStore = useArchiveStore();
|
||||
|
||||
const statusClasses = computed(() => {
|
||||
const isConnected = archiveStore.connectionStatus === 'connected';
|
||||
const isError = archiveStore.connectionStatus === 'error';
|
||||
|
||||
return {
|
||||
dot: isConnected ? 'bg-green-500' : (isError ? 'bg-red-500' : 'bg-yellow-500'),
|
||||
ping: isConnected ? 'animate-ping bg-green-400' : (isError ? 'bg-red-400' : 'bg-yellow-400')
|
||||
};
|
||||
});
|
||||
|
||||
const statusText = computed(() => {
|
||||
switch (archiveStore.connectionStatus) {
|
||||
case 'connected':
|
||||
return 'Live • Stream Active';
|
||||
case 'waiting':
|
||||
return 'Waiting...';
|
||||
case 'error':
|
||||
return 'Offline • Reconnecting';
|
||||
default:
|
||||
return 'Stream Disconnected';
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await archiveStore.fetchCachedBlocks();
|
||||
archiveStore.startStream()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
archiveStore.stopStream()
|
||||
})
|
||||
</script>
|
||||
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="archive flex flex-col min-h-screen pt-24 p-4 sm:p-6 lg:p-8">
|
||||
<div class="flex justify-center m-20 pb-20 sm:pt-20">
|
||||
<img
|
||||
src="@/assets/graphics/tsubasa-lg.png"
|
||||
alt="Logo"
|
||||
class="w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
<header class="w-full grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8 items-start mb-8">
|
||||
<div class="w-full relative group">
|
||||
<WalletInfo
|
||||
:walletAddress="walletStore.accountName"
|
||||
:walletAmount="walletStore.balance"
|
||||
@regenerate="walletStore.regenerateWallet"
|
||||
@next="walletStore.nextWallet"
|
||||
@prev="walletStore.prevWallet"
|
||||
class="max-w-none w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full relative group">
|
||||
<TransactionCreator @transfer="handleTransfer" class="max-w-none w-full" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="w-full pb-12 pt-12">
|
||||
<TransactionList
|
||||
:currentAccount="walletStore.accountName"
|
||||
:transactions="walletStore.transactions"
|
||||
class="max-w-none w-full"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { useWalletStore } from '@/stores/walletView.js';
|
||||
import { TransactionCreator, TransactionList, WalletInfo } from '@/components';
|
||||
|
||||
const walletStore = useWalletStore();
|
||||
|
||||
const handleTransfer = async (data) => {
|
||||
await walletStore.sendTransaction(data);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
walletStore.startPolling();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
walletStore.stopPolling();
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,4 @@
|
||||
import ArchiveView from './ArchiveView.vue';
|
||||
import WalletView from './WalletView.vue';
|
||||
|
||||
export { ArchiveView, WalletView };
|
||||
10
testnet/l2-sequencer-archival-demo/webapp/tailwind.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: ['class'],
|
||||
}
|
||||
|
||||
18
testnet/l2-sequencer-archival-demo/webapp/vite.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
||||