From a573b6e5adee497473461141648bf58f5e97413f Mon Sep 17 00:00:00 2001 From: parabit Date: Sun, 15 Feb 2026 18:19:39 -0600 Subject: [PATCH 1/7] initial code structuring --- .gitignore | 4 +- Cargo.lock | 668 ++++++++++++++++++++++++++++++++++++++- Cargo.toml | 17 + README.md | 34 +- bin/.gitignore | 4 + bin/.just-1.46.0.pkg | 1 - bin/.rustup-1.28.2.pkg | 1 - bin/README.hermit.md | 7 - bin/activate-hermit | 21 -- bin/activate-hermit.fish | 24 -- bin/cargo | 1 - bin/cargo-clippy | 1 - bin/cargo-fmt | 1 - bin/cargo-miri | 1 - bin/clippy-driver | 1 - bin/hermit | 43 --- bin/hermit.hcl | 2 - bin/just | 1 - bin/rls | 1 - bin/rust-analyzer | 1 - bin/rust-gdb | 1 - bin/rust-gdbgui | 1 - bin/rust-lldb | 1 - bin/rustc | 1 - bin/rustdoc | 1 - bin/rustfmt | 1 - bin/rustup | 1 - justfile | 2 + src/api.rs | 48 +++ src/bin/main.rs | 5 +- src/lib.rs | 45 ++- src/server.rs | 34 ++ src/swap.rs | 130 ++++++++ tests/api.rs | 70 ++++ tests/lib.rs | 55 ++++ tests/server.rs | 38 +++ tests/swap.rs | 121 +++++++ 37 files changed, 1264 insertions(+), 125 deletions(-) create mode 100644 bin/.gitignore delete mode 120000 bin/.just-1.46.0.pkg delete mode 120000 bin/.rustup-1.28.2.pkg delete mode 100644 bin/README.hermit.md delete mode 100755 bin/activate-hermit delete mode 100755 bin/activate-hermit.fish delete mode 120000 bin/cargo delete mode 120000 bin/cargo-clippy delete mode 120000 bin/cargo-fmt delete mode 120000 bin/cargo-miri delete mode 120000 bin/clippy-driver delete mode 100755 bin/hermit delete mode 100644 bin/hermit.hcl delete mode 120000 bin/just delete mode 120000 bin/rls delete mode 120000 bin/rust-analyzer delete mode 120000 bin/rust-gdb delete mode 120000 bin/rust-gdbgui delete mode 120000 bin/rust-lldb delete mode 120000 bin/rustc delete mode 120000 bin/rustdoc delete mode 120000 bin/rustfmt delete mode 120000 bin/rustup create mode 100644 src/api.rs create mode 100644 src/server.rs create mode 100644 src/swap.rs create mode 100644 tests/api.rs create mode 100644 tests/server.rs create mode 100644 tests/swap.rs diff --git a/.gitignore b/.gitignore index a2f9ba9..9d49729 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target /.hermit -/.idea \ No newline at end of file +/.idea + +justfile.local \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 11d7c2d..dffceae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -49,7 +49,164 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "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", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "base64", + "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", ] [[package]] @@ -102,7 +259,62 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" name = "entangle" version = "0.0.1" dependencies = [ + "axum", + "bitcoin", "clap", + "http-body-util", + "serde", + "serde_json", + "tokio", + "tower", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", ] [[package]] @@ -111,18 +323,185 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -141,6 +520,126 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "strsim" version = "0.11.1" @@ -158,6 +657,85 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-ident" version = "1.0.22" @@ -170,12 +748,27 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -184,3 +777,74 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 013401e..391d840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "entangle" description = "Swaps made easy" version = "0.0.1" homepage = "https://github.com/parasitepool/entangle" +autotests = false authors.workspace = true edition.workspace = true @@ -21,10 +22,26 @@ repository = "https://github.com/parasitepool/entangle" rust-version = "1.91.0" [workspace.dependencies] +axum = "0.8" +bitcoin = { version = "0.32", features = ["base64", "serde", "std"] } clap = { version = "4.5.36", features = ["derive", "env"] } +http-body-util = "0.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tower = { version = "0.5", features = ["util"] } [dependencies] +axum.workspace = true +bitcoin.workspace = true clap.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true + +[dev-dependencies] +http-body-util.workspace = true +tower.workspace = true [[bin]] name = "entangle" diff --git a/README.md b/README.md index 5d8db2a..161ce6a 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,36 @@
-`entangle` is currently in early development check back soon. \ No newline at end of file +`entangle` builds Bitcoin swap transactions. Given two UTXOs owned by different parties, it produces a PSBT that safely exchanges them — party A signs, then party B signs, and the swap is complete. + +## Usage + +As a **library**: + +```rust +use entangle::swap::{build_swap_psbt, SwapRequest}; + +let psbt = build_swap_psbt(&request)?; +``` + +As an **API server**: + +```sh +entangle api --bind 0.0.0.0:3000 +``` + +As a **frontend server** (with optional embedded API): + +```sh +entangle server --bind 0.0.0.0:8080 --with-api +``` + +## Development + +Requires [Hermit](https://cashapp.github.io/hermit/) for toolchain management. + +```sh +just init # set up environment +just dev # run the app +just ci # clippy + format check + tests +``` \ No newline at end of file diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..79b6ec0 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1,4 @@ +* + +!.gitignore +!package \ No newline at end of file diff --git a/bin/.just-1.46.0.pkg b/bin/.just-1.46.0.pkg deleted file mode 120000 index 383f451..0000000 --- a/bin/.just-1.46.0.pkg +++ /dev/null @@ -1 +0,0 @@ -hermit \ No newline at end of file diff --git a/bin/.rustup-1.28.2.pkg b/bin/.rustup-1.28.2.pkg deleted file mode 120000 index 383f451..0000000 --- a/bin/.rustup-1.28.2.pkg +++ /dev/null @@ -1 +0,0 @@ -hermit \ No newline at end of file diff --git a/bin/README.hermit.md b/bin/README.hermit.md deleted file mode 100644 index e889550..0000000 --- a/bin/README.hermit.md +++ /dev/null @@ -1,7 +0,0 @@ -# Hermit environment - -This is a [Hermit](https://github.com/cashapp/hermit) bin directory. - -The symlinks in this directory are managed by Hermit and will automatically -download and install Hermit itself as well as packages. These packages are -local to this environment. diff --git a/bin/activate-hermit b/bin/activate-hermit deleted file mode 100755 index fe28214..0000000 --- a/bin/activate-hermit +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# This file must be used with "source bin/activate-hermit" from bash or zsh. -# You cannot run it directly -# -# THIS FILE IS GENERATED; DO NOT MODIFY - -if [ "${BASH_SOURCE-}" = "$0" ]; then - echo "You must source this script: \$ source $0" >&2 - exit 33 -fi - -BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")" -if "${BIN_DIR}/hermit" noop > /dev/null; then - eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")" - - if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then - hash -r 2>/dev/null - fi - - echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated" -fi diff --git a/bin/activate-hermit.fish b/bin/activate-hermit.fish deleted file mode 100755 index 0367d23..0000000 --- a/bin/activate-hermit.fish +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env fish - -# This file must be sourced with "source bin/activate-hermit.fish" from Fish shell. -# You cannot run it directly. -# -# THIS FILE IS GENERATED; DO NOT MODIFY - -if status is-interactive - set BIN_DIR (dirname (status --current-filename)) - - if "$BIN_DIR/hermit" noop > /dev/null - # Source the activation script generated by Hermit - "$BIN_DIR/hermit" activate "$BIN_DIR/.." | source - - # Clear the command cache if applicable - functions -c > /dev/null 2>&1 - - # Display activation message - echo "Hermit environment $($HERMIT_ENV/bin/hermit env HERMIT_ENV) activated" - end -else - echo "You must source this script: source $argv[0]" >&2 - exit 33 -end diff --git a/bin/cargo b/bin/cargo deleted file mode 120000 index 906ee94..0000000 --- a/bin/cargo +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/cargo-clippy b/bin/cargo-clippy deleted file mode 120000 index 906ee94..0000000 --- a/bin/cargo-clippy +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/cargo-fmt b/bin/cargo-fmt deleted file mode 120000 index 906ee94..0000000 --- a/bin/cargo-fmt +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/cargo-miri b/bin/cargo-miri deleted file mode 120000 index 906ee94..0000000 --- a/bin/cargo-miri +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/clippy-driver b/bin/clippy-driver deleted file mode 120000 index 906ee94..0000000 --- a/bin/clippy-driver +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/hermit b/bin/hermit deleted file mode 100755 index 31559b7..0000000 --- a/bin/hermit +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# -# THIS FILE IS GENERATED; DO NOT MODIFY - -set -eo pipefail - -export HERMIT_USER_HOME=~ - -if [ -z "${HERMIT_STATE_DIR}" ]; then - case "$(uname -s)" in - Darwin) - export HERMIT_STATE_DIR="${HERMIT_USER_HOME}/Library/Caches/hermit" - ;; - Linux) - export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HERMIT_USER_HOME}/.cache}/hermit" - ;; - esac -fi - -export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}" -HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")" -export HERMIT_CHANNEL -export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit} - -if [ ! -x "${HERMIT_EXE}" ]; then - echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2 - INSTALL_SCRIPT="$(mktemp)" - # This value must match that of the install script - INSTALL_SCRIPT_SHA256="09ed936378857886fd4a7a4878c0f0c7e3d839883f39ca8b4f2f242e3126e1c6" - if [ "${INSTALL_SCRIPT_SHA256}" = "BYPASS" ]; then - curl -fsSL "${HERMIT_DIST_URL}/install.sh" -o "${INSTALL_SCRIPT}" - else - # Install script is versioned by its sha256sum value - curl -fsSL "${HERMIT_DIST_URL}/install-${INSTALL_SCRIPT_SHA256}.sh" -o "${INSTALL_SCRIPT}" - # Verify install script's sha256sum - openssl dgst -sha256 "${INSTALL_SCRIPT}" | \ - awk -v EXPECTED="$INSTALL_SCRIPT_SHA256" \ - '$2!=EXPECTED {print "Install script sha256 " $2 " does not match " EXPECTED; exit 1}' - fi - /bin/bash "${INSTALL_SCRIPT}" 1>&2 -fi - -exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@" diff --git a/bin/hermit.hcl b/bin/hermit.hcl deleted file mode 100644 index 081cbe8..0000000 --- a/bin/hermit.hcl +++ /dev/null @@ -1,2 +0,0 @@ -github-token-auth { -} diff --git a/bin/just b/bin/just deleted file mode 120000 index 816066f..0000000 --- a/bin/just +++ /dev/null @@ -1 +0,0 @@ -.just-1.46.0.pkg \ No newline at end of file diff --git a/bin/rls b/bin/rls deleted file mode 120000 index 906ee94..0000000 --- a/bin/rls +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rust-analyzer b/bin/rust-analyzer deleted file mode 120000 index 906ee94..0000000 --- a/bin/rust-analyzer +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rust-gdb b/bin/rust-gdb deleted file mode 120000 index 906ee94..0000000 --- a/bin/rust-gdb +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rust-gdbgui b/bin/rust-gdbgui deleted file mode 120000 index 906ee94..0000000 --- a/bin/rust-gdbgui +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rust-lldb b/bin/rust-lldb deleted file mode 120000 index 906ee94..0000000 --- a/bin/rust-lldb +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rustc b/bin/rustc deleted file mode 120000 index 906ee94..0000000 --- a/bin/rustc +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rustdoc b/bin/rustdoc deleted file mode 120000 index 906ee94..0000000 --- a/bin/rustdoc +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rustfmt b/bin/rustfmt deleted file mode 120000 index 906ee94..0000000 --- a/bin/rustfmt +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/bin/rustup b/bin/rustup deleted file mode 120000 index 906ee94..0000000 --- a/bin/rustup +++ /dev/null @@ -1 +0,0 @@ -.rustup-1.28.2.pkg \ No newline at end of file diff --git a/justfile b/justfile index 4dd4dc2..230d1fd 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,8 @@ set dotenv-load set dotenv-filename := ".env.local" +import? 'justfile.local' + dev: cargo run diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..bc15144 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,48 @@ +use axum::{Json, Router, routing::post}; + +use crate::swap::{SwapError, SwapRequest, build_swap_psbt}; + +/// Build the API router with all routes. +pub fn router() -> Router { + Router::new().route("/swap", post(create_swap)) +} + +/// POST /swap — Build a swap PSBT from a SwapRequest. +async fn create_swap(Json(request): Json) -> Result, ApiError> { + let psbt = build_swap_psbt(&request).map_err(ApiError::Swap)?; + Ok(Json(SwapResponse { + psbt_base64: psbt.to_string(), + })) +} + +/// Response body for the swap endpoint. +#[derive(serde::Serialize)] +pub struct SwapResponse { + /// The unsigned PSBT encoded as a base64 string. + pub psbt_base64: String, +} + +/// API error wrapper. +pub enum ApiError { + Swap(SwapError), +} + +impl axum::response::IntoResponse for ApiError { + fn into_response(self) -> axum::response::Response { + let (status, message) = match self { + ApiError::Swap(e) => (axum::http::StatusCode::BAD_REQUEST, e.to_string()), + }; + let body = serde_json::json!({ "error": message }); + (status, Json(body)).into_response() + } +} + +/// Start the API server, binding to the given address. +pub async fn serve(bind: &str) { + let app = router(); + let listener = tokio::net::TcpListener::bind(bind) + .await + .expect("failed to bind API server"); + println!("API server listening on {bind}"); + axum::serve(listener, app).await.expect("API server error"); +} diff --git a/src/bin/main.rs b/src/bin/main.rs index d3eb841..d94a7be 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,3 +1,4 @@ -fn main() { - entangle::main(); +#[tokio::main] +async fn main() { + entangle::main().await; } diff --git a/src/lib.rs b/src/lib.rs index 03746aa..69d8ca3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,44 @@ -use clap::Parser; +pub mod api; +pub mod server; +pub mod swap; + +use clap::{Parser, Subcommand}; #[derive(Parser)] -#[command(version, author)] -struct Args {} +#[command(version, author, about = "Swaps made easy")] +pub struct Args { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Subcommand)] +pub enum Command { + /// Start the REST API server. + Api { + /// Address to bind the API server to. + #[arg(long, default_value = "0.0.0.0:3000", env = "ENTANGLE_API_BIND")] + bind: String, + }, + /// Start the frontend web server. + Server { + /// Address to bind the frontend server to. + #[arg(long, default_value = "0.0.0.0:8080", env = "ENTANGLE_SERVER_BIND")] + bind: String, + /// Also serve the API routes under /api. + #[arg(long)] + with_api: bool, + }, +} + +pub async fn main() { + let args = Args::parse(); -pub fn main() { - Args::parse(); + match args.command { + Command::Api { bind } => { + api::serve(&bind).await; + } + Command::Server { bind, with_api } => { + server::serve(&bind, with_api).await; + } + } } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..5ec196d --- /dev/null +++ b/src/server.rs @@ -0,0 +1,34 @@ +use axum::{Router, routing::get}; + +/// Build the frontend router. +/// +/// Currently a placeholder. Will be replaced with Leptos SSR routes. +pub fn router() -> Router { + Router::new().route("/", get(index)) +} + +async fn index() -> &'static str { + "entangle frontend - coming soon" +} + +/// Start the frontend server. +/// +/// If `with_api` is true, the API routes are nested under `/api`. +pub async fn serve(bind: &str, with_api: bool) { + let mut app = router(); + + if with_api { + app = app.nest("/api", crate::api::router()); + } + + let listener = tokio::net::TcpListener::bind(bind) + .await + .expect("failed to bind frontend server"); + println!("Frontend server listening on {bind}"); + if with_api { + println!("API routes available at /api/*"); + } + axum::serve(listener, app) + .await + .expect("frontend server error"); +} diff --git a/src/swap.rs b/src/swap.rs new file mode 100644 index 0000000..95c871f --- /dev/null +++ b/src/swap.rs @@ -0,0 +1,130 @@ +use bitcoin::{ + Address, Network, OutPoint, Psbt, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, + absolute, transaction, +}; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// A UTXO identified by its outpoint and the output it represents. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Utxo { + /// The outpoint (txid:vout) identifying this UTXO. + pub outpoint: OutPoint, + /// The transaction output (value + scriptPubKey). + pub txout: TxOut, +} + +/// A request to build a swap PSBT. +/// +/// Party A's UTXO is always required. Party B's UTXO is optional — +/// if absent, the PSBT spends only A's UTXO to B's address. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SwapRequest { + /// The UTXO owned by party A. + pub utxo_a: Utxo, + /// The address where party A wants to receive funds. + pub address_a: Address, + /// The UTXO owned by party B (optional). + pub utxo_b: Option, + /// The address where party B wants to receive funds. + pub address_b: Address, + /// The bitcoin network to validate addresses against. + pub network: Network, +} + +/// Errors that can occur when building a swap PSBT. +#[derive(Debug)] +pub enum SwapError { + /// An address does not belong to the expected network. + NetworkMismatch { expected: Network, address: String }, + /// The unsigned transaction could not be converted to a PSBT. + PsbtCreation(String), +} + +impl fmt::Display for SwapError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SwapError::NetworkMismatch { expected, address } => { + write!(f, "address {address} does not match network {expected}") + } + SwapError::PsbtCreation(msg) => write!(f, "failed to create PSBT: {msg}"), + } + } +} + +impl std::error::Error for SwapError {} + +/// Validate an unchecked address against the expected network. +fn validate_address( + address: &Address, + network: Network, +) -> Result { + let addr_string = address.clone().assume_checked().to_string(); + address + .clone() + .require_network(network) + .map_err(|_| SwapError::NetworkMismatch { + expected: network, + address: addr_string, + }) +} + +/// Build a PSBT that swaps UTXOs between two parties. +/// +/// The resulting PSBT contains: +/// - An input spending `utxo_a` with an output paying to `address_b` +/// - If `utxo_b` is provided: an input spending `utxo_b` with an output paying to `address_a` +/// +/// The PSBT is unsigned. Party A signs first, then party B. +pub fn build_swap_psbt(request: &SwapRequest) -> Result { + let address_a = validate_address(&request.address_a, request.network)?; + let address_b = validate_address(&request.address_b, request.network)?; + + // Build inputs + let mut inputs = vec![TxIn { + previous_output: request.utxo_a.outpoint, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: Witness::default(), + }]; + + if let Some(ref utxo_b) = request.utxo_b { + inputs.push(TxIn { + previous_output: utxo_b.outpoint, + script_sig: ScriptBuf::new(), + sequence: Sequence::MAX, + witness: Witness::default(), + }); + } + + // Build outputs: A's value → B's address, B's value → A's address + let mut outputs = vec![TxOut { + value: request.utxo_a.txout.value, + script_pubkey: address_b.script_pubkey(), + }]; + + if let Some(ref utxo_b) = request.utxo_b { + outputs.push(TxOut { + value: utxo_b.txout.value, + script_pubkey: address_a.script_pubkey(), + }); + } + + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: absolute::LockTime::ZERO, + input: inputs, + output: outputs, + }; + + let mut psbt = + Psbt::from_unsigned_tx(tx).map_err(|e| SwapError::PsbtCreation(e.to_string()))?; + + // Set witness_utxo on PSBT inputs for signing support + psbt.inputs[0].witness_utxo = Some(request.utxo_a.txout.clone()); + if let Some(ref utxo_b) = request.utxo_b { + psbt.inputs[1].witness_utxo = Some(utxo_b.txout.clone()); + } + + Ok(psbt) +} diff --git a/tests/api.rs b/tests/api.rs new file mode 100644 index 0000000..ccf1f1c --- /dev/null +++ b/tests/api.rs @@ -0,0 +1,70 @@ +use axum::body::Body; +use axum::http::{Request, StatusCode}; +use http_body_util::BodyExt; +use tower::ServiceExt; + +use crate::*; + +#[tokio::test] +async fn post_swap_returns_200() { + let app = entangle::api::router(); + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/swap") + .header("content-type", "application/json") + .body(Body::from(swap_request_json(true))) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = response.into_body().collect().await.unwrap().to_bytes(); + let json: serde_json::Value = serde_json::from_slice(&body).unwrap(); + let psbt_str = json["psbt_base64"].as_str().unwrap(); + // Verify it parses as a valid PSBT + let _: bitcoin::Psbt = psbt_str.parse().unwrap(); +} + +#[tokio::test] +async fn post_swap_network_mismatch_returns_400() { + let app = entangle::api::router(); + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/swap") + .header("content-type", "application/json") + .body(Body::from(mismatch_request_json())) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + + let body = response.into_body().collect().await.unwrap().to_bytes(); + let json: serde_json::Value = serde_json::from_slice(&body).unwrap(); + assert!(json.get("error").is_some()); +} + +#[tokio::test] +async fn post_swap_invalid_json_returns_400() { + let app = entangle::api::router(); + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/swap") + .header("content-type", "application/json") + .body(Body::from("not json")) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} diff --git a/tests/lib.rs b/tests/lib.rs index 8b13789..4da0f41 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1 +1,56 @@ +mod api; +mod server; +mod swap; +use std::str::FromStr; + +use bitcoin::{Amount, Network, OutPoint, ScriptBuf, TxOut}; + +use entangle::swap::{SwapRequest, Utxo}; + +pub const TESTNET_ADDR_A: &str = "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"; +pub const TESTNET_ADDR_B: &str = "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"; +pub const MAINNET_ADDR: &str = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; + +pub fn make_utxo(sats: u64, vout: u32) -> Utxo { + let outpoint = OutPoint::from_str(&format!( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:{vout}" + )) + .unwrap(); + Utxo { + outpoint, + txout: TxOut { + value: Amount::from_sat(sats), + script_pubkey: ScriptBuf::new(), + }, + } +} + +pub fn make_swap_request(two_sided: bool) -> SwapRequest { + SwapRequest { + utxo_a: make_utxo(50_000, 0), + address_a: TESTNET_ADDR_A.parse().unwrap(), + utxo_b: if two_sided { + Some(make_utxo(75_000, 1)) + } else { + None + }, + address_b: TESTNET_ADDR_B.parse().unwrap(), + network: Network::Testnet, + } +} + +pub fn swap_request_json(two_sided: bool) -> String { + serde_json::to_string(&make_swap_request(two_sided)).unwrap() +} + +pub fn mismatch_request_json() -> String { + let request = SwapRequest { + utxo_a: make_utxo(50_000, 0), + address_a: MAINNET_ADDR.parse().unwrap(), + utxo_b: None, + address_b: TESTNET_ADDR_B.parse().unwrap(), + network: Network::Testnet, + }; + serde_json::to_string(&request).unwrap() +} diff --git a/tests/server.rs b/tests/server.rs new file mode 100644 index 0000000..e89a4bb --- /dev/null +++ b/tests/server.rs @@ -0,0 +1,38 @@ +use axum::body::Body; +use axum::http::{Request, StatusCode}; +use http_body_util::BodyExt; +use tower::ServiceExt; + +use crate::*; + +#[tokio::test] +async fn get_index_returns_placeholder() { + let app = entangle::server::router(); + let response = app + .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = response.into_body().collect().await.unwrap().to_bytes(); + assert_eq!(&body[..], b"entangle frontend - coming soon"); +} + +#[tokio::test] +async fn with_api_nesting() { + let app = entangle::server::router().nest("/api", entangle::api::router()); + let response = app + .oneshot( + Request::builder() + .method("POST") + .uri("/api/swap") + .header("content-type", "application/json") + .body(Body::from(swap_request_json(true))) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); +} diff --git a/tests/swap.rs b/tests/swap.rs new file mode 100644 index 0000000..990ee49 --- /dev/null +++ b/tests/swap.rs @@ -0,0 +1,121 @@ +use bitcoin::Network; + +use entangle::swap::{SwapRequest, build_swap_psbt}; + +use crate::*; + +#[test] +fn two_sided_swap_produces_valid_psbt() { + let request = make_swap_request(true); + let psbt = build_swap_psbt(&request).unwrap(); + + assert_eq!(psbt.unsigned_tx.input.len(), 2); + assert_eq!(psbt.unsigned_tx.output.len(), 2); + + // Inputs reference the correct outpoints + assert_eq!( + psbt.unsigned_tx.input[0].previous_output, + request.utxo_a.outpoint + ); + assert_eq!( + psbt.unsigned_tx.input[1].previous_output, + request.utxo_b.as_ref().unwrap().outpoint + ); + + // Output 0: A's value → B's address + assert_eq!(psbt.unsigned_tx.output[0].value, request.utxo_a.txout.value); + let addr_b = request + .address_b + .clone() + .require_network(request.network) + .unwrap(); + assert_eq!( + psbt.unsigned_tx.output[0].script_pubkey, + addr_b.script_pubkey() + ); + + // Output 1: B's value → A's address + assert_eq!( + psbt.unsigned_tx.output[1].value, + request.utxo_b.as_ref().unwrap().txout.value + ); + let addr_a = request + .address_a + .clone() + .require_network(request.network) + .unwrap(); + assert_eq!( + psbt.unsigned_tx.output[1].script_pubkey, + addr_a.script_pubkey() + ); + + // Witness UTXOs set for signing + assert_eq!( + psbt.inputs[0].witness_utxo, + Some(request.utxo_a.txout.clone()) + ); + assert_eq!( + psbt.inputs[1].witness_utxo, + Some(request.utxo_b.as_ref().unwrap().txout.clone()) + ); +} + +#[test] +fn one_sided_swap_produces_valid_psbt() { + let request = make_swap_request(false); + let psbt = build_swap_psbt(&request).unwrap(); + + assert_eq!(psbt.unsigned_tx.input.len(), 1); + assert_eq!(psbt.unsigned_tx.output.len(), 1); + assert_eq!( + psbt.unsigned_tx.input[0].previous_output, + request.utxo_a.outpoint + ); + assert_eq!(psbt.unsigned_tx.output[0].value, request.utxo_a.txout.value); + assert_eq!( + psbt.inputs[0].witness_utxo, + Some(request.utxo_a.txout.clone()) + ); +} + +#[test] +fn network_mismatch_address_a() { + let request = SwapRequest { + utxo_a: make_utxo(50_000, 0), + address_a: MAINNET_ADDR.parse().unwrap(), + utxo_b: None, + address_b: TESTNET_ADDR_B.parse().unwrap(), + network: Network::Testnet, + }; + let err = build_swap_psbt(&request).unwrap_err(); + assert!(matches!( + err, + entangle::swap::SwapError::NetworkMismatch { .. } + )); +} + +#[test] +fn network_mismatch_address_b() { + let request = SwapRequest { + utxo_a: make_utxo(50_000, 0), + address_a: TESTNET_ADDR_A.parse().unwrap(), + utxo_b: None, + address_b: MAINNET_ADDR.parse().unwrap(), + network: Network::Testnet, + }; + let err = build_swap_psbt(&request).unwrap_err(); + assert!(matches!( + err, + entangle::swap::SwapError::NetworkMismatch { .. } + )); +} + +#[test] +fn psbt_base64_round_trips() { + let request = make_swap_request(true); + let psbt = build_swap_psbt(&request).unwrap(); + let base64_str = psbt.to_string(); + let parsed: bitcoin::Psbt = base64_str.parse().unwrap(); + assert_eq!(parsed.unsigned_tx.input.len(), 2); + assert_eq!(parsed.unsigned_tx.output.len(), 2); +} From 9074215dc73cf279a43b7dcf80eed807a01e3ffc Mon Sep 17 00:00:00 2001 From: parabit Date: Sun, 15 Feb 2026 20:32:53 -0600 Subject: [PATCH 2/7] leptos for frontend --- Cargo.lock | 2780 ++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 71 +- justfile | 4 +- src/app.rs | 50 + src/lib.rs | 16 + src/server.rs | 34 - src/server/mod.rs | 46 + tests/server.rs | 20 +- 8 files changed, 2695 insertions(+), 326 deletions(-) create mode 100644 src/app.rs delete mode 100644 src/server.rs create mode 100644 src/server/mod.rs diff --git a/Cargo.lock b/Cargo.lock index dffceae..3de8947 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.21" @@ -52,18 +61,94 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "any_spawner" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" +dependencies = [ + "futures", + "thiserror 2.0.18", + "tokio", + "wasm-bindgen-futures", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attribute-derive" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05832cdddc8f2650cc2cc187cc2e952b8c133a48eb055f35211f61ee81502d77" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7cdbbd4bd005c5d3e2e9c885e6fa575db4f4a3572335b974d8db853b6beb61" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn", +] + [[package]] name = "axum" version = "0.8.8" @@ -71,6 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -83,14 +169,17 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -116,6 +205,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + [[package]] name = "base58ck" version = "0.1.0" @@ -132,6 +227,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bech32" version = "0.11.1" @@ -145,7 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", - "base64", + "base64 0.21.7", "bech32", "bitcoin-internals", "bitcoin-io", @@ -193,12 +294,39 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" + [[package]] name = "cc" version = "1.2.56" @@ -209,6 +337,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "clap" version = "4.5.57" @@ -250,326 +384,1897 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] -name = "colorchoice" -version = "1.0.4" +name = "codee" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "entangle" -version = "0.0.1" +checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" dependencies = [ - "axum", - "bitcoin", - "clap", - "http-body-util", "serde", "serde_json", - "tokio", - "tower", + "thiserror 2.0.18", ] [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "collection_literals" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "colorchoice" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "percent-encoding", + "crossbeam-utils", ] [[package]] -name = "futures-channel" -version = "0.3.32" +name = "config" +version = "0.15.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" dependencies = [ - "futures-core", + "convert_case 0.6.0", + "pathdiff", + "serde_core", + "toml", + "winnow", ] [[package]] -name = "futures-core" -version = "0.3.32" +name = "console_error_panic_hook" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] [[package]] -name = "futures-task" -version = "0.3.32" +name = "const-str" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b0664d2867b4a32697dfe655557f5c3b187e9b605b38612a748e5ec99811d160" [[package]] -name = "futures-util" -version = "0.3.32" +name = "const_format" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", + "const_format_proc_macros", ] [[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex-conservative" -version = "0.2.2" +name = "const_format_proc_macros" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ - "arrayvec", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] -name = "hex_lit" -version = "0.1.1" +name = "const_str_slice_concat" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" [[package]] -name = "http" -version = "1.4.0" +name = "convert_case" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" dependencies = [ - "bytes", - "itoa", + "unicode-segmentation", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "convert_case" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ - "bytes", - "http", + "unicode-segmentation", ] [[package]] -name = "http-body-util" -version = "0.1.3" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "unicode-segmentation", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] -name = "httpdate" -version = "1.0.3" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "hyper" -version = "1.8.1" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", + "generic-array", + "typenum", ] [[package]] -name = "hyper-util" -version = "0.1.20" +name = "dashmap" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ - "bytes", - "http", - "http-body", - "hyper", - "pin-project-lite", - "tokio", - "tower-service", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "data-encoding" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] -name = "itoa" -version = "1.0.17" +name = "derive-where" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "libc" -version = "0.2.182" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] -name = "log" -version = "0.4.29" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "matchit" -version = "0.8.4" +name = "drain_filter_polyfill" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" [[package]] -name = "memchr" -version = "2.8.0" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "mime" -version = "0.3.17" +name = "either_of" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "216d23e0ec69759a17f05e1c553f3a6870e5ec73420fbb07807a6f34d5d1d5a4" +dependencies = [ + "paste", + "pin-project-lite", +] [[package]] -name = "mio" -version = "1.1.1" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "cfg-if", ] [[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +name = "entangle" +version = "0.0.1" +dependencies = [ + "axum", + "bitcoin", + "clap", + "console_error_panic_hook", + "http-body-util", + "leptos", + "leptos_axum", + "leptos_meta", + "leptos_router", + "serde", + "serde_json", + "tokio", + "tower", + "wasm-bindgen", +] [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "guardian" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hydration_context" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8714ae4adeaa846d838f380fbd72f049197de629948f91bf045329e0cf0a283" +dependencies = [ + "futures", + "js-sys", + "once_cell", + "or_poisoned", + "pin-project-lite", + "serde", + "throw_error", + "wasm-bindgen", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "leptos" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9569fc37575a5d64c0512145af7630bf651007237ef67a8a77328199d315bb" +dependencies = [ + "any_spawner", + "base64 0.22.1", + "cfg-if", + "either_of", + "futures", + "getrandom 0.3.4", + "hydration_context", + "leptos_config", + "leptos_dom", + "leptos_hot_reload", + "leptos_macro", + "leptos_server", + "oco_ref", + "or_poisoned", + "paste", + "rand", + "reactive_graph", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn", + "slotmap", + "tachys", + "thiserror 2.0.18", + "throw_error", + "typed-builder 0.23.2", + "typed-builder-macro 0.23.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_split_helpers", + "web-sys", +] + +[[package]] +name = "leptos_axum" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0caa95760f87f3067e05025140becefdbdfd36cbc2adac4519f06e1f1edf4af" +dependencies = [ + "any_spawner", + "axum", + "dashmap", + "futures", + "hydration_context", + "leptos", + "leptos_integration_utils", + "leptos_macro", + "leptos_meta", + "leptos_router", + "parking_lot", + "server_fn", + "tachys", + "tokio", + "tower", + "tower-http", +] + +[[package]] +name = "leptos_config" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071fc40aeb9fcab885965bad1887990477253ad51f926cd19068f45a44c59e89" +dependencies = [ + "config", + "regex", + "serde", + "thiserror 2.0.18", + "typed-builder 0.21.2", +] + +[[package]] +name = "leptos_dom" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f4330c88694c5575e0bfe4eecf81b045d14e76a4f8b00d5fd2a63f8779f895" +dependencies = [ + "js-sys", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "tachys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38" +dependencies = [ + "anyhow", + "camino", + "indexmap", + "parking_lot", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn", + "walkdir", +] + +[[package]] +name = "leptos_integration_utils" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13cccc9305df53757bae61bf15641bfa6a667b5f78456ace4879dfe0591ae0e8" +dependencies = [ + "futures", + "hydration_context", + "leptos", + "leptos_config", + "leptos_meta", + "leptos_router", + "reactive_graph", +] + +[[package]] +name = "leptos_macro" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86ffd2e9cf3e264e9b3e16bdb086cefa26bd0fa7bc6a26b0cc5f6c1fd3178ed" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case 0.10.0", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error2", + "proc-macro2", + "quote", + "rstml", + "rustc_version", + "server_fn_macro", + "syn", + "uuid", +] + +[[package]] +name = "leptos_meta" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578" +dependencies = [ + "futures", + "indexmap", + "leptos", + "or_poisoned", + "send_wrapper", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_router" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e573711f2fb9ab5d655ec38115220d359eaaf1dcb93cc0ea624543b6dba959" +dependencies = [ + "any_spawner", + "either_of", + "futures", + "gloo-net", + "js-sys", + "leptos", + "leptos_router_macro", + "or_poisoned", + "percent-encoding", + "reactive_graph", + "rustc_version", + "send_wrapper", + "tachys", + "thiserror 2.0.18", + "url", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_router_macro" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409c0bd99f986c3cfa1a4db2443c835bc602ded1a12784e22ecb28c3ed5a2ae2" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "leptos_server" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf1045af93050bf3388d1c138426393fc131f6d9e46a65519da884c033ed730" +dependencies = [ + "any_spawner", + "base64 0.22.1", + "codee", + "futures", + "hydration_context", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "serde", + "serde_json", + "server_fn", + "tachys", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "linear-map" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "next_tuple" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" + +[[package]] +name = "oco_ref" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" +dependencies = [ + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "or_poisoned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "reactive_graph" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f0df355582937223ea403e52490201d65295bd6981383c69bfae5a1f8730c2" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "hydration_context", + "indexmap", + "or_poisoned", + "paste", + "pin-project-lite", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "slotmap", + "thiserror 2.0.18", + "web-sys", +] + +[[package]] +name = "reactive_stores" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35372f05664a62a3dd389503371a15b8feb3396f99f6ec000de651fddb030942" +dependencies = [ + "dashmap", + "guardian", + "itertools", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores_macro", + "rustc-hash", + "send_wrapper", +] + +[[package]] +name = "reactive_stores_macro" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa40919eb2975100283b2a70e68eafce1e8bcf81f0622ff168e4c2b3f8d46bb" +dependencies = [ + "convert_case 0.8.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rstml" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" +dependencies = [ + "derive-where", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", + "syn_derive", + "thiserror 2.0.18", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_qs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "server_fn" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353d02fa2886cd8dae0b8da0965289fa8f2ecc7df633d1ce965f62fdf9644d29" +dependencies = [ + "axum", + "base64 0.22.1", + "bytes", + "const-str", + "const_format", + "dashmap", + "futures", + "gloo-net", + "http", + "http-body-util", + "hyper", + "inventory", + "js-sys", + "pin-project-lite", + "rustc_version", + "rustversion", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror 2.0.18", + "throw_error", + "tokio", + "tower", + "tower-layer", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950b8cfc9ff5f39ca879c5a7c5e640de2695a199e18e424c3289d0964cabe642" +dependencies = [ + "const_format", + "convert_case 0.8.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63eb08f80db903d3c42f64e60ebb3875e0305be502bdc064ec0a0eab42207f00" +dependencies = [ + "server_fn_macro", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "syn" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ + "proc-macro2", + "quote", "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.44" +name = "syn_derive" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" dependencies = [ + "proc-macro-error2", "proc-macro2", + "quote", + "syn", ] [[package]] -name = "ryu" -version = "1.0.23" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] -name = "secp256k1" -version = "0.29.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "bitcoin_hashes", - "secp256k1-sys", - "serde", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "secp256k1-sys" -version = "0.10.1" +name = "tachys" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +checksum = "f2b2db11e455f7e84e2cc3e76f8a3f3843f7956096265d5ecff781eabe235077" dependencies = [ - "cc", + "any_spawner", + "async-trait", + "const_str_slice_concat", + "drain_filter_polyfill", + "either_of", + "erased", + "futures", + "html-escape", + "indexmap", + "itertools", + "js-sys", + "linear-map", + "next_tuple", + "oco_ref", + "or_poisoned", + "parking_lot", + "paste", + "reactive_graph", + "reactive_stores", + "rustc-hash", + "rustc_version", + "send_wrapper", + "slotmap", + "throw_error", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "serde" -version = "1.0.228" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "serde_core", - "serde_derive", + "thiserror-impl 1.0.69", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "serde_derive", + "thiserror-impl 2.0.18", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -577,182 +2282,511 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "throw_error" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0ed6038fcbc0795aca7c92963ddda636573b956679204e044492d2b13c8f64" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "itoa", - "memchr", - "serde", "serde_core", - "zmij", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "typed-builder" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" +dependencies = [ + "typed-builder-macro 0.21.2", +] + +[[package]] +name = "typed-builder" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" +dependencies = [ + "typed-builder-macro 0.23.2", +] + +[[package]] +name = "typed-builder-macro" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typed-builder-macro" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "serde_path_to_error" -version = "0.1.20" +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ - "itoa", + "form_urlencoded", + "idna", + "percent-encoding", "serde", - "serde_core", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "getrandom 0.4.1", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "slab" -version = "0.4.12" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] -name = "smallvec" -version = "1.15.1" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "socket2" -version = "0.6.2" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "libc", - "windows-sys 0.60.2", + "wit-bindgen", ] [[package]] -name = "strsim" -version = "0.11.1" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] [[package]] -name = "syn" -version = "2.0.114" +name = "wasm-bindgen" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "sync_wrapper" -version = "1.0.2" +name = "wasm-bindgen-futures" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] [[package]] -name = "tokio" -version = "1.49.0" +name = "wasm-bindgen-macro" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "tokio-macros" -version = "2.6.0" +name = "wasm-bindgen-macro-support" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", + "wasm-bindgen-shared", ] [[package]] -name = "tower" -version = "0.5.3" +name = "wasm-bindgen-shared" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", + "unicode-ident", ] [[package]] -name = "tower-layer" -version = "0.3.3" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] [[package]] -name = "tower-service" -version = "0.3.3" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] [[package]] -name = "tracing" -version = "0.1.44" +name = "wasm-streams" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ - "log", - "pin-project-lite", - "tracing-core", + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "tracing-core" -version = "0.1.36" +name = "wasm_split_helpers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "a114b3073258dd5de3d812cdd048cca6842342755e828a14dbf15f843f2d1b84" dependencies = [ - "once_cell", + "async-once-cell", + "wasm_split_macros", ] [[package]] -name = "unicode-ident" -version = "1.0.22" +name = "wasm_split_macros" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "56481f8ed1a9f9ae97ea7b08a5e2b12e8adf9a7818a6ba952b918e09c7be8bf0" +dependencies = [ + "base16", + "quote", + "sha2", + "syn", +] [[package]] -name = "utf8parse" -version = "0.2.2" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] [[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" +name = "web-sys" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] name = "windows-link" @@ -843,6 +2877,218 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 391d840..d35b174 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true rust-version.workspace = true [workspace] -members = ["."] [workspace.package] authors = ["The Parasite Devs"] @@ -25,19 +24,56 @@ rust-version = "1.91.0" axum = "0.8" bitcoin = { version = "0.32", features = ["base64", "serde", "std"] } clap = { version = "4.5.36", features = ["derive", "env"] } +console_error_panic_hook = "0.1" http-body-util = "0.1" +leptos = "0.8" +leptos_axum = "0.8" +leptos_meta = "0.8" +leptos_router = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tower = { version = "0.5", features = ["util"] } +wasm-bindgen = "0.2" + +[features] +default = ["ssr"] +ssr = [ + "dep:axum", + "dep:bitcoin", + "dep:clap", + "dep:leptos_axum", + "dep:serde", + "dep:serde_json", + "dep:tokio", + "leptos/ssr", + "leptos_meta/ssr", + "leptos_router/ssr", +] +hydrate = [ + "dep:console_error_panic_hook", + "dep:wasm-bindgen", + "leptos/hydrate", +] [dependencies] -axum.workspace = true -bitcoin.workspace = true -clap.workspace = true -serde.workspace = true -serde_json.workspace = true -tokio.workspace = true +# Shared (compiled for both SSR and hydrate) +leptos = { workspace = true } +leptos_meta = { workspace = true } +leptos_router = { workspace = true } + +# SSR-only +axum = { workspace = true, optional = true } +bitcoin = { workspace = true, optional = true } +clap = { workspace = true, optional = true } +leptos_axum = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } + +# Hydrate-only +console_error_panic_hook = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } [dev-dependencies] http-body-util.workspace = true @@ -46,11 +82,32 @@ tower.workspace = true [[bin]] name = "entangle" path = "src/bin/main.rs" +required-features = ["ssr"] [lib] name = "entangle" path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] [[test]] name = "integration" path = "tests/lib.rs" +required-features = ["ssr"] + +[package.metadata.leptos] +output-name = "entangle" +site-root = "target/site" +site-pkg-dir = "pkg" +site-addr = "127.0.0.1:8080" +reload-port = 3001 +bin-features = ["ssr"] +bin-default-features = false +lib-features = ["hydrate"] +lib-default-features = false +lib-profile-release = "wasm-release" + +[profile.wasm-release] +inherits = "release" +opt-level = "z" +lto = true +codegen-units = 1 diff --git a/justfile b/justfile index 230d1fd..1c13fa8 100644 --- a/justfile +++ b/justfile @@ -4,7 +4,7 @@ set dotenv-filename := ".env.local" import? 'justfile.local' dev: - cargo run + cargo run -- server init: hermit init --quiet @@ -49,4 +49,4 @@ publish-release revision='master': git checkout {{ revision }} cargo publish cd ../.. - rm -rf tmp/release \ No newline at end of file + rm -rf tmp/release diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..f82ae44 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,50 @@ +use leptos::prelude::*; +use leptos_meta::MetaTags; +use leptos_router::{ + StaticSegment, + components::{Route, Router, Routes}, +}; + +/// HTML shell wrapping the App. Used server-side to render the full document. +#[cfg(feature = "ssr")] +pub fn shell(options: leptos::config::LeptosOptions) -> impl IntoView { + view! { + + + + + + + + + + + + + + } +} + +/// Root application component, shared between server and client. +#[component] +pub fn App() -> impl IntoView { + leptos_meta::provide_meta_context(); + + view! { + +
+ "Page not found"

}> + +
+
+
+ } +} + +#[component] +fn HomePage() -> impl IntoView { + view! { +

"entangle"

+

"Swaps made easy"

+ } +} diff --git a/src/lib.rs b/src/lib.rs index 69d8ca3..ed996ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,16 @@ +pub mod app; + +#[cfg(feature = "ssr")] pub mod api; +#[cfg(feature = "ssr")] pub mod server; +#[cfg(feature = "ssr")] pub mod swap; +#[cfg(feature = "ssr")] use clap::{Parser, Subcommand}; +#[cfg(feature = "ssr")] #[derive(Parser)] #[command(version, author, about = "Swaps made easy")] pub struct Args { @@ -11,6 +18,7 @@ pub struct Args { pub command: Command, } +#[cfg(feature = "ssr")] #[derive(Subcommand)] pub enum Command { /// Start the REST API server. @@ -30,6 +38,7 @@ pub enum Command { }, } +#[cfg(feature = "ssr")] pub async fn main() { let args = Args::parse(); @@ -42,3 +51,10 @@ pub async fn main() { } } } + +#[cfg(feature = "hydrate")] +#[wasm_bindgen::prelude::wasm_bindgen] +pub fn hydrate() { + console_error_panic_hook::set_once(); + leptos::mount::hydrate_body(app::App); +} diff --git a/src/server.rs b/src/server.rs deleted file mode 100644 index 5ec196d..0000000 --- a/src/server.rs +++ /dev/null @@ -1,34 +0,0 @@ -use axum::{Router, routing::get}; - -/// Build the frontend router. -/// -/// Currently a placeholder. Will be replaced with Leptos SSR routes. -pub fn router() -> Router { - Router::new().route("/", get(index)) -} - -async fn index() -> &'static str { - "entangle frontend - coming soon" -} - -/// Start the frontend server. -/// -/// If `with_api` is true, the API routes are nested under `/api`. -pub async fn serve(bind: &str, with_api: bool) { - let mut app = router(); - - if with_api { - app = app.nest("/api", crate::api::router()); - } - - let listener = tokio::net::TcpListener::bind(bind) - .await - .expect("failed to bind frontend server"); - println!("Frontend server listening on {bind}"); - if with_api { - println!("API routes available at /api/*"); - } - axum::serve(listener, app) - .await - .expect("frontend server error"); -} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..d9e39f1 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,46 @@ +use axum::Router; +use leptos::config::LeptosOptions; +use leptos_axum::{LeptosRoutes, generate_route_list}; + +use crate::app::{App, shell}; + +/// Build the server router with Leptos SSR. +/// +/// When `with_api` is true, the API routes are nested under `/api`. +pub fn router(leptos_options: LeptosOptions, with_api: bool) -> Router { + let routes = generate_route_list(App); + + let mut app = Router::new(); + + if with_api { + app = app.nest_service("/api", crate::api::router()); + } + + app.leptos_routes(&leptos_options, routes, { + let leptos_options = leptos_options.clone(); + move || shell(leptos_options.clone()) + }) + .fallback(leptos_axum::file_and_error_handler::( + shell, + )) + .with_state(leptos_options) +} + +/// Start the frontend server with Leptos SSR. +/// +/// If `with_api` is true, the API routes are nested under `/api`. +pub async fn serve(bind: &str, with_api: bool) { + let conf = leptos::config::get_configuration(Some("Cargo.toml")).unwrap(); + let leptos_options = conf.leptos_options; + + let app = router(leptos_options, with_api); + + let listener = tokio::net::TcpListener::bind(bind) + .await + .expect("failed to bind server"); + println!("Server listening on {bind}"); + if with_api { + println!("API routes available at /api/*"); + } + axum::serve(listener, app).await.expect("server error"); +} diff --git a/tests/server.rs b/tests/server.rs index e89a4bb..0ca11d1 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1,27 +1,15 @@ use axum::body::Body; use axum::http::{Request, StatusCode}; -use http_body_util::BodyExt; +use leptos::config::get_configuration; use tower::ServiceExt; use crate::*; -#[tokio::test] -async fn get_index_returns_placeholder() { - let app = entangle::server::router(); - let response = app - .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) - .await - .unwrap(); - - assert_eq!(response.status(), StatusCode::OK); - - let body = response.into_body().collect().await.unwrap().to_bytes(); - assert_eq!(&body[..], b"entangle frontend - coming soon"); -} - #[tokio::test] async fn with_api_nesting() { - let app = entangle::server::router().nest("/api", entangle::api::router()); + let conf = get_configuration(None).unwrap(); + let leptos_options = conf.leptos_options; + let app = entangle::server::router(leptos_options, true); let response = app .oneshot( Request::builder() From 3d5df00a304acde4c573b638d1e36ea50657b1d9 Mon Sep 17 00:00:00 2001 From: parabit Date: Sun, 15 Feb 2026 22:19:42 -0600 Subject: [PATCH 3/7] layout and build improvements --- .gitignore | 1 + Cargo.toml | 44 +++++++++++++++++++----------- justfile | 7 ++++- src/app.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 20 ++++++++------ 5 files changed, 121 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 9d49729..9505327 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /.hermit /.idea +.env.local justfile.local \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d35b174..10df90b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,40 +37,51 @@ tower = { version = "0.5", features = ["util"] } wasm-bindgen = "0.2" [features] -default = ["ssr"] -ssr = [ +default = ["server"] +api = [ "dep:axum", "dep:bitcoin", "dep:clap", - "dep:leptos_axum", "dep:serde", "dep:serde_json", "dep:tokio", - "leptos/ssr", - "leptos_meta/ssr", - "leptos_router/ssr", ] hydrate = [ "dep:console_error_panic_hook", + "dep:leptos", + "dep:leptos_meta", + "dep:leptos_router", "dep:wasm-bindgen", "leptos/hydrate", ] +server = [ + "api", + "dep:leptos", + "dep:leptos_axum", + "dep:leptos_meta", + "dep:leptos_router", + "leptos/ssr", + "leptos_meta/ssr", + "leptos_router/ssr", +] [dependencies] -# Shared (compiled for both SSR and hydrate) -leptos = { workspace = true } -leptos_meta = { workspace = true } -leptos_router = { workspace = true } - -# SSR-only +# API + swap core (enabled by api feature) axum = { workspace = true, optional = true } bitcoin = { workspace = true, optional = true } clap = { workspace = true, optional = true } -leptos_axum = { workspace = true, optional = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } tokio = { workspace = true, optional = true } +# Leptos (enabled by server or hydrate features) +leptos = { workspace = true, optional = true } +leptos_meta = { workspace = true, optional = true } +leptos_router = { workspace = true, optional = true } + +# Server-only +leptos_axum = { workspace = true, optional = true } + # Hydrate-only console_error_panic_hook = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } @@ -82,7 +93,7 @@ tower.workspace = true [[bin]] name = "entangle" path = "src/bin/main.rs" -required-features = ["ssr"] +required-features = ["api"] [lib] name = "entangle" @@ -92,7 +103,7 @@ crate-type = ["cdylib", "rlib"] [[test]] name = "integration" path = "tests/lib.rs" -required-features = ["ssr"] +required-features = ["server"] [package.metadata.leptos] output-name = "entangle" @@ -100,10 +111,11 @@ site-root = "target/site" site-pkg-dir = "pkg" site-addr = "127.0.0.1:8080" reload-port = 3001 -bin-features = ["ssr"] +bin-features = ["server"] bin-default-features = false lib-features = ["hydrate"] lib-default-features = false +assets-dir = "public" lib-profile-release = "wasm-release" [profile.wasm-release] diff --git a/justfile b/justfile index 1c13fa8..3fc403d 100644 --- a/justfile +++ b/justfile @@ -4,13 +4,18 @@ set dotenv-filename := ".env.local" import? 'justfile.local' dev: - cargo run -- server + cargo leptos watch server + +serve: + cargo leptos serve server init: hermit init --quiet hermit install just hermit install rustup rustup default stable + rustup target add wasm32-unknown-unknown + cargo install cargo-leptos cargo clean cargo build diff --git a/src/app.rs b/src/app.rs index f82ae44..ad9dba6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,12 +1,12 @@ use leptos::prelude::*; -use leptos_meta::MetaTags; +use leptos_meta::{Link, MetaTags, Title}; use leptos_router::{ StaticSegment, components::{Route, Router, Routes}, }; /// HTML shell wrapping the App. Used server-side to render the full document. -#[cfg(feature = "ssr")] +#[cfg(feature = "server")] pub fn shell(options: leptos::config::LeptosOptions) -> impl IntoView { view! { @@ -18,7 +18,7 @@ pub fn shell(options: leptos::config::LeptosOptions) -> impl IntoView { - + @@ -31,16 +31,86 @@ pub fn App() -> impl IntoView { leptos_meta::provide_meta_context(); view! { + + <Link rel="icon" type_="image/svg+xml" href="/favicon.svg"/> <Router> - <main> + <Layout> <Routes fallback=|| view! { <p>"Page not found"</p> }> <Route path=StaticSegment("") view=HomePage/> </Routes> - </main> + </Layout> </Router> } } +/// Page layout with top nav bar, slide-out sidebar, and main content area. +#[component] +fn Layout(children: Children) -> impl IntoView { + let sidebar_open = RwSignal::new(false); + + view! { + <NavBar sidebar_open/> + <Sidebar sidebar_open/> + <main style="margin-top: 48px; padding: 24px;"> + {children()} + </main> + } +} + +/// Top navigation bar. +#[component] +fn NavBar(sidebar_open: RwSignal<bool>) -> impl IntoView { + let toggle = move |_| sidebar_open.update(|open| *open = !*open); + + view! { + <nav style="position: fixed; top: 0; left: 0; right: 0; height: 48px; \ + background: #1a1a2e; color: #fff; display: flex; \ + align-items: center; padding: 0 16px; z-index: 100; \ + box-shadow: 0 2px 4px rgba(0,0,0,0.2);"> + <button + on:click=toggle + style="background: none; border: none; color: #fff; \ + font-size: 20px; cursor: pointer; margin-right: 16px; \ + padding: 4px 8px;" + > + {move || if sidebar_open.get() { "\u{2715}" } else { "\u{2630}" }} + </button> + <span style="font-weight: 700; font-size: 18px; margin-right: 32px;"> + "entangle" + </span> + <a href="/" style="color: #ccc; text-decoration: none; padding: 4px 12px; \ + font-size: 14px;"> + "Home" + </a> + </nav> + } +} + +/// Slide-out sidebar panel. +#[component] +fn Sidebar(sidebar_open: RwSignal<bool>) -> impl IntoView { + let style = move || { + let translate = if sidebar_open.get() { + "translateX(0%)" + } else { + "translateX(-100%)" + }; + format!( + "position: fixed; top: 48px; left: 0; bottom: 0; width: 240px; \ + background: #16213e; z-index: 90; \ + transition: transform 0.2s ease; \ + transform: {translate}; \ + box-shadow: 2px 0 8px rgba(0,0,0,0.15);" + ) + }; + + view! { + <aside style=style> + <div style="padding: 16px;"></div> + </aside> + } +} + #[component] fn HomePage() -> impl IntoView { view! { diff --git a/src/lib.rs b/src/lib.rs index ed996ee..c4c2bf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,16 @@ -pub mod app; - -#[cfg(feature = "ssr")] +#[cfg(feature = "api")] pub mod api; -#[cfg(feature = "ssr")] +#[cfg(any(feature = "server", feature = "hydrate"))] +pub mod app; +#[cfg(feature = "server")] pub mod server; -#[cfg(feature = "ssr")] +#[cfg(feature = "api")] pub mod swap; -#[cfg(feature = "ssr")] +#[cfg(feature = "api")] use clap::{Parser, Subcommand}; -#[cfg(feature = "ssr")] +#[cfg(feature = "api")] #[derive(Parser)] #[command(version, author, about = "Swaps made easy")] pub struct Args { @@ -18,7 +18,7 @@ pub struct Args { pub command: Command, } -#[cfg(feature = "ssr")] +#[cfg(feature = "api")] #[derive(Subcommand)] pub enum Command { /// Start the REST API server. @@ -28,6 +28,7 @@ pub enum Command { bind: String, }, /// Start the frontend web server. + #[cfg(feature = "server")] Server { /// Address to bind the frontend server to. #[arg(long, default_value = "0.0.0.0:8080", env = "ENTANGLE_SERVER_BIND")] @@ -38,7 +39,7 @@ pub enum Command { }, } -#[cfg(feature = "ssr")] +#[cfg(feature = "api")] pub async fn main() { let args = Args::parse(); @@ -46,6 +47,7 @@ pub async fn main() { Command::Api { bind } => { api::serve(&bind).await; } + #[cfg(feature = "server")] Command::Server { bind, with_api } => { server::serve(&bind, with_api).await; } From 61acb73a81787492c0806597e3e4eab84c797262 Mon Sep 17 00:00:00 2001 From: parabit <parabit@parasite.dev> Date: Sun, 15 Feb 2026 22:20:14 -0600 Subject: [PATCH 4/7] add favicon --- public/favicon.svg | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 public/favicon.svg diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..a109b1d --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> + <rect width="64" height="64" rx="12" fill="#1a1a2e"/> + <g fill="none" stroke-width="4" stroke-linecap="round"> + <ellipse cx="24" cy="32" rx="14" ry="18" transform="rotate(-30 24 32)" stroke="#6c63ff"/> + <ellipse cx="40" cy="32" rx="14" ry="18" transform="rotate(30 40 32)" stroke="#f72585"/> + </g> +</svg> From 3d6ac3dc4490e882b919340334129ec269172f93 Mon Sep 17 00:00:00 2001 From: parabit <parabit@parasite.dev> Date: Sun, 15 Feb 2026 22:42:35 -0600 Subject: [PATCH 5/7] apply theme --- src/app.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/app.rs b/src/app.rs index ad9dba6..1db94e1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,6 +3,7 @@ use leptos_meta::{Link, MetaTags, Title}; use leptos_router::{ StaticSegment, components::{Route, Router, Routes}, + hooks::use_location, }; /// HTML shell wrapping the App. Used server-side to render the full document. @@ -18,7 +19,8 @@ pub fn shell(options: leptos::config::LeptosOptions) -> impl IntoView { <HydrationScripts options/> <MetaTags/> </head> - <body style="margin: 0; font-family: system-ui, -apple-system, sans-serif;"> + <body style="margin: 0; font-family: system-ui, -apple-system, sans-serif; \ + background: #0f0f23; color: #e0e0e0;"> <App/> </body> </html> @@ -37,6 +39,7 @@ pub fn App() -> impl IntoView { <Layout> <Routes fallback=|| view! { <p>"Page not found"</p> }> <Route path=StaticSegment("") view=HomePage/> + <Route path=StaticSegment("listings") view=ListingsPage/> </Routes> </Layout> </Router> @@ -61,6 +64,8 @@ fn Layout(children: Children) -> impl IntoView { #[component] fn NavBar(sidebar_open: RwSignal<bool>) -> impl IntoView { let toggle = move |_| sidebar_open.update(|open| *open = !*open); + let location = use_location(); + let show_toggle = move || location.pathname.get() != "/" || sidebar_open.get(); view! { <nav style="position: fixed; top: 0; left: 0; right: 0; height: 48px; \ @@ -69,23 +74,43 @@ fn NavBar(sidebar_open: RwSignal<bool>) -> impl IntoView { box-shadow: 0 2px 4px rgba(0,0,0,0.2);"> <button on:click=toggle - style="background: none; border: none; color: #fff; \ - font-size: 20px; cursor: pointer; margin-right: 16px; \ - padding: 4px 8px;" + style=move || format!( + "background: none; border: none; color: #fff; \ + font-size: 20px; cursor: pointer; margin-right: 16px; \ + padding: 4px 8px; visibility: {};", + if show_toggle() { "visible" } else { "hidden" } + ) > {move || if sidebar_open.get() { "\u{2715}" } else { "\u{2630}" }} </button> <span style="font-weight: 700; font-size: 18px; margin-right: 32px;"> "entangle" </span> - <a href="/" style="color: #ccc; text-decoration: none; padding: 4px 12px; \ - font-size: 14px;"> - "Home" - </a> + <NavLink href="/" label="Home"/> + <NavLink href="/listings" label="Listings"/> </nav> } } +/// A navigation link that highlights when active. +#[component] +fn NavLink(href: &'static str, label: &'static str) -> impl IntoView { + let location = use_location(); + let is_active = move || location.pathname.get() == href; + + view! { + <a + href=href + style=move || format!( + "text-decoration: none; padding: 4px 12px; font-size: 14px; color: {};", + if is_active() { "#6c63ff" } else { "#ccc" } + ) + > + {label} + </a> + } +} + /// Slide-out sidebar panel. #[component] fn Sidebar(sidebar_open: RwSignal<bool>) -> impl IntoView { @@ -114,7 +139,17 @@ fn Sidebar(sidebar_open: RwSignal<bool>) -> impl IntoView { #[component] fn HomePage() -> impl IntoView { view! { - <h1>"entangle"</h1> - <p>"Swaps made easy"</p> + <div style="text-align: center; padding-top: 80px;"> + <h1 style="font-size: 48px; margin: 0; color: #6c63ff;">"entangle"</h1> + <p style="font-size: 20px; color: #f72585; margin-top: 8px;">"Swaps made easy"</p> + </div> + } +} + +#[component] +fn ListingsPage() -> impl IntoView { + view! { + <h1 style="color: #6c63ff;">"Listings"</h1> + <p>"No listings yet."</p> } } From c8ab42a103693b3eed00d3ceeeed5e9bdfddccba Mon Sep 17 00:00:00 2001 From: parabit <parabit@parasite.dev> Date: Sun, 15 Feb 2026 22:46:10 -0600 Subject: [PATCH 6/7] adjust viewport when slide-out menu is open --- src/app.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 1db94e1..abc1ed1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -54,7 +54,11 @@ fn Layout(children: Children) -> impl IntoView { view! { <NavBar sidebar_open/> <Sidebar sidebar_open/> - <main style="margin-top: 48px; padding: 24px;"> + <main style=move || format!( + "margin-top: 48px; padding: 24px; \ + margin-left: {}px; transition: margin-left 0.2s ease;", + if sidebar_open.get() { 240 } else { 0 } + )> {children()} </main> } From 10a883ba65e7bd28398358ce5705fd5758da8c77 Mon Sep 17 00:00:00 2001 From: parabit <parabit@parasite.dev> Date: Mon, 16 Feb 2026 01:14:38 -0600 Subject: [PATCH 7/7] add listing form to sidebar --- .gitignore | 3 +- Cargo.lock | 383 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 + src/app.rs | 213 ++++++++++++++++++++++++-- src/lib.rs | 2 + src/listing.rs | 204 ++++++++++++++++++++++++ src/server/mod.rs | 7 +- tests/lib.rs | 1 + tests/listing.rs | 139 +++++++++++++++++ tests/server.rs | 8 +- 10 files changed, 942 insertions(+), 22 deletions(-) create mode 100644 src/listing.rs create mode 100644 tests/listing.rs diff --git a/.gitignore b/.gitignore index 9505327..c645ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /.idea .env.local -justfile.local \ No newline at end of file +justfile.local +listings.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3de8947..290344a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.5.57" @@ -618,6 +624,7 @@ dependencies = [ "leptos_axum", "leptos_meta", "leptos_router", + "reqwest", "serde", "serde_json", "tokio", @@ -777,6 +784,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -981,6 +1001,24 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", ] [[package]] @@ -989,13 +1027,21 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -1133,6 +1179,22 @@ dependencies = [ "rustversion", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1425,6 +1487,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "manyhow" version = "0.11.4" @@ -1700,6 +1768,61 @@ dependencies = [ "yansi", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.44" @@ -1858,6 +1981,58 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rstml" version = "0.12.1" @@ -1888,6 +2063,41 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2178,6 +2388,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.114" @@ -2206,6 +2422,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -2311,6 +2530,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -2337,6 +2571,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -2424,12 +2668,14 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", + "iri-string", "mime", "mime_guess", "percent-encoding", "pin-project-lite", "tokio", "tokio-util", + "tower", "tower-layer", "tower-service", "tracing", @@ -2467,6 +2713,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tungstenite" version = "0.28.0" @@ -2554,6 +2806,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -2617,6 +2875,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2779,6 +3046,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -2794,13 +3080,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -2812,6 +3107,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -2819,58 +3130,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -3056,6 +3415,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 10df90b..d0303e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ leptos = "0.8" leptos_axum = "0.8" leptos_meta = "0.8" leptos_router = "0.8" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } @@ -42,6 +43,7 @@ api = [ "dep:axum", "dep:bitcoin", "dep:clap", + "dep:reqwest", "dep:serde", "dep:serde_json", "dep:tokio", @@ -51,6 +53,7 @@ hydrate = [ "dep:leptos", "dep:leptos_meta", "dep:leptos_router", + "dep:serde", "dep:wasm-bindgen", "leptos/hydrate", ] @@ -70,6 +73,7 @@ server = [ axum = { workspace = true, optional = true } bitcoin = { workspace = true, optional = true } clap = { workspace = true, optional = true } +reqwest = { workspace = true, optional = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } tokio = { workspace = true, optional = true } diff --git a/src/app.rs b/src/app.rs index abc1ed1..849ecd8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,15 @@ use leptos::prelude::*; -use leptos_meta::{Link, MetaTags, Title}; +#[cfg(feature = "server")] +use leptos_meta::MetaTags; +use leptos_meta::{Link, Title}; use leptos_router::{ StaticSegment, components::{Route, Router, Routes}, hooks::use_location, }; +use crate::listing::{CreateListing, Listing, list_listings}; + /// HTML shell wrapping the App. Used server-side to render the full document. #[cfg(feature = "server")] pub fn shell(options: leptos::config::LeptosOptions) -> impl IntoView { @@ -50,14 +54,16 @@ pub fn App() -> impl IntoView { #[component] fn Layout(children: Children) -> impl IntoView { let sidebar_open = RwSignal::new(false); + let create_action = ServerAction::<CreateListing>::new(); + provide_context(create_action.version()); view! { <NavBar sidebar_open/> - <Sidebar sidebar_open/> + <Sidebar sidebar_open create_action/> <main style=move || format!( "margin-top: 48px; padding: 24px; \ margin-left: {}px; transition: margin-left 0.2s ease;", - if sidebar_open.get() { 240 } else { 0 } + if sidebar_open.get() { 280 } else { 0 } )> {children()} </main> @@ -115,9 +121,22 @@ fn NavLink(href: &'static str, label: &'static str) -> impl IntoView { } } -/// Slide-out sidebar panel. +const INPUT_STYLE: &str = "width: 100%; padding: 6px 8px; background: #1a1a2e; \ + border: 1px solid #333; color: #e0e0e0; border-radius: 4px; \ + font-size: 13px; box-sizing: border-box;"; + +const LABEL_STYLE: &str = "display: block; font-size: 11px; color: #999; \ + margin: 8px 0 2px 0;"; + +/// Slide-out sidebar panel with listing creation form. #[component] -fn Sidebar(sidebar_open: RwSignal<bool>) -> impl IntoView { +fn Sidebar( + sidebar_open: RwSignal<bool>, + create_action: ServerAction<CreateListing>, +) -> impl IntoView { + let pending = create_action.pending(); + let value = create_action.value(); + let style = move || { let translate = if sidebar_open.get() { "translateX(0%)" @@ -125,17 +144,71 @@ fn Sidebar(sidebar_open: RwSignal<bool>) -> impl IntoView { "translateX(-100%)" }; format!( - "position: fixed; top: 48px; left: 0; bottom: 0; width: 240px; \ + "position: fixed; top: 48px; left: 0; bottom: 0; width: 280px; \ background: #16213e; z-index: 90; \ transition: transform 0.2s ease; \ transform: {translate}; \ - box-shadow: 2px 0 8px rgba(0,0,0,0.15);" + box-shadow: 2px 0 8px rgba(0,0,0,0.15); \ + overflow-y: auto;" ) }; view! { <aside style=style> - <div style="padding: 16px;"></div> + <div style="padding: 16px;"> + <h3 style="color: #6c63ff; margin: 0 0 12px 0; font-size: 14px;"> + "New Listing" + </h3> + <ActionForm action=create_action> + <label style=LABEL_STYLE>"UTXO A *"</label> + <input type="text" name="utxo_a" required style=INPUT_STYLE + placeholder="txid:vout"/> + + <label style=LABEL_STYLE>"Address A *"</label> + <input type="text" name="address_a" required style=INPUT_STYLE + placeholder="tb1q..."/> + + <label style=LABEL_STYLE>"UTXO B"</label> + <input type="text" name="utxo_b" style=INPUT_STYLE + placeholder="txid:vout (optional)"/> + + <label style=LABEL_STYLE>"Address B *"</label> + <input type="text" name="address_b" required style=INPUT_STYLE + placeholder="tb1q..."/> + + <label style=LABEL_STYLE>"Network *"</label> + <select name="network" required style=INPUT_STYLE> + <option value="testnet">"Testnet"</option> + <option value="mainnet">"Mainnet"</option> + <option value="signet">"Signet"</option> + </select> + + <label style=LABEL_STYLE>"Tags"</label> + <input type="text" name="tags" style=INPUT_STYLE + placeholder="swap, atomic (comma-separated)"/> + + <button + type="submit" + disabled=move || pending.get() + style="width: 100%; margin-top: 12px; padding: 8px; \ + background: #6c63ff; color: #fff; border: none; \ + border-radius: 4px; cursor: pointer; font-size: 13px; \ + font-weight: 600;" + > + {move || if pending.get() { "Creating..." } else { "Create Listing" }} + </button> + </ActionForm> + + {move || { + value.get().and_then(|r| r.err()).map(|e| { + view! { + <p style="color: #f72585; font-size: 12px; margin-top: 8px;"> + {e.to_string()} + </p> + } + }) + }} + </div> </aside> } } @@ -152,8 +225,130 @@ fn HomePage() -> impl IntoView { #[component] fn ListingsPage() -> impl IntoView { + let search = RwSignal::new(String::new()); + let version = expect_context::<RwSignal<usize>>(); + + let listings = Resource::new( + move || (search.get(), version.get()), + |(s, _)| list_listings(s), + ); + view! { <h1 style="color: #6c63ff;">"Listings"</h1> - <p>"No listings yet."</p> + <input + type="text" + placeholder="Search by address, UTXO, or tag..." + prop:value=move || search.get() + on:input=move |ev| search.set(event_target_value(&ev)) + style="width: 100%; max-width: 500px; padding: 10px; background: #1a1a2e; \ + border: 1px solid #333; color: #e0e0e0; border-radius: 4px; \ + margin-bottom: 16px; box-sizing: border-box;" + /> + <Suspense fallback=|| view! { <p>"Loading listings..."</p> }> + {move || Suspend::new(async move { + match listings.await { + Ok(items) if items.is_empty() => { + view! { <p>"No listings found."</p> }.into_any() + } + Ok(items) => { + items + .into_iter() + .map(|l| view! { <ListingCard listing=l/> }) + .collect_view() + .into_any() + } + Err(e) => { + view! { + <p style="color: #f72585;">"Error: " {e.to_string()}</p> + } + .into_any() + } + } + })} + </Suspense> + } +} + +#[component] +fn ListingCard(listing: Listing) -> impl IntoView { + let Listing { + id, + utxo_a, + amount_a_sats, + address_a, + utxo_b, + amount_b_sats, + address_b, + network, + tags, + .. + } = listing; + + let utxo_b_view = utxo_b.map(|u| { + view! { + <p style="margin: 4px 0;"> + "UTXO B: " <code>{u}</code> + </p> + } + }); + let amount_b_view = amount_b_sats.map(|a| { + view! { + <p style="margin: 4px 0;"> + "Amount B: " {a} " sats" + </p> + } + }); + let tags_view = (!tags.is_empty()).then(|| { + let chips = tags + .into_iter() + .map(|t| { + view! { + <span style="background: #f72585; color: #fff; padding: 2px 8px; \ + border-radius: 12px; font-size: 11px;"> + {t} + </span> + } + }) + .collect_view(); + view! { + <div style="margin-top: 8px; display: flex; gap: 4px; flex-wrap: wrap;"> + {chips} + </div> + } + }); + + view! { + <div style="background: #1a1a2e; border-radius: 8px; padding: 16px; \ + margin-bottom: 12px; max-width: 600px;"> + <div style="display: flex; justify-content: space-between; \ + align-items: center; margin-bottom: 8px;"> + <span style="color: #6c63ff; font-weight: 700;"> + "#" {id} + </span> + <span style="background: #6c63ff; color: #fff; padding: 2px 8px; \ + border-radius: 4px; font-size: 12px;"> + {network} + </span> + </div> + <div style="font-size: 13px; color: #ccc;"> + <p style="margin: 4px 0;"> + "UTXO A: " <code>{utxo_a}</code> + </p> + <p style="margin: 4px 0;"> + "Amount A: " {amount_a_sats} " sats" + </p> + <p style="margin: 4px 0;"> + "Address A: " + <code style="word-break: break-all;">{address_a}</code> + </p> + {utxo_b_view} + {amount_b_view} + <p style="margin: 4px 0;"> + "Address B: " + <code style="word-break: break-all;">{address_b}</code> + </p> + </div> + {tags_view} + </div> } } diff --git a/src/lib.rs b/src/lib.rs index c4c2bf2..9db3f28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ pub mod api; #[cfg(any(feature = "server", feature = "hydrate"))] pub mod app; +#[cfg(any(feature = "server", feature = "hydrate"))] +pub mod listing; #[cfg(feature = "server")] pub mod server; #[cfg(feature = "api")] diff --git a/src/listing.rs b/src/listing.rs new file mode 100644 index 0000000..3e8199c --- /dev/null +++ b/src/listing.rs @@ -0,0 +1,204 @@ +use leptos::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Listing { + pub id: u64, + pub utxo_a: String, + pub amount_a_sats: u64, + pub address_a: String, + pub utxo_b: Option<String>, + pub amount_b_sats: Option<u64>, + pub address_b: String, + pub network: String, + pub tags: Vec<String>, + pub created_at: u64, + #[serde(default)] + pub script_pubkey_a: Option<String>, + #[serde(default)] + pub script_pubkey_b: Option<String>, +} + +#[cfg(feature = "server")] +mod store { + use super::Listing; + use leptos::prelude::ServerFnError; + use std::path::PathBuf; + use std::sync::Arc; + use tokio::sync::RwLock; + + #[derive(serde::Deserialize)] + struct EsploraTransaction { + vout: Vec<EsploraVout>, + } + + #[derive(serde::Deserialize)] + struct EsploraVout { + scriptpubkey: String, + value: u64, + } + + fn esplora_base_url(network: &str) -> String { + if let Ok(url) = std::env::var("ENTANGLE_ESPLORA_URL") { + return url; + } + match network { + "mainnet" => "https://mempool.space/api".into(), + "testnet" | "testnet4" => "https://mempool.space/testnet4/api".into(), + "signet" => "https://mempool.space/signet/api".into(), + _ => "https://mempool.space/testnet4/api".into(), + } + } + + pub async fn fetch_utxo(network: &str, outpoint: &str) -> Result<(u64, String), ServerFnError> { + let (txid, vout_str) = outpoint + .rsplit_once(':') + .ok_or_else(|| ServerFnError::new("invalid outpoint format, expected txid:vout"))?; + let vout_idx: usize = vout_str + .parse() + .map_err(|_| ServerFnError::new("invalid vout index"))?; + + let base = esplora_base_url(network); + let url = format!("{base}/tx/{txid}"); + + let tx: EsploraTransaction = reqwest::get(&url) + .await + .map_err(|e| ServerFnError::new(format!("esplora request failed: {e}")))? + .json() + .await + .map_err(|e| ServerFnError::new(format!("esplora response parse failed: {e}")))?; + + let output = tx + .vout + .get(vout_idx) + .ok_or_else(|| ServerFnError::new(format!("vout index {vout_idx} out of range")))?; + + Ok((output.value, output.scriptpubkey.clone())) + } + + struct Inner { + listings: RwLock<Vec<Listing>>, + path: PathBuf, + } + + #[derive(Clone)] + pub struct ListingStore { + inner: Arc<Inner>, + } + + impl ListingStore { + pub fn load(path: impl Into<PathBuf>) -> Self { + let path = path.into(); + let listings = if path.exists() { + let data = std::fs::read_to_string(&path).unwrap_or_default(); + serde_json::from_str(&data).unwrap_or_default() + } else { + Vec::new() + }; + ListingStore { + inner: Arc::new(Inner { + listings: RwLock::new(listings), + path, + }), + } + } + + pub async fn create(&self, mut listing: Listing) -> Listing { + let mut listings = self.inner.listings.write().await; + listing.id = listings.iter().map(|l| l.id).max().unwrap_or(0) + 1; + listing.created_at = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + listings.push(listing.clone()); + self.save(&listings); + listing + } + + pub async fn search(&self, query: &str) -> Vec<Listing> { + let listings = self.inner.listings.read().await; + if query.is_empty() { + return listings.clone(); + } + let q = query.to_lowercase(); + listings + .iter() + .filter(|l| { + l.utxo_a.to_lowercase().contains(&q) + || l.address_a.to_lowercase().contains(&q) + || l.address_b.to_lowercase().contains(&q) + || l.utxo_b + .as_ref() + .is_some_and(|u| u.to_lowercase().contains(&q)) + || l.network.to_lowercase().contains(&q) + || l.tags.iter().any(|t| t.to_lowercase().contains(&q)) + }) + .cloned() + .collect() + } + + fn save(&self, listings: &[Listing]) { + if let Ok(data) = serde_json::to_string_pretty(listings) { + let _ = std::fs::write(&self.inner.path, data); + } + } + } +} + +#[cfg(feature = "server")] +pub use store::ListingStore; + +#[server] +pub async fn create_listing( + utxo_a: String, + address_a: String, + utxo_b: String, + address_b: String, + network: String, + tags: String, +) -> Result<Listing, ServerFnError> { + let axum::Extension(store): axum::Extension<ListingStore> = leptos_axum::extract().await?; + + let (amount_a_sats, script_pubkey_a) = store::fetch_utxo(&network, &utxo_a).await?; + + let utxo_b = if utxo_b.trim().is_empty() { + None + } else { + Some(utxo_b) + }; + let (amount_b_sats, script_pubkey_b) = if let Some(ref ub) = utxo_b { + let (amt, spk) = store::fetch_utxo(&network, ub).await?; + (Some(amt), Some(spk)) + } else { + (None, None) + }; + + let tags: Vec<String> = tags + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + + let listing = Listing { + id: 0, + utxo_a, + amount_a_sats, + address_a, + utxo_b, + amount_b_sats, + address_b, + network, + tags, + created_at: 0, + script_pubkey_a: Some(script_pubkey_a), + script_pubkey_b, + }; + + Ok(store.create(listing).await) +} + +#[server] +pub async fn list_listings(search: String) -> Result<Vec<Listing>, ServerFnError> { + let axum::Extension(store): axum::Extension<ListingStore> = leptos_axum::extract().await?; + Ok(store.search(&search).await) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index d9e39f1..e809def 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,11 +3,12 @@ use leptos::config::LeptosOptions; use leptos_axum::{LeptosRoutes, generate_route_list}; use crate::app::{App, shell}; +use crate::listing::ListingStore; /// Build the server router with Leptos SSR. /// /// When `with_api` is true, the API routes are nested under `/api`. -pub fn router(leptos_options: LeptosOptions, with_api: bool) -> Router { +pub fn router(leptos_options: LeptosOptions, with_api: bool, store: ListingStore) -> Router { let routes = generate_route_list(App); let mut app = Router::new(); @@ -20,6 +21,7 @@ pub fn router(leptos_options: LeptosOptions, with_api: bool) -> Router { let leptos_options = leptos_options.clone(); move || shell(leptos_options.clone()) }) + .layer(axum::Extension(store)) .fallback(leptos_axum::file_and_error_handler::<LeptosOptions, _>( shell, )) @@ -32,8 +34,9 @@ pub fn router(leptos_options: LeptosOptions, with_api: bool) -> Router { pub async fn serve(bind: &str, with_api: bool) { let conf = leptos::config::get_configuration(Some("Cargo.toml")).unwrap(); let leptos_options = conf.leptos_options; + let store = ListingStore::load("listings.json"); - let app = router(leptos_options, with_api); + let app = router(leptos_options, with_api, store); let listener = tokio::net::TcpListener::bind(bind) .await diff --git a/tests/lib.rs b/tests/lib.rs index 4da0f41..9b635fa 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,4 +1,5 @@ mod api; +mod listing; mod server; mod swap; diff --git a/tests/listing.rs b/tests/listing.rs new file mode 100644 index 0000000..7377ca6 --- /dev/null +++ b/tests/listing.rs @@ -0,0 +1,139 @@ +pub(crate) use entangle::listing::{Listing, ListingStore}; + +fn temp_path(name: &str) -> std::path::PathBuf { + std::env::temp_dir().join(format!("entangle_test_{name}.json")) +} + +fn make_listing() -> Listing { + Listing { + id: 0, + utxo_a: "aabb:0".into(), + amount_a_sats: 50_000, + address_a: "tb1qaddr_a".into(), + utxo_b: None, + amount_b_sats: None, + address_b: "tb1qaddr_b".into(), + network: "testnet".into(), + tags: vec!["swap".into()], + created_at: 0, + script_pubkey_a: None, + script_pubkey_b: None, + } +} + +#[tokio::test] +async fn create_and_search() { + let path = temp_path("create_search"); + let _ = std::fs::remove_file(&path); + + let store = ListingStore::load(&path); + let created = store.create(make_listing()).await; + + assert_eq!(created.id, 1); + assert!(created.created_at > 0); + + let results = store.search("aabb").await; + assert_eq!(results.len(), 1); + assert_eq!(results[0].utxo_a, "aabb:0"); + + let results = store.search("nonexistent").await; + assert!(results.is_empty()); + + let _ = std::fs::remove_file(&path); +} + +#[tokio::test] +async fn persistence_across_loads() { + let path = temp_path("persistence"); + let _ = std::fs::remove_file(&path); + + { + let store = ListingStore::load(&path); + store.create(make_listing()).await; + } + + let store = ListingStore::load(&path); + let results = store.search("").await; + assert_eq!(results.len(), 1); + assert_eq!(results[0].id, 1); + + let _ = std::fs::remove_file(&path); +} + +#[tokio::test] +async fn case_insensitive_search() { + let path = temp_path("case_insensitive"); + let _ = std::fs::remove_file(&path); + + let store = ListingStore::load(&path); + let mut listing = make_listing(); + listing.utxo_a = "AABB:0".into(); + listing.tags = vec!["Atomic".into()]; + store.create(listing).await; + + let results = store.search("aabb").await; + assert_eq!(results.len(), 1); + + let results = store.search("atomic").await; + assert_eq!(results.len(), 1); + + let results = store.search("AABB").await; + assert_eq!(results.len(), 1); + + let _ = std::fs::remove_file(&path); +} + +#[tokio::test] +async fn search_matches_address_and_tags() { + let path = temp_path("search_fields"); + let _ = std::fs::remove_file(&path); + + let store = ListingStore::load(&path); + store.create(make_listing()).await; + + // Search by address_a + assert_eq!(store.search("addr_a").await.len(), 1); + // Search by address_b + assert_eq!(store.search("addr_b").await.len(), 1); + // Search by tag + assert_eq!(store.search("swap").await.len(), 1); + // Search by network + assert_eq!(store.search("testnet").await.len(), 1); + + let _ = std::fs::remove_file(&path); +} + +#[tokio::test] +async fn empty_search_returns_all() { + let path = temp_path("empty_search"); + let _ = std::fs::remove_file(&path); + + let store = ListingStore::load(&path); + store.create(make_listing()).await; + + let mut listing2 = make_listing(); + listing2.utxo_a = "ccdd:1".into(); + store.create(listing2).await; + + let results = store.search("").await; + assert_eq!(results.len(), 2); + + let _ = std::fs::remove_file(&path); +} + +#[tokio::test] +async fn ids_auto_increment() { + let path = temp_path("auto_increment"); + let _ = std::fs::remove_file(&path); + + let store = ListingStore::load(&path); + let a = store.create(make_listing()).await; + let b = store.create(make_listing()).await; + let c = store.create(make_listing()).await; + + assert_eq!(a.id, 1); + assert_eq!(b.id, 2); + assert_eq!(c.id, 3); + + let _ = std::fs::remove_file(&path); +} diff --git a/tests/server.rs b/tests/server.rs index 0ca11d1..357272e 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -5,11 +5,17 @@ use tower::ServiceExt; use crate::*; +fn temp_store() -> listing::ListingStore { + let path = std::env::temp_dir().join("entangle_test_server.json"); + let _ = std::fs::remove_file(&path); + listing::ListingStore::load(path) +} + #[tokio::test] async fn with_api_nesting() { let conf = get_configuration(None).unwrap(); let leptos_options = conf.leptos_options; - let app = entangle::server::router(leptos_options, true); + let app = entangle::server::router(leptos_options, true, temp_store()); let response = app .oneshot( Request::builder()